/******************************************************************************* * Copyright (c) 2013-2021, Andrés Martinelli * * 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 * * . * * 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 * \date 06/06/2021 * \brief This file contains the main functions to support the undo/redo feature. */ /* * UNDO and REDO feature 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_ptr * added: 'ent' elements added by the change * * struct ent_ptr * removed: 'ent' elements removed by the change * * struct allocation_list * allocations: since we alloc over added and removed * list in batches. we need to keep the first position in memory of each calloc. * * 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 * * row_frozed: integers list (int *) frozen rows on screen * * row_unfrozed: integers list (int *) unfrozen 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 * * col_frozed: integers list (int *) frozen cols on screen * * col_unfrozed: integers list (int *) unfrozen cols on screen * * struct undo_cols_format * cols_format: list of 'undo_col_info' elements used for * undoing / redoing changes in columns format (fwidth, precision y realfmt) * * struct undo_rows_format * rows_format: list of 'undo_row_info' elements used for * undoing / redoing changes in rows format (height) * * p_sig: pointer to 'undo' struct, If NULL, this node is the last change in * the session. * * modflg_bef: document modflg before the change * maxrow_bef: sheet maxrow before the change * maxcol_bef: sheet maxcol before the change * modflg_aft: document modflg after the change * maxrow_aft: sheet maxrow after the change * maxcol_aft: sheet maxcol after the change * * 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 alignment 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. the cellcolor command * 17. Change in format of a column as a result of the 'f' command * 18. Change in format of a column as a result of auto_jus * 19. Change format of columns as a result of ic dc * 20. fill command * 21. unformat * 22. change in the format of rows * 23. undo of freeze / unfreeze commands * */ #ifdef UNDO #include #include "sc.h" #include "undo.h" #include "macros.h" #include "color.h" #include "curses.h" #include "conf.h" #include "cmds/cmds.h" #include "marks.h" #include "actions/shift.h" #include "graph.h" extern struct session * session; // 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 'undo_item' * \return none */ void create_undo_action() { undo_item.added = NULL; undo_item.removed = NULL; undo_item.allocations = NULL; undo_item.p_ant = NULL; undo_item.p_sig = NULL; undo_item.range_shift = NULL; undo_item.cols_format = NULL; undo_item.rows_format = NULL; undo_item.sheet = session->cur_doc->cur_sh; // we should keep the current sheet reference undo_item.row_hidded = NULL; undo_item.row_showed = NULL; undo_item.row_frozed = NULL; undo_item.row_unfrozed = NULL; undo_item.col_hidded = NULL; undo_item.col_showed = NULL; undo_item.col_frozed = NULL; undo_item.col_unfrozed = NULL; undo_item.modflg_bef = session->cur_doc->modflg; undo_item.maxrow_bef = undo_item.sheet->maxrow; undo_item.maxcol_bef = undo_item.sheet->maxcol; return; } /** * @brief end_undo_action() * * \details Save undo_item copy with 'ent' elements modified, and the * undo range shift struct into the undolist. * * \return none */ void end_undo_action() { struct roman * roman = session->cur_doc; undo_item.modflg_aft = roman->modflg; undo_item.maxrow_aft = undo_item.sheet->maxrow; undo_item.maxcol_aft = undo_item.sheet->maxcol; add_to_undolist(undo_item); // just check if we need to dismiss this undo_item! if ((undo_item.added == NULL && undo_item.allocations == NULL && undo_item.removed == NULL && undo_item.range_shift == NULL && undo_item.row_hidded == NULL && undo_item.row_showed == NULL && undo_item.row_frozed == NULL && undo_item.col_frozed == NULL && undo_item.row_unfrozed == NULL && undo_item.col_unfrozed == NULL && undo_item.cols_format == NULL && undo_item.rows_format == NULL && undo_item.col_hidded == NULL && undo_item.col_showed == NULL) || roman->loading) { if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant; undo_list_pos--; clear_from_current_pos(); } return; } /** * \brief 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->sheet = u.sheet; ul->allocations = u.allocations; ul->range_shift = u.range_shift; ul->cols_format = u.cols_format; ul->rows_format = u.rows_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; ul->row_frozed = u.row_frozed; ul->col_frozed = u.col_frozed; ul->row_unfrozed = u.row_unfrozed; ul->col_unfrozed = u.col_unfrozed; if (undo_list == NULL) { ul->p_ant = NULL; undo_list = ul; } else { ul->p_ant = undo_list; // go to end of list // TODO we can improve this by keeping always the last pointer at hand 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 undo item. * It is internally used by free_undo_node(). 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) { if (ul == NULL) ul = &undo_item; // first free inside each added and removed ents // (their labels, expressions, etc. struct ent_ptr * en; struct ent_ptr * de; en = ul->added; // free added while (en != NULL) { de = en->next; clearent(en->vp); free(en->vp); en->vp = NULL; en = de; } en = ul->removed; // free removed while (en != NULL) { de = en->next; clearent(en->vp); free(en->vp); en->vp = NULL; en = de; } // now free added and removed lists // in the way they were alloc'ed (as batches) int i, size = ul->allocations != NULL ? ul->allocations->size : 0; struct ent_ptr ** alls = ul->allocations != NULL ? ul->allocations->items : NULL; for (i = 0; i < size; i++) { free(*alls); // each ent_ptr *alls = NULL; alls++; } if (ul->allocations != NULL) { if (ul->allocations->items != NULL) { free(ul->allocations->items); ul->allocations->items = NULL; } free(ul->allocations); ul->allocations = NULL; } 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->rows_format != NULL) { // Free rows_format memory free(ul->rows_format->rows); free(ul->rows_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->row_frozed != NULL) free(ul->row_frozed); // Free frozed row memory if (ul->col_showed != NULL) free(ul->col_showed); // Free showed col memory if (ul->col_frozed != NULL) free(ul->col_frozed); // Free frozed col memory if (ul->row_unfrozed != NULL) free(ul->row_unfrozed); // Free unfrozed row memory if (ul->col_unfrozed != NULL) free(ul->col_unfrozed); // Free unfrozed 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; } /** * \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 Return the length of the undo list * \return length of undolist */ int len_undo_list() { return undo_list_len; } /* * \brief ent_ptr_exists_on_list * \details check if an ent exists on ent_ptr list * (used for added / removed lists of undo struct) * return 0 if not exists or 1 if exists. */ int ent_ptr_exists_on_list(struct ent_ptr * list, struct ent_ptr * ep) { int repeated = 0; if (ep == NULL) return repeated; while (list != NULL && list->vp != NULL) { if (list->sheet == ep->sheet && list->vp->row == ep->vp->row && list->vp->col == ep->vp->col) { repeated = 1; break; } list = list->next; } return repeated; } /** * \brief copy_to_undostruct() * * \details Take a range of 'ent' elements of a sheet, and create ent copies to keep in undo structs lists * such as the 'added' or 'removed' lists. * * char type: indicates UNDO_ADD ('a') for added list. or UNDO_DEL ('d') for the 'removed' list. * * handle_deps: if set to HANDLE_DEPS it will store the dependencies of the specified range as well. * remember deps is a global variable. * * destination: struct ent_ptr ** to use in the copy. if none was given, just malloc one. * Note that if destination is not null, the struct ent * vp inside the struct ent_ptr is already malloc'ed * returns: none */ void copy_to_undostruct (struct sheet * sh, int ri, int ci, int rf, int cf, char type, short handle_deps, struct ent_ptr ** destination) { int i, c, r; struct ent * p; extern struct ent_ptr * deps; // ask for memory to keep struct ent * for the whole range // and only if no destination pointer was given struct ent_ptr * y_cells = destination == NULL ? NULL : *destination; if (y_cells == NULL && handle_deps == HANDLE_DEPS && deps != NULL) y_cells = (struct ent_ptr *) calloc((rf-ri+1)*(cf-ci+1) + deps->vf, sizeof(struct ent_ptr)); else if (y_cells == NULL) y_cells = (struct ent_ptr *) calloc((rf-ri+1)*(cf-ci+1), sizeof(struct ent_ptr)); // if no destination pointer was given // we save the pointer for future free if (destination == NULL) save_yl_pointer_after_calloc(y_cells); for (r = ri; r <= rf; r++) for (c = ci; c <= cf; c++) { p = *ATBL(sh, sh->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_ptr * lista = type == 'a' ? undo_item.added : undo_item.removed; struct ent_ptr e; e.sheet = sh; e.vp = p; if (ent_ptr_exists_on_list(lista, &e)) continue; // initialize the 'ent' //if (destination == NULL) y_cells->vp = malloc(sizeof(struct ent)); y_cells->vp = malloc(sizeof(struct ent)); cleanent(y_cells->vp); y_cells->sheet = sh; // Copy cell at 'r, c' contents to 'y_cells' ent y_cells->vp->expr = NULL; copyent(y_cells->vp, sh, lookat(sh, r, c), 0, 0, 0, 0, 0, 0, 'u'); // Append 'ent' element at the beginning //TODO: add it ordered? if (type == UNDO_ADD) { y_cells->next = undo_item.added; undo_item.added = y_cells; } else { y_cells->next = undo_item.removed; undo_item.removed = y_cells; } // increase pointer! if (destination == NULL) y_cells++; else *destination = ++y_cells; } // do the same for dependencies if (handle_deps == HANDLE_DEPS) for (i = 0; deps != NULL && i < deps->vf; i++) { p = *ATBL(deps[i].sheet, deps[i].sheet->tbl, deps[i].vp->row, deps[i].vp->col); if (p == NULL) continue; // initialize the 'ent' //if (destination == NULL) y_cells->vp = malloc(sizeof(struct ent)); y_cells->vp = malloc(sizeof(struct ent)); cleanent(y_cells->vp); y_cells->sheet = deps[i].sheet; // Copy cell at deps[i].vp->row, deps[i].vp->col contents to 'y_cells' ent copyent(y_cells->vp, deps[i].sheet, lookat(deps[i].sheet, deps[i].vp->row, deps[i].vp->col), 0, 0, 0, 0, 0, 0, 'u'); //sc_debug("copy_to_undostruct a undo %d %d", deps[i].vp->row, deps[i].vp->col); // Append 'ent' element at the beginning //TODO: add it ordered? if (type == UNDO_ADD) { y_cells->next = undo_item.added; undo_item.added = y_cells; } else { y_cells->next = undo_item.removed; undo_item.removed = y_cells; } if (destination == NULL) y_cells++; else *destination = ++y_cells; } return; } /** * \brief save_yl_pointer_after_calloc() * * \details This function keeps in a pointer array every pointer that was returned by calloc * so we can free them * * \param[in] struct ent e * \return void */ void save_yl_pointer_after_calloc(struct ent_ptr * e) { if (undo_item.allocations == NULL) { undo_item.allocations = malloc(sizeof(struct allocation_list)); undo_item.allocations->size = 0; undo_item.allocations->items = NULL; } undo_item.allocations->items = (struct ent_ptr **) realloc(undo_item.allocations->items, sizeof(struct ent_ptr *) * (++(undo_item.allocations->size))); undo_item.allocations->items[undo_item.allocations->size-1] = e; // keep the pointer so later can be freed } /* * \brief copy_cell_to_undostruct() * * \details This function adds an struct ent * (new) to undo struct lists. * its contents are based on the struct ent * (ori). * could be added list or deleted list depending on the type. * \param[in] struct ent_ptr * e_ptr * \param[in] struct sheet * sh_ori * \param[in] struct ent * ori * \param[in] char type: indicates UNDO_ADD ('a') for added list. or UNDO_DEL ('d') for the 'removed' list. * * the struct ent_ptr * e_ptr should be already alloc'ed * \return void * NOTE: used on yank.c * */ void copy_cell_to_undostruct (struct ent_ptr * e_ptr, struct sheet * sh_ori, struct ent * ori, char type) { /* here check that ent to add is not already in the list * if so, avoid to add a duplicate ent */ struct ent_ptr * lista = type == 'a' ? undo_item.added : undo_item.removed; struct ent_ptr e; e.sheet = sh_ori; e.vp = ori; if (ent_ptr_exists_on_list(lista, &e)) return; // in case of returning because ent exists on list, make sure struct ent_ptr gets freed later. // if reached here, malloc and add to undolist struct ent_ptr * new_ptr = e_ptr; struct ent * new = malloc(sizeof(struct ent)); // initialize the 'ent' cleanent(new); // Copy 'ori' cell contents to 'new' ent copyent(new, sh_ori, ori, 0, 0, 0, 0, 0, 0, 'u'); new_ptr->sheet = sh_ori; new_ptr->vp = new; // Append 'ent' element at the beginning //TODO: add it ordered? if (type == UNDO_ADD) { new_ptr->next = undo_item.added; undo_item.added = new_ptr; } else { new_ptr->next = undo_item.removed; undo_item.removed = new_ptr; } return; } /** * \brief 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 add_undo_row_format() * * \param[in] row * \param[in] type * \param[in] format * * \return none */ void add_undo_row_format(int row, int type, unsigned char format) { if (undo_item.rows_format == NULL) { undo_item.rows_format = (struct undo_rows_format *) malloc( sizeof(struct undo_rows_format)); undo_item.rows_format->length = 1; undo_item.rows_format->rows = (struct undo_row_info *) malloc( sizeof(struct undo_row_info)); } else { undo_item.rows_format->length++; undo_item.rows_format->rows = (struct undo_row_info *) realloc(undo_item.rows_format->rows, undo_item.rows_format->length * sizeof(struct undo_row_info)); } undo_item.rows_format->rows[ undo_item.rows_format->length - 1].type = type; undo_item.rows_format->rows[ undo_item.rows_format->length - 1].row = row; undo_item.rows_format->rows[ undo_item.rows_format->length - 1].format = format; return; } /** * \brief save_undo_range_shift() * * \details 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; } /* * \brief undo_hide_show() * 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. * * \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 undo_freeze_unfreeze() * * \details this function is used for undoint and redoing changes * caused by freeze row/col and unfreeze row/col commands * * \param[in] row * \param[in] col * \param[in] type 'f' or 'u' * \param[in] arg * * \return none */ void undo_freeze_unfreeze(int row, int col, char type, int arg) { int i; if (type == 'f') { if (row > -1) { // hide row if (undo_item.row_frozed == NULL) { undo_item.row_frozed = (int *) malloc(sizeof(int) * (arg + 1)); undo_item.row_frozed[0] = 0; } else undo_item.row_frozed = (int *) realloc(undo_item.row_frozed, sizeof(int) * (undo_item.row_frozed[0] + arg + 1)); for (i=0; i < arg; i++) undo_item.row_frozed[undo_item.row_frozed[0] + i + 1] = row + i; undo_item.row_frozed[0] += arg; // keep in first position the number of elements (rows) } else if (col > -1) { // hide col if (undo_item.col_frozed == NULL) { undo_item.col_frozed = (int *) malloc(sizeof(int) * (arg + 1)); undo_item.col_frozed[0] = 0; } else undo_item.col_frozed = (int *) realloc(undo_item.col_frozed, sizeof(int) * (undo_item.col_frozed[0] + arg + 1)); for (i=0; i < arg; i++) undo_item.col_frozed[undo_item.col_frozed[0] + i + 1] = col + i; undo_item.col_frozed[0] += arg; // keep in first position the number of elements (cols) } } else if (type == 'u') { if (row > -1) { // unfreeze row if (undo_item.row_unfrozed == NULL) { undo_item.row_unfrozed = (int *) malloc(sizeof(int) * (arg + 1)); undo_item.row_unfrozed[0] = 0; } else undo_item.row_unfrozed = (int *) realloc(undo_item.row_unfrozed, sizeof(int) * (undo_item.row_unfrozed[0] + arg + 1)); for (i=0; i < arg; i++) undo_item.row_unfrozed[undo_item.row_unfrozed[0] + i + 1] = row + i; undo_item.row_unfrozed[0] += arg; // keep in first position the number of elements (rows) } else if (col > -1) { // unfreeze col if (undo_item.col_unfrozed == NULL) { undo_item.col_unfrozed = (int *) malloc(sizeof(int) * (arg + 1)); undo_item.col_unfrozed[0] = 0; } else undo_item.col_unfrozed = (int *) realloc(undo_item.col_unfrozed, sizeof(int) * (undo_item.col_unfrozed[0] + arg + 1)); for (i=0; i < arg; i++) undo_item.col_unfrozed[undo_item.col_unfrozed[0] + i + 1] = col + i; undo_item.col_unfrozed[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() { struct roman * roman = session->cur_doc; if (undo_list == NULL || undo_list_pos == 0) { sc_error("No UNDO's left."); return; } // move to the according sheet struct sheet * sh = undo_list->sheet; roman->cur_sh = sh; int ori_currow = sh->currow; int ori_curcol = sh->curcol; //int mf = roman->modflg; // save modflag status struct undo * ul = undo_list; // removed added ents struct ent_ptr * i = ul->added; while (i != NULL) { erase_area(i->sheet, i->vp->row, i->vp->col, i->vp->row, i->vp->col, 1, 0); i = i->next; } // Make undo shift, if any if (ul->range_shift != NULL) { int deltarows = ul->range_shift->delta_rows; int deltacols = ul->range_shift->delta_cols; int brrow = ul->range_shift->brrow; int tlrow = ul->range_shift->tlrow; int brcol = ul->range_shift->brcol; int tlcol = ul->range_shift->tlcol; // fix marks for rows if (deltarows > 0) // sj fix_marks(sh, -(brrow - tlrow + 1), 0, tlrow, sh->maxrow, tlcol, brcol); else if (deltarows < 0) // sk fix_marks(sh, (brrow - tlrow + 1), 0, tlrow, sh->maxrow, tlcol, brcol); // handle row_hidden fix_row_hidden(sh, deltarows, tlrow, sh->maxrow); // fix marks for cols if (deltacols > 0) // sl fix_marks(sh, 0, -(brcol - tlcol + 1), tlrow, brrow, tlcol, sh->maxcol); else if (deltacols < 0) // sh fix_marks(sh, 0, (brcol - tlcol + 1), tlrow, brrow, tlcol, sh->maxcol); // handle col_hidden fix_col_hidden(sh, deltacols, tlcol, sh->maxcol); // handle row_frozen fix_row_frozen(sh, deltarows, tlrow, sh->maxrow); // handle col_frozen fix_col_frozen(sh, deltacols, tlcol, sh->maxcol); // shift range now shift_range(sh, - deltarows, - deltacols, tlrow, tlcol, brrow, brcol); // shift col_formats here. if (tlcol >= 0 && tlrow == 0 && brrow == sh->maxrows) { //sh->maxcols -= deltacols; int i; if (deltacols > 0) for (i = brcol + deltacols; i <= sh->maxcols; i++) { sh->fwidth[i - deltacols] = sh->fwidth[i]; sh->precision[i - deltacols] = sh->precision[i]; sh->realfmt[i - deltacols] = sh->realfmt[i]; } else for (i = sh->maxcols; i >= tlcol - deltacols; i--) { sh->fwidth[i] = sh->fwidth[i + deltacols]; sh->precision[i] = sh->precision[i + deltacols]; sh->realfmt[i] = sh->realfmt[i + deltacols]; } } // do the same for rows here. if (tlrow >= 0 && tlcol == 0 && brcol == sh->maxcols) { //sh->maxrows -= deltarows; int i; if (deltarows > 0) for (i = brrow + deltarows; i <= sh->maxrows; i++) sh->row_format[i - deltarows] = sh->row_format[i]; else for (i = sh->maxrow; i >= tlrow - deltarows; i--) sh->row_format[i] = sh->row_format[i + deltarows]; } } // Change cursor position //if (ul->removed != NULL) { // currow = ul->removed->row; // curcol = ul->removed->col; //} // Append 'ent' elements from the removed ones struct ent_ptr * j = ul->removed; while (j != NULL) { struct ent * h; if ((h = *ATBL(j->sheet, j->sheet->tbl, j->vp->row, j->vp->col))) clearent(h); struct ent * e_now = lookat(j->sheet, j->vp->row, j->vp->col); (void) copyent(e_now, j->sheet, j->vp, 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--) { sh->col_hidden[*(pd++)] = FALSE; } } else if (ul->col_showed != NULL) { int * pd = ul->col_showed; int left = *(pd++); while (left--) { sh->col_hidden[*(pd++)] = TRUE; } } else if (ul->row_hidded != NULL) { int * pd = ul->row_hidded; int left = *(pd++); while (left--) { sh->row_hidden[*(pd++)] = FALSE; } } else if (ul->row_showed != NULL) { int * pd = ul->row_showed; int left = *(pd++); while (left--) { sh->row_hidden[*(pd++)] = TRUE; } } // freeze frozen cols and rows // unfreeze unfrozen cols and rows if (ul->col_unfrozed != NULL) { int * pd = ul->col_unfrozed; int left = *(pd++); while (left--) { sh->col_frozen[*(pd++)] = TRUE; } } if (ul->col_frozed != NULL) { int * pd = ul->col_frozed; int left = *(pd++); while (left--) { sh->col_frozen[*(pd++)] = FALSE; } } if (ul->row_unfrozed != NULL) { int * pd = ul->row_unfrozed; int left = *(pd++); while (left--) { sh->row_frozen[*(pd++)] = TRUE; } } if (ul->row_frozed != NULL) { int * pd = ul->row_frozed; int left = *(pd++); while (left--) { sh->row_frozen[*(pd++)] = FALSE; } } // 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') { sh->fwidth[uf->cols[i].col] = uf->cols[i].fwidth; sh->precision[uf->cols[i].col] = uf->cols[i].precision; sh->realfmt[uf->cols[i].col] = uf->cols[i].realfmt; } } } // Restore previous row format if (ul->rows_format != NULL) { struct undo_rows_format * uf = ul->rows_format; int size = uf->length; int i; for (i=0; i < size; i++) { if (uf->rows[i].type == 'R') { sh->row_format[uf->rows[i].row] = uf->rows[i].format; } } } // for every ent in added and removed, we reeval expression to update graph struct ent_ptr * ie = ul->added; while (ie != NULL) { struct ent * p; if ((p = *ATBL(ie->sheet, ie->sheet->tbl, ie->vp->row, ie->vp->col)) && p->expr) EvalJustOneVertex(ie->sheet, p, 1); ie = ie->next; } ie = ul->removed; while (ie != NULL) { struct ent * p; if ((p = *ATBL(ie->sheet, ie->sheet->tbl, ie->vp->row, ie->vp->col)) && p->expr) EvalJustOneVertex(ie->sheet, p, 1); ie = ie->next; } // Restores cursor position sh->currow = ori_currow; sh->curcol = ori_curcol; // decrease modflg //roman->modflg = mf - 1; // restore maxrow, maxcol and modflg status before the action roman->modflg = undo_item.modflg_bef; sh->maxrow = undo_item.maxrow_bef; sh->maxcol = undo_item.maxcol_bef; if (undo_list->p_ant != NULL) undo_list = undo_list->p_ant; 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() { struct roman * roman = session->cur_doc; if ( undo_list == NULL || undo_list_pos == len_undo_list() ) { sc_error("No REDO's left."); return; } if ((undo_list->p_ant != NULL || undo_list_pos != 0) && (undo_list->p_sig != NULL)) undo_list = undo_list->p_sig; struct undo * ul = undo_list; struct sheet * sh = undo_list->sheet; int ori_currow = sh->currow; int ori_curcol = sh->curcol; //int mf = roman->modflg; // save modflag status // move to the according sheet roman->cur_sh = sh; // Remove 'ent' elements struct ent_ptr * i = ul->removed; while (i != NULL) { erase_area(i->sheet, i->vp->row, i->vp->col, i->vp->row, i->vp->col, 1, 0); i = i->next; } // Make undo shift, if any if (ul->range_shift != NULL) { int deltarows = ul->range_shift->delta_rows; int deltacols = ul->range_shift->delta_cols; int brrow = ul->range_shift->brrow; int tlrow = ul->range_shift->tlrow; int brcol = ul->range_shift->brcol; int tlcol = ul->range_shift->tlcol; // fix marks for rows if (deltarows > 0) // sj fix_marks(sh, (brrow - tlrow + 1), 0, tlrow, sh->maxrow, tlcol, brcol); else if (deltarows < 0) // sk fix_marks(sh, -(brrow - tlrow + 1), 0, tlrow, sh->maxrow, tlcol, brcol); // handle row_hidden fix_row_hidden(sh, -deltarows, tlrow, sh->maxrow); // fix marks for cols if (deltacols > 0) // sl fix_marks(sh, 0, (brcol - tlcol + 1), tlrow, brrow, tlcol, sh->maxcol); else if (deltacols < 0) // sh fix_marks(sh, 0, -(brcol - tlcol + 1), tlrow, brrow, tlcol, sh->maxcol); // handle col_hidden fix_col_hidden(sh, -deltacols, tlcol, sh->maxcol); // handle row_frozen fix_row_frozen(sh, -deltarows, tlrow, sh->maxrow); // handle col_frozen fix_col_frozen(sh, -deltacols, tlcol, sh->maxcol); // shift range now shift_range(sh, deltarows, deltacols, tlrow, tlcol, brrow, brcol); // shift col_formats here if (tlcol >= 0 && tlrow == 0 && brrow == sh->maxrow) { //sh->maxcols += deltacols; int i; if (deltacols > 0) for (i = sh->maxcols; i >= tlcol + deltacols; i--) { sh->fwidth[i] = sh->fwidth[i - deltacols]; sh->precision[i] = sh->precision[i - deltacols]; sh->realfmt[i] = sh->realfmt[i - deltacols]; } else for (i = tlcol; i - deltacols <= sh->maxcols; i++) { sh->fwidth[i] = sh->fwidth[i - deltacols]; sh->precision[i] = sh->precision[i - deltacols]; sh->realfmt[i] = sh->realfmt[i - deltacols]; } } // do the same for rows here if (tlrow >= 0 && tlcol == 0 && brcol == sh->maxcols) { //sh->maxrows += deltarows; int i; if (deltarows > 0) for (i = sh->maxrows; i >= tlrow + deltarows; i--) sh->row_format[i] = sh->row_format[i - deltarows]; else for (i = tlrow; i - deltarows <= sh->maxrows; i++) sh->row_format[i] = sh->row_format[i - deltarows]; } } // 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_ptr * j = ul->added; while (j != NULL) { struct ent * h; if ((h = *ATBL(j->sheet, j->sheet->tbl, j->vp->row, j->vp->col))) clearent(h); struct ent * e_now = lookat(j->sheet, j->vp->row, j->vp->col); (void) copyent(e_now, j->sheet, j->vp, 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--) { sh->col_hidden[*(pd++)] = TRUE; } } else if (ul->col_showed != NULL) { int * pd = ul->col_showed; int left = *(pd++); while (left--) { sh->col_hidden[*(pd++)] = FALSE; } } else if (ul->row_hidded != NULL) { int * pd = ul->row_hidded; int left = *(pd++); while (left--) { sh->row_hidden[*(pd++)] = TRUE; } } else if (ul->row_showed != NULL) { int * pd = ul->row_showed; int left = *(pd++); while (left--) { sh->row_hidden[*(pd++)] = FALSE; } } // freeze frozen cols and rows // unfreeze unfrozen cols and rows if (ul->col_unfrozed != NULL) { int * pd = ul->col_unfrozed; int left = *(pd++); while (left--) { sh->col_frozen[*(pd++)] = FALSE; } } if (ul->col_frozed != NULL) { int * pd = ul->col_frozed; int left = *(pd++); while (left--) { sh->col_frozen[*(pd++)] = TRUE; } } if (ul->row_unfrozed != NULL) { int * pd = ul->row_unfrozed; int left = *(pd++); while (left--) { sh->row_frozen[*(pd++)] = FALSE; } } if (ul->row_frozed != NULL) { int * pd = ul->row_frozed; int left = *(pd++); while (left--) { sh->row_frozen[*(pd++)] = TRUE; } } // 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') { sh->fwidth[uf->cols[i].col] = uf->cols[i].fwidth; sh->precision[uf->cols[i].col] = uf->cols[i].precision; sh->realfmt[uf->cols[i].col] = uf->cols[i].realfmt; } } } // Restore new row format if (ul->rows_format != NULL) { struct undo_rows_format * uf = ul->rows_format; int size = uf->length; int i; for (i=0; i < size; i++) { if (uf->rows[i].type == 'A') { sh->row_format[uf->rows[i].row] = uf->rows[i].format; } } } // for every ent in added and removed, we reeval expression to update graph struct ent_ptr * ie = ul->added; while (ie != NULL) { struct ent * p; if ((p = *ATBL(ie->sheet, ie->sheet->tbl, ie->vp->row, ie->vp->col)) && p->expr) EvalJustOneVertex(ie->sheet, p, 1); ie = ie->next; } ie = ul->removed; while (ie != NULL) { struct ent * p; if ((p = *ATBL(ie->sheet, ie->sheet->tbl, ie->vp->row, ie->vp->col)) && p->expr) EvalJustOneVertex(ie->sheet, p, 1); ie = ie->next; } // Restores cursor position sh->currow = ori_currow; sh->curcol = ori_curcol; // increase modflg //roman->modflg = mf + 1; // restore maxrow, maxcol and modflg status before the action roman->modflg = undo_item.modflg_aft; sh->maxrow = undo_item.maxrow_aft; sh->maxcol = undo_item.maxcol_aft; sc_info("Change: %d of %d", ++undo_list_pos, len_undo_list()); return; } #endif