text: /*******************************************************************************
   * Copyright (c) 2013-2017, Andrés Martinelli <andmarti@gmail.com              *
   * All rights reserved.                                                        *
   *                                                                             *
   * This file is a part of SC-IM                                                *
   *                                                                             *
   * SC-IM is a spreadsheet program that is based on SC. The original authors    *
   * of SC are James Gosling and Mark Weiser, and mods were later added by       *
   * Chuck Martin.                                                               *
   *                                                                             *
   * Redistribution and use in source and binary forms, with or without          *
   * modification, are permitted provided that the following conditions are met: *
   * 1. Redistributions of source code must retain the above copyright           *
   *    notice, this list of conditions and the following disclaimer.            *
   * 2. Redistributions in binary form must reproduce the above copyright        *
   *    notice, this list of conditions and the following disclaimer in the      *
   *    documentation and/or other materials provided with the distribution.     *
   * 3. All advertising materials mentioning features or use of this software    *
   *    must display the following acknowledgement:                              *
   *    This product includes software developed by Andrés Martinelli            *
   *    <andmarti@gmail.com>.                                                    *
   * 4. Neither the name of the Andrés Martinelli nor the                        *
   *   names of other contributors may be used to endorse or promote products    *
   *   derived from this software without specific prior written permission.     *
   *                                                                             *
   * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY            *
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED   *
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE      *
   * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY           *
   * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES  *
   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT  *
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE       *
   * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.           *
   *******************************************************************************/
  
  /**
   * \file undo.c
   * \author Andrés Martinelli <andmarti@gmail.com>
   * \date 2017-07-18
   * \brief TODO Write a tbrief file description.
   */
  
  #ifdef UNDO
  /*
   * UNDO and REDO features works with an 'undo' struct list.
   * Which contains:
   *       p_ant: pointer to 'undo' struct. If NULL, this node is the first change
   *              for the session.
   *       struct ent * added: 'ent' elements added by the change
   *       struct ent * removed: 'ent' elements removed by the change
   *       struct ent * aux_ents: 'ent' elements that needs to be around, but out of tbl.
   *                               these are used to update formulas correctly, upon shift/deletion of ents.
   *       struct undo_range_shift * range_shift: range shifted by change
   *       row_hidded: integers list (int *) hidden rows on screen
   *       row_showed: integers list (int *) visible rows on screen
   *       col_hidded: integers list (int *) hidden columns on screen
   *       col_showed: integers list (int *) visible columns on screen
   *              NOTE: the first position of the lists contains (number of elements - 1) in the list
   *       struct undo_cols_format * cols_format: list of 'undo_col_info' elements used for
   *              undoing / redoing changes in columns format (fwidth, precision y realfmt)
   *       p_sig: pointer to 'undo' struct, If NULL, this node is the last change in
   *              the session.
   *
   * Follows one level UNDO/REDO scheme. A change (C1) is made, then an UNDO operation, and
   * another change (C2). From there later changes are removed.
   * Scheme:
   *
   * + C1 -> + -> UNDO -
   * ^                 \
   * |_                |
   *   \---------------/
   * |
   * |
   * |
   *  \-> C2 --> + ...
   *
   * undo_shift_range struct contains:
   *     int delta_rows: delta rows for the range shift
   *     int delta_cols: delta columns for the range shift
   *     int tlrow:      Upper left row defining the range of the shift
   *                     (As if a cell range shift is made)
   *     int tlcol:      Upper left column defining the range of the shift
   *                     (As if a cell range shift is made)
   *     int brrow:      Lower right row defining the range of the shift
   *                     (As if a cell range shift is made)
   *     int brcol:      Lower right column defining the range of the shift
   *                     (As if a cell range shift is made)
   *
   * Implemented actions for UNDO/REDO:
   * 1. Remove content from cell or range
   * 2. Input content into a cell
   * 3. Edit a cell
   * 4. Change alginment of range or cell
   * 5. Paste range or cell
   * 6. Shift range or cell with sh, sj, sk, sl
   * 7. Insert row or column
   * 8. Delete row or column
   * 9. Paste row or column
   * 10. Hide/show rows and columns
   * 11. Sort of a range
   * 12. Change in the format of a range or cell
   * 13. '-' and '+' commands in normal mode
   * 14. Lock and unlock of cells
   * 15. datefmt command
   * 16. Change in format of a column as a result of the 'f' command
   * 17. Change in format of a column as a result of auto_jus
   * 18. Change format of columns as a result of ic dc
   * 19. fill command
   * 20. unformat
   *
   * NOT implemented:
   * 1. undo of freeze / unfreeze command
   */
  
  #include <stdlib.h>
  #include "undo.h"
  #include "macros.h"
  #include "curses.h"
  #include "conf.h"
  #include "sc.h"
  #include "cmds.h"
  #include "marks.h"
  #include "shift.h"
  #include "dep_graph.h"
  
  // undolist
  static struct undo * undo_list = NULL;
  
  // current position in the list
  static int undo_list_pos = 0;
  
  // Number of elements in the list
  static int undo_list_len = 0;
  
  // Temporal variable
  static struct undo undo_item;
  
  /**
   * \brief Init 'unto_item'
   *
   * \return none
   */
  
  void create_undo_action() {
      undo_item.added       = NULL;
      undo_item.removed     = NULL;
      undo_item.p_ant       = NULL;
      undo_item.p_sig       = NULL;
      undo_item.range_shift = NULL;
      undo_item.cols_format = NULL;
      undo_item.aux_ents    = NULL;
  
      undo_item.row_hidded  = NULL;
      undo_item.row_showed  = NULL;
      undo_item.col_hidded  = NULL;
      undo_item.col_showed  = NULL;
      return;
  }
  
  /**
   * @brief TODO Document end_undo_action()
   *
   * \details Save undo_item copy with 'ent' elements modified, and the
   * unto range shift struct into the undolist.
   *
   * \return none
   */
  
  void end_undo_action() {
      add_to_undolist(undo_item);
  
      // in case we need to dismissed this undo_item!
      if ((undo_item.added      == NULL && undo_item.aux_ents == NULL &&
          undo_item.removed     == NULL && undo_item.range_shift == NULL &&
          undo_item.row_hidded  == NULL && undo_item.row_showed  == NULL &&
          undo_item.cols_format == NULL &&
          undo_item.col_hidded  == NULL && undo_item.col_showed  == NULL) || loading) {
          if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant;
          undo_list_pos--;
          clear_from_current_pos();
      }
  
      return;
  }
  
  /**
   * \brief Document add_to_undolist()
   *
   * \details Add an undo node to the undolist. Allocate memory for
   * undo struct. Fill variable with undo_item value and append it
   * to the list.
   *
   * \param[in] u
   *
   * \return none
   */
  
  void add_to_undolist(struct undo u) {
      // If not at the end of the list, remove from the end
      if ( undo_list != NULL && undo_list_pos != len_undo_list() )
          clear_from_current_pos();
  
      struct undo * ul = (struct undo *) malloc (sizeof(struct undo));
      ul->p_sig = NULL;
  
      // Add 'ent' elements
      ul->added = u.added;
      ul->removed = u.removed;
      ul->aux_ents = u.aux_ents;
      ul->range_shift = u.range_shift;
      ul->cols_format = u.cols_format;
      ul->row_hidded = u.row_hidded;
      ul->col_hidded = u.col_hidded;
      ul->row_showed = u.row_showed;
      ul->col_showed = u.col_showed;
  
      if (undo_list == NULL) {
          ul->p_ant = NULL;
          undo_list = ul;
      } else {
          ul->p_ant = undo_list;
  
          // go to end of list
          while (undo_list->p_sig != NULL) undo_list = undo_list->p_sig;
  
          undo_list->p_sig = ul;
          undo_list = undo_list->p_sig;
      }
      undo_list_pos++;
      undo_list_len++;
      return;
  }
  
  /**
   * \brief Dismiss current undo_item
   *
   * \details This function frees memory of a struct unto. It is used
   * in function free_undo_node for that purpose. But as well, this 
   * function shall be called instead of end_undo_action in case we want
   * to cancel a previous create_undo_action. If called for this purpose,
   * argument shall be NULL.
   *
   * \param[in] ul
   *
   * \return none
   */
  
  void dismiss_undo_item(struct undo * ul) {
      struct ent * en;
      struct ent * de;
  
      if (ul == NULL) ul = &undo_item;
  
      en = ul->added;     // free added
      while (en != NULL) {
          de = en->next;
          clearent(en);
          free(en);
          en = de;
      }
      en = ul->removed;   // free removed
      while (en != NULL) {
          de = en->next;
          clearent(en);
          free(en);
          en = de;
      }
      en = ul->aux_ents; // free aux_ents
      while (en != NULL) {
          de = en->next;
          clearent(en);
          free(en);
          en = de;
      }
      if (ul->range_shift != NULL) free(ul->range_shift); // Free undo_range_shift memory
      if (ul->cols_format != NULL) {                      // Free cols_format memory
          free(ul->cols_format->cols);
          free(ul->cols_format);
      }
      if (ul->row_hidded  != NULL) free(ul->row_hidded); // Free hidden row memory
      if (ul->col_hidded  != NULL) free(ul->col_hidded); // Free hidden col memory
      if (ul->row_showed  != NULL) free(ul->row_showed); // Free showed row memory
      if (ul->col_showed  != NULL) free(ul->col_showed); // Free showed col memory
  
      return;
  }
  
  /**
   * \brief Cascade free UNDO node memory
   *
   * \param[in] ul
   *
   * \return none
   */
  
  void free_undo_node(struct undo * ul) {
      struct undo * e;
  
      // Remove from current position
      while (ul != NULL) {
          dismiss_undo_item(ul);
  
          e = ul->p_sig;
          free(ul);
          undo_list_len--;
          ul = e;
      }
      return;
  }
  
  /**
   * \brief Remove nodes below the current position from the undolist
   *
   * \return none
   */
  
  void clear_from_current_pos() {
      if (undo_list == NULL) return;
  
      if (undo_list->p_ant == NULL) {
          free_undo_node(undo_list);
          undo_list = NULL;
      } else {
          struct undo * ul = undo_list->p_sig; // Previous
          free_undo_node(ul);
          undo_list->p_sig = NULL;
      }
  
      return;
  }
  
  // Remove undolist content
  /**
   * \brief Remove undolist content
   *
   * \return none
   */
  
  void clear_undo_list() {
      if (undo_list == NULL) return;
  
      // Go to the beginning of the list
      while (undo_list->p_ant != NULL ) {
          undo_list = undo_list->p_ant;
      }
  
      struct undo * ul = undo_list;
  
      free_undo_node(ul);
  
      undo_list = NULL;
      undo_list_pos = 0;
  
      return;
  }
  
  /**
   * \brief Returnt the length of the undo list
   *
   * \return length of undolist
   */
  
  int len_undo_list() {
      return undo_list_len;
  }
  
  /**
   * \brief TODO Document copy_to_undostruct()
   *
   * \details Take a range of 'ent' elements and create new ones (as many
   * as there are elements inside the specified range). Then copy the content
   * of the original ones to the new ones and save them into the 'added' or
   * 'removed' list of undo_items, according to the char type.
   *
   * Example usage:
   * @code
   *     <function name>();
   * @endcode
   * returns: none
   */
  
  void copy_to_undostruct (int row_desde, int col_desde, int row_hasta, int col_hasta, char type) {
      int c, r;
      struct ent * p;
      int repeated;
  
      for (r = row_desde; r <= row_hasta; r++)
          for (c = col_desde; c <= col_hasta; c++) {
              p = *ATBL(tbl, r, c);
              if (p == NULL) continue;
  
              // here check that ent to add is not already in the list
              // if so, avoid to add a duplicate ent
              struct ent * lista = type == 'a' ? undo_item.added : undo_item.removed;
              repeated = 0;
              while (lista != NULL) {
                  if (lista->row == r && lista->col == c) {
                      repeated = 1;
                      break;
                  }
                  lista = lista->next;
              }
              if (repeated) continue;
  
              // not repeated - we malloc an ent and add it to list
              struct ent * e = (struct ent *) malloc( (unsigned) sizeof(struct ent) );
              cleanent(e);
              //copyent(e, lookat(r, c), 0, 0, 0, 0, 0, 0, 0);
              copyent(e, lookat(r, c), 0, 0, 0, 0, 0, 0, 'u');
  
              // Append 'ent' element at the beginning
              if (type == 'a') {
                  e->next = undo_item.added;
                  undo_item.added = e;
              } else {
                  e->next = undo_item.removed;
                  undo_item.removed = e;
              }
  
          }
      return;
  }
  
  /**
   * \brief TODO Codument add_undo_aux_ent()
   *
   * \details This function is used to keep aux ents in the undo struct.
   * This is used for undoing dr, dc, sk, and sh commands. It stores a copy
   * of the ent received as a parameter and returns the pointer to that copy.
   *
   * \return pointer to a copied ent
   */
  
  struct ent * add_undo_aux_ent(struct ent * e) {
      struct ent * e_new = (struct ent *) malloc( (unsigned) sizeof(struct ent) );
      cleanent(e_new);
      copyent(e_new, e, 0, 0, 0, 0, 0, 0, 'u'); // copy ent with special='u' (undo)
      e_new->next = undo_item.aux_ents;         // add e_new to beginning of list
      undo_item.aux_ents = e_new;
      return e_new;
  }
  
  /**
   * \brief TODO Document add_undo_col_format()
   *
   * \param[in] col
   * \param[in] type
   * \param[in] fwidth
   * \param[in] precision
   * \param[in] realfmt
   *
   * \return none
   */
  
  void add_undo_col_format(int col, int type, int fwidth, int precision, int realfmt) {
      if (undo_item.cols_format == NULL) {
          undo_item.cols_format = (struct undo_cols_format *) malloc( sizeof(struct undo_cols_format));
          undo_item.cols_format->length = 1;
          undo_item.cols_format->cols = (struct undo_col_info *) malloc( sizeof(struct undo_col_info));
      } else {
          undo_item.cols_format->length++;
          undo_item.cols_format->cols = (struct undo_col_info *) realloc(undo_item.cols_format->cols, undo_item.cols_format->length * sizeof(struct undo_col_info));
      }
      undo_item.cols_format->cols[ undo_item.cols_format->length - 1].type = type;
      undo_item.cols_format->cols[ undo_item.cols_format->length - 1].col = col;
      undo_item.cols_format->cols[ undo_item.cols_format->length - 1].fwidth = fwidth;
      undo_item.cols_format->cols[ undo_item.cols_format->length - 1].precision = precision;
      undo_item.cols_format->cols[ undo_item.cols_format->length - 1].realfmt = realfmt;
      return;
  }
  
  /**
   * \brief TODO Document save_undo_range_shift()
   *
   * \detials Take a range, a rows and columns delta and save them into
   * they undo struct. Used to shift ranges when UNDO or REDO without
   * duplicating 'ent'elements.
   *
   * \param[in] delta_rows
   * \param[in] delta_cols
   * \param[in] tlrow
   * \param[in] tlcol
   * \param[in] brrow
   * \param[in] brcol
   *
   * \return none
   */
  
  void save_undo_range_shift(int delta_rows, int delta_cols, int tlrow, int tlcol, int brrow, int brcol) {
      struct undo_range_shift * urs = (struct undo_range_shift *) malloc( (unsigned) sizeof(struct undo_range_shift ) );
      urs->delta_rows = delta_rows;
      urs->delta_cols = delta_cols;
      urs->tlrow = tlrow;
      urs->tlcol = tlcol;
      urs->brrow = brrow;
      urs->brcol = brcol;
      undo_item.range_shift = urs;
      return;
  }
  
  /*
   * This function is used for undoing and redoing
   * changes caused by commands that hide/show rows/columns of screen
   * such as Zr Zc Sc Sr commands.
   * it stores in four different lists (int * list) the row or columns numbers
   * that are showed or hidden because of a change.
   * As these lists are dynamically built, in the first position of every list,
   * we always store the number of elements that the list has.
   */
  /**
   * \brief TODO Document undo_hide_show()
   *
   * \details This function is used for undoint and redoing changes
   * caused by commands that hide/show rows/columns of screen such
   * as Zr, Zc, Sc, and Sr commands.
   *
   * It stores in four different lists (int * list) the row or column numbers
   * that are shown or hidden because of a change. As these lists are
   * dynamically built, in the first position of every list, we always store
   * the number of elements that the list has.
   *
   * \param[in] row
   * \param[in] col
   * \param[in] type
   * \param[in] arg
   *
   * \return none
   */
  
  void undo_hide_show(int row, int col, char type, int arg) {
      int i;
      if (type == 'h') {
          if (row > -1) {        // hide row
              if (undo_item.row_hidded == NULL) {
                  undo_item.row_hidded = (int *) malloc(sizeof(int) * (arg + 1));
                  undo_item.row_hidded[0] = 0;
              } else
                  undo_item.row_hidded = (int *) realloc(undo_item.row_hidded, sizeof(int) * (undo_item.row_hidded[0] + arg + 1));
  
              for (i=0; i < arg; i++)
                  undo_item.row_hidded[undo_item.row_hidded[0] + i + 1] = row + i;
  
              undo_item.row_hidded[0] += arg; // keep in first position the number of elements (rows)
  
          } else if (col > -1) { // hide col
              if (undo_item.col_hidded == NULL) {
                  undo_item.col_hidded = (int *) malloc(sizeof(int) * (arg + 1));
                  undo_item.col_hidded[0] = 0;
              } else
                  undo_item.col_hidded = (int *) realloc(undo_item.col_hidded, sizeof(int) * (undo_item.col_hidded[0] + arg + 1));
  
              for (i=0; i < arg; i++)
                  undo_item.col_hidded[undo_item.col_hidded[0] + i + 1] = col + i;
  
              undo_item.col_hidded[0] += arg; // keep in first position the number of elements (cols)
          }
  
      } else if (type == 's') {
          if (row > -1) {        // show row
              if (undo_item.row_showed == NULL) {
                  undo_item.row_showed = (int *) malloc(sizeof(int) * (arg + 1));
                  undo_item.row_showed[0] = 0;
              } else
                  undo_item.row_showed = (int *) realloc(undo_item.row_showed, sizeof(int) * (undo_item.row_showed[0] + arg + 1));
  
              for (i=0; i < arg; i++)
                  undo_item.row_showed[undo_item.row_showed[0] + i + 1] = row + i;
  
              undo_item.row_showed[0] += arg; // keep in first position the number of elements (rows)
  
          } else if (col > -1) { // show col
              if (undo_item.col_showed == NULL) {
                  undo_item.col_showed = (int *) malloc(sizeof(int) * (arg + 1));
                  undo_item.col_showed[0] = 0;
              } else
                  undo_item.col_showed = (int *) realloc(undo_item.col_showed, sizeof(int) * (undo_item.col_showed[0] + arg + 1));
  
              for (i=0; i < arg; i++)
                  undo_item.col_showed[undo_item.col_showed[0] + i + 1] = col + i;
  
              undo_item.col_showed[0] += arg; // keep in first position the number of elements (cols)
  
          }
      }
      return;
  }
  
  /**
   * \brief Do UNDO operation
   *
   * Shift a range of an undo shift range to the original position, if any,
   * append 'ent' elements 'removed' and remove those from 'added'.
   *
   * \return none
   */
  
  void do_undo() {
      if (undo_list == NULL || undo_list_pos == 0) {
          sc_error("No UNDO's left");
          return;
      }
      //sc_info("%d %d", undo_list_pos, len_undo_list());
  
      int ori_currow = currow;
      int ori_curcol = curcol;
      int mf = modflg; // save modflag status
  
      struct undo * ul = undo_list;
  
      // removed added ents
      struct ent * i = ul->added;
      while (i != NULL) {
          struct ent * pp = *ATBL(tbl, i->row, i->col);
          clearent(pp);
          cleanent(pp);
          i = i->next;
      }
  
      // Make undo shift, if any
      if (ul->range_shift != NULL) {
          // fix marks
          if (ul->range_shift->delta_rows > 0)      // sj
              fix_marks(-(ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
          else if (ul->range_shift->delta_rows < 0) // sk
              fix_marks( (ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
          if (ul->range_shift->delta_cols > 0)      // sl
              fix_marks(0, -(ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
          else if (ul->range_shift->delta_cols < 0) // sh
              fix_marks(0,  (ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
  
          shift_range(- ul->range_shift->delta_rows, - ul->range_shift->delta_cols,
              ul->range_shift->tlrow, ul->range_shift->tlcol, ul->range_shift->brrow, ul->range_shift->brcol);
  
          // shift col_formats here
          if (ul->range_shift->tlcol >= 0 && ul->range_shift->tlrow == 0 && ul->range_shift->brrow == maxrow) { // && ul->range_shift->delta_cols > 0) {
              int i;
              if (ul->range_shift->delta_cols > 0)
              for (i = ul->range_shift->brcol + ul->range_shift->delta_cols; i <= maxcol; i++) {
                  fwidth[i - ul->range_shift->delta_cols] = fwidth[i];
                  precision[i - ul->range_shift->delta_cols] = precision[i];
                  realfmt[i - ul->range_shift->delta_cols] = realfmt[i];
              }
              else
              for (i = maxcol; i >= ul->range_shift->tlcol - ul->range_shift->delta_cols; i--) {
                   fwidth[i] = fwidth[i + ul->range_shift->delta_cols];
                   precision[i] = precision[i + ul->range_shift->delta_cols];
                   realfmt[i] = realfmt[i + ul->range_shift->delta_cols];
              }
          }
      }
  
      //update(TRUE); //FIXME remove this line. its just to help debugging
  
      // Change cursor position
      //if (ul->removed != NULL) {
      //    currow = ul->removed->row;
      //    curcol = ul->removed->col;
      //}
  
      // Append 'ent' elements from the removed ones
      struct ent * j = ul->removed;
      while (j != NULL) {
          struct ent * h;
          if ((h = *ATBL(tbl, j->row, j->col))) clearent(h);
          struct ent * e_now = lookat(j->row, j->col);
          (void) copyent(e_now, j, 0, 0, 0, 0, 0, 0, 0);
          j = j->next;
      }
  
      // Show hidden cols and rows
      // Hide visible cols and rows
      if (ul->col_hidded != NULL) {
          int * pd = ul->col_hidded;
          int left = *(pd++);
          while (left--) {
              col_hidden[*(pd++)] = FALSE;
          }
      }
      else if (ul->col_showed  != NULL) {
          int * pd = ul->col_showed;
          int left = *(pd++);
          while (left--) {
              col_hidden[*(pd++)] = TRUE;
          }
      }
      else if (ul->row_hidded  != NULL) {
          int * pd = ul->row_hidded;
          int left = *(pd++);
          while (left--) {
              row_hidden[*(pd++)] = FALSE;
          }
      }
      else if (ul->row_showed  != NULL) {
          int * pd = ul->row_showed;
          int left = *(pd++);
          while (left--) {
              row_hidden[*(pd++)] = TRUE;
          }
      }
  
      // Restore previous col format
      if (ul->cols_format != NULL) {
          struct undo_cols_format * uf = ul->cols_format;
          int size = uf->length;
          int i;
  
          for (i=0; i < size; i++) {
             if (uf->cols[i].type == 'R') {
                 fwidth[uf->cols[i].col]    = uf->cols[i].fwidth;
                 precision[uf->cols[i].col] = uf->cols[i].precision;
                 realfmt[uf->cols[i].col]   = uf->cols[i].realfmt;
             }
          }
      }
  
      // for every ent in added and removed, we reeval expression to update graph
      struct ent * ie = ul->added;
      while (ie != NULL) {
          struct ent * p;
          if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
              EvalJustOneVertex(p, ie->row, ie->col, 1);
          ie = ie->next;
      }
      ie = ul->removed;
      while (ie != NULL) {
          struct ent * p;
          if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
              EvalJustOneVertex(p, ie->row, ie->col, 1);
          ie = ie->next;
      }
  
      // Restores cursor position
      currow = ori_currow;
      curcol = ori_curcol;
  
      // decrease modflg
      modflg= mf - 1;
  
      if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant;
      undo_list_pos--;
      sc_info("Change: %d of %d", undo_list_pos, len_undo_list());
      return;
  }
  
  /**
   * \brief Do REDO
   *
   * Shift a range of an undo shift range to the original position, if any,
   * append 'ent' elements from 'added' and remove those from 'removed'.
   *
   * \return none
   */
  
  void do_redo() {
      //if ( undo_list == NULL || undo_list_pos == len_undo_list()  ) {
      //FIXME check why undo_list_pos can sometimes be > len_undo_list(). it shouldnt!!
      if ( undo_list == NULL || undo_list_pos >= len_undo_list()  ) {
          sc_error("No REDO's left");
          return;
      }
      //sc_info("%d %d", undo_list_pos, len_undo_list());
  
      int ori_currow = currow;
      int ori_curcol = curcol;
      int mf = modflg; // save modflag status
  
      if (undo_list->p_ant == NULL && undo_list_pos == 0);
      else if (undo_list->p_sig != NULL) undo_list = undo_list->p_sig;
  
      struct undo * ul = undo_list;
  
      // Remove 'ent' elements
      struct ent * i = ul->removed;
      while (i != NULL) {
          struct ent * pp = *ATBL(tbl, i->row, i->col);
          clearent(pp);
          cleanent(pp);
          i = i->next;
      }
  
      // Make undo shift, if any
      if (ul->range_shift != NULL) {
          // fix marks
          if (ul->range_shift->delta_rows > 0)      // sj
              fix_marks( (ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
          else if (ul->range_shift->delta_rows < 0) // sk
              fix_marks(-(ul->range_shift->brrow - ul->range_shift->tlrow + 1), 0, ul->range_shift->tlrow, maxrow, ul->range_shift->tlcol, ul->range_shift->brcol);
          if (ul->range_shift->delta_cols > 0)      // sl
              fix_marks(0,  (ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
          else if (ul->range_shift->delta_cols < 0) // sh
              fix_marks(0, -(ul->range_shift->brcol - ul->range_shift->tlcol + 1), ul->range_shift->tlrow, ul->range_shift->brrow, ul->range_shift->tlcol, maxcol);
  
          shift_range(ul->range_shift->delta_rows, ul->range_shift->delta_cols,
              ul->range_shift->tlrow, ul->range_shift->tlcol, ul->range_shift->brrow, ul->range_shift->brcol);
  
          // shift col_formats here
          if (ul->range_shift->tlcol >= 0 && ul->range_shift->tlrow == 0 && ul->range_shift->brrow == maxrow) {
              int i;
              if (ul->range_shift->delta_cols > 0)
              for (i = maxcol; i >= ul->range_shift->tlcol + ul->range_shift->delta_cols; i--) {
                  fwidth[i] = fwidth[i - ul->range_shift->delta_cols];
                  precision[i] = precision[i - ul->range_shift->delta_cols];
                  realfmt[i] = realfmt[i - ul->range_shift->delta_cols];
              }
              else
              for (i = ul->range_shift->tlcol; i - ul->range_shift->delta_cols <= maxcol; i++) {
                  fwidth[i] = fwidth[i - ul->range_shift->delta_cols];
                  precision[i] = precision[i - ul->range_shift->delta_cols];
                  realfmt[i] = realfmt[i - ul->range_shift->delta_cols];
              }
          }
      }
  
      //update(TRUE);
  
      // Change cursor position
      //if (ul->p_sig != NULL && ul->p_sig->removed != NULL) {
      //    currow = ul->p_sig->removed->row;
      //    curcol = ul->p_sig->removed->col;
      //}
  
      // Append 'ent' elements
      struct ent * j = ul->added;
      while (j != NULL) {
          struct ent * h;
          if ((h = *ATBL(tbl, j->row, j->col))) clearent(h);
          struct ent * e_now = lookat(j->row, j->col);
          (void) copyent(e_now, j, 0, 0, 0, 0, 0, 0, 0);
          j = j->next;
      }
  
      // Hide previously hidden cols and rows
      // Show previously visible cols and rows
      if (ul->col_hidded != NULL) {
          int * pd = ul->col_hidded;
          int left = *(pd++);
          while (left--) {
              col_hidden[*(pd++)] = TRUE;
          }
      }
      else if (ul->col_showed  != NULL) {
          int * pd = ul->col_showed;
          int left = *(pd++);
          while (left--) {
              col_hidden[*(pd++)] = FALSE;
          }
      }
      else if (ul->row_hidded  != NULL) {
          int * pd = ul->row_hidded;
          int left = *(pd++);
          while (left--) {
              row_hidden[*(pd++)] = TRUE;
          }
      }
      else if (ul->row_showed  != NULL) {
          int * pd = ul->row_showed;
          int left = *(pd++);
          while (left--) {
              row_hidden[*(pd++)] = FALSE;
          }
      }
  
      // Restore new col format
      if (ul->cols_format != NULL) {
          struct undo_cols_format * uf = ul->cols_format;
          int size = uf->length;
          int i;
  
          for (i=0; i < size; i++) {
              if (uf->cols[i].type == 'A') {
                  fwidth[uf->cols[i].col]    = uf->cols[i].fwidth;
                  precision[uf->cols[i].col] = uf->cols[i].precision;
                  realfmt[uf->cols[i].col]   = uf->cols[i].realfmt;
              }
          }
      }
  
      // for every ent in added and removed, we reeval expression to update graph
      struct ent * ie = ul->added;
      while (ie != NULL) {
          struct ent * p;
          if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
              EvalJustOneVertex(p, ie->row, ie->col, 1);
          ie = ie->next;
      }
      ie = ul->removed;
      while (ie != NULL) {
          struct ent * p;
          if ((p = *ATBL(tbl, ie->row, ie->col)) && p->expr)
              EvalJustOneVertex(p, ie->row, ie->col, 1);
          ie = ie->next;
      }
  
      // Restores cursor position
      currow = ori_currow;
      curcol = ori_curcol;
  
      // increase modflg
      modflg = mf + 1;
  
      sc_info("Change: %d of %d", undo_list_pos + 1, len_undo_list());
      undo_list_pos++;
  
      return;
  }
  #endif
  