/* undo.c - part of asedit program * * set of procedures to realize Undo and Redo edit operations; * maximum number of changes that may be undone is set by EditSTACK_SIZE * * Last revision - 21.03.1994 * * In the version for asedit 1.2x declaration od undo/redo data types is moved into * asedit.h and the specific data is obtained from the general aseditWindowStruct structure. */ /* * Copyright 1991 - 1994, Andrzej Stochniol, London, UK * * ASEDIT text editor, both binary and source (hereafter, Software) is * copyrighted by Andrzej Stochniol (hereafter, AS) and ownership remains * with AS. * * AS grants you (hereafter, Licensee) a license to use the Software * for academic, research and internal business purposes only, without a * fee. Licensee may distribute the binary and source code (if released) * to third parties provided that the copyright notice and this statement * appears on all copies and that no charge is associated with such copies. * * Licensee may make derivative works. However, if Licensee distributes * any derivative work based on or derived from the Software, then * Licensee will: * (1) notify AS regarding its distribution of the derivative work, and * (2) clearly notify users that such derivative work is a modified version * and not the original ASEDIT distributed by AS. * * Any Licensee wishing to make commercial use of the Software should * contact AS to negotiate an appropriate license for such commercial use. * Commercial use includes: * (1) integration of all or part of the source code into a product for sale * or license by or on behalf of Licensee to third parties, or * (2) distribution of the binary code or source code to third parties that * need it to utilize a commercial product sold or licensed by or on * behalf of Licensee. * * A. STOCHNIOL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS * SOFTWARE FOR ANY PURPOSE. IT IS PROVIDED "AS IS" WITHOUT EXPRESS OR * IMPLIED WARRANTY. IN NO EVENT SHALL A. STOCHNIOL BE LIABLE FOR ANY * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * By using or copying this Software, Licensee agrees to abide by the * copyright law and all other applicable laws, and the terms of this * license. * AS shall have the right to terminate this license immediately by * written notice upon Licensee's breach of, or non-compliance with, any * of its terms. Licensee may be held legally responsible for any * copyright infringement that is caused or encouraged by Licensee's * failure to abide by the terms of this license. * * * Andrzej Stochniol (A.Stochniol@ic.ac.uk) * 30 Hatch Road * London SW16 4PN * UK */ #include #include #include #include "asedit.h" /* for the definition of aseditWindowStruct */ /* external declarations */ extern Display *display; /* Display */ extern XmStringCharSet charset; /* declarations specific for undo were moved into asedit.h since asedit 1.2x */ #ifdef _NO_PROTO void flush_edit_stack( ); void pop_edit_stack (); void push_edit_stack(); int get_id_from_asdat(); aseditWindowStruct *get_win_from_asdat(); #else void flush_edit_stack(EditStack *s); void pop_edit_stack (EditStack *s, EditActionPtr *el); void push_edit_stack(EditStack *s, EditActionPtr el); int get_id_from_asdat(XtPointer as_client_data); aseditWindowStruct *get_win_from_asdat(XtPointer as_client_data); #endif #ifdef _NO_PROTO void push_edit_stack(s, el) EditStack *s; EditActionPtr el; #else /* _NO_PROTO */ void push_edit_stack(EditStack *s, EditActionPtr el) #endif { /* push an element on the edit stack (on its top); the stack has predefined maximum length (EditSTACK_SIZE); if the stack is filled up one element from the bottom of the stack is destroyed, address of the bottom is moved up so we get one space on the stack. In fact it is not a classic stack which I implemented here but this "cyclic" version gives an easy way of restoring last EditSTACK_SIZE edit commands; because of the possibility of moving of the stack bottom the address of any element on the stack must be calculated "modulo" EditSTACK_SIZE; */ s->el[s->top] = el; s->top = (s->top + 1) % EditSTACK_SIZE; /* check if the stack is used up completely; if so destroy the oldest element (i.e. from the bottom of the stack) and move the address of the bottom */ if(s->bottom == s->top) { /* if memory was allocated for the text free it */ if(s->el[s->bottom]->del_text) { XtFree(s->el[s->bottom]->del_text); s->el[s->bottom]->del_text = NULL; } if(s->el[s->bottom]->ins_text) { XtFree(s->el[s->bottom]->ins_text); s->el[s->bottom]->ins_text = NULL; } s->bottom = (s->bottom + 1) % EditSTACK_SIZE; } } /* push_edit_stack */ #ifdef _NO_PROTO void pop_edit_stack(s, el) EditStack *s; EditActionPtr *el; #else /* _NO_PROTO */ void pop_edit_stack(EditStack *s, EditActionPtr *el) #endif { /* pop the element from the top of the edit stack s */ if(s->bottom != s->top) { s->top --; if(s->top < 0) s->top += EditSTACK_SIZE; /* "cyclic" stack */ *el = s->el[s->top]; /* moved here to avoid checking twice for sign of s->topp -- */ } else *el = NULL; /* empty stack */ } /* pop_edit_stack */ #ifdef _NO_PROTO void flush_edit_stack(s) EditStack *s; #else /* _NO_PROTO */ void flush_edit_stack(EditStack *s) #endif { /* flush the edit stack pointed by s */ int i, j, n; n = s->top - s->bottom; if(n < 0) n += EditSTACK_SIZE; /* "cyclic" stack */ for(i=0; itop -i -1; if(j < 0) j += EditSTACK_SIZE; /* if memory was allocated for the text free it */ if(s->el[j]->del_text) { XtFree(s->el[j]->del_text); s->el[j]->del_text = NULL; } if(s->el[j]->ins_text) { XtFree(s->el[j]->ins_text); s->el[j]->ins_text = NULL; } } s->top = s->bottom; } /* flush_edit_stack */ #ifdef _NO_PROTO void reset_undo_redo(win) aseditWindowStruct *win; #else /* _NO_PROTO */ void reset_undo_redo( aseditWindowStruct *win) #endif { /* resets undo/redo by flushing redo and undo stacks and set the redo/undo flags */ flush_edit_stack(&(win->redo_stack)); flush_edit_stack(&(win->undo_stack)); win->can_undo = win->can_redo = False; } /* reset_undo_redo */ #ifdef _NO_PROTO void SaveActionForUndo(w, client_data, call_data) Widget w; XtPointer client_data; XtPointer call_data; #else /* _NO_PROTO */ void SaveActionForUndo(Widget w, XtPointer client_data, XtPointer call_data) #endif { /* save the current XmTextVerifyCallbackStruct for later use; additionally for delete operation save the deleted text in the undo_text->text->ptr; for an easy access in undo/redo I terminate undo_text->text->ptr with a NULL (so it can be used straight away in the, for example, XmTextReplace). */ XmTextVerifyCallbackStruct *txt_data = (XmTextVerifyCallbackStruct *)call_data; /**XmTextVerifyPtr txt_data = (XmTextVerifyPtr) call_data; ***/ EditActionPtr previous_undo; String text_buffer; String text_changed; Arg al[2]; int reason; /* reason for the callback */ int len; EditActionPtr undo_el; /* txt_data for undo operation */ /* to speed the process of editing we preallocate a buffer for edit_history, with exactly the same length as EditSTACK_SIZE; this get rid of permanent allocation and deallocation of memory for every edit action; the memory must be only allocated/deallocated to store text added (not for all variables describing the edit operation) */ /* Since asedit 1.2 the data below is obtained from aseditWindowStruct ... (old) static EditAction edit_history[EditSTACK_SIZE]; (old) static Boolean edit_history_initialized = False; */ Boolean undo_sequence = False, redo_sequence = False; /* default values */ int i; int id = get_id_from_asdat(client_data); aseditWindowStruct *win = get_win_from_asdat(client_data); /* when you start editing allocate memory for text blocks in edit_history */ if(!win->edit_history_initialized) { win->edit_history_initialized = True; for(i=0; i < EditSTACK_SIZE; i++) { win->edit_history[i].del_text = NULL; win->edit_history[i].ins_text = NULL; win->edit_history[i].undo_sequence = False; win->edit_history[i].redo_sequence = False; ; /* was needed in the version with text blocks */ /****OLD edit_history[i].text = (XmTextBlock) XtMalloc(sizeof(XmTextBlockRec)); *****/ } } /* first check if the previous undo_txt was connected with the mouse operation; if so and the current operation is move do not save the current move operation; WE ONLY save first new move operation !!! */ pop_edit_stack(&(win->undo_stack), &previous_undo); if(previous_undo == NULL && txt_data->reason == XmCR_MOVING_INSERT_CURSOR ) return; /* do not store initial movements */ reason = txt_data->reason; if(previous_undo != NULL ) /* the stack is not empty */ { /* set the previous redo_sequence to False (it might be set later on ) */ previous_undo->redo_sequence = False; if(previous_undo->reason == XmCR_MOVING_INSERT_CURSOR && txt_data->reason == XmCR_MOVING_INSERT_CURSOR ) { push_edit_stack(&(win->undo_stack), previous_undo); /* push it back ... */ return; /* RETURN - do not store successive moves */ } else { /* take care of both undo_sequence & redo_sequence */ /* the following is only used for Motif 1.2x !!! */ if(XmVersion >= 1002 && previous_undo->reason == XmCR_MODIFYING_TEXT_VALUE) { if( reason == XmCR_MODIFYING_TEXT_VALUE /* Change All case, sequence of insert files or insert file & change(s) !!! ... */ || (reason == XmCR_MOVING_INSERT_CURSOR && previous_undo->currInsert == txt_data->currInsert)) /* the second case happens for Motif 1.2 even in a standard typing mode (in Motif 1.2 the old modify callback is practically replaced with a sequence of modify and moving calbacks) */ { previous_undo->redo_sequence = True; redo_sequence = undo_sequence = True; } /* the following is a hack to get rid off joinig togeteher few insert file operations or insert operation and following changes */ if(reason == XmCR_MODIFYING_TEXT_VALUE && previous_undo->currInsert == previous_undo->newInsert && previous_undo->startPos == previous_undo->endPos) { previous_undo->redo_sequence = False; redo_sequence = undo_sequence = False; } } push_edit_stack(&(win->undo_stack), previous_undo); /* push it back ... */ } } /* the undo_el is a pointer to an element in edit_history, the element in edit_history has the same index as the top element on the undo_stack (which has a "cyclic" nature) */ undo_el = &(win->edit_history[win->undo_stack.top]); /* if there was anything on the redo stack flush it (we are just about to put a new entry on the undo stack); make the redo button insensitive */ flush_edit_stack(&(win->redo_stack)); win->can_redo = False; XtSetSensitive(win->menu_redo_button, False); switch(reason) { case XmCR_MODIFYING_TEXT_VALUE: /* for a modifying of the edit text allocate memory and the text; there may be two cases: text addition or deletion; */ len = txt_data->text->length; if(len > 0) { /* text adding */ undo_el->ins_text = (String) XtMalloc((len+1) * sizeof(char)); /* + NULL */ /**AIX 3.2.5 - NO: strncpy(undo_el->ins_text, txt_data->text->ptr, len); */ /* can't use strcpy because ptr field is not NULL terminated */ /* NOTE for AIX 3.2.5: when we use the above strncpy under AIX 3.2.5 *nothing* is usually copied !!!! I don't know why, maybe because the field is not a real String because it's not NULL terminated; because of the above we use the following workaround: */ { /* AIX 3.2.5 hack (instead of using strncpy) */ int k; for(k=0; kins_text[k] = txt_data->text->ptr[k]; } /* terminate the string with NULL (for easy use in undo/redo) */ #if defined(vax11c) || defined(__DECC) undo_el->ins_text[len] = '\0'; #else undo_el->ins_text[len] = NULL; #endif /* vax11c */ } /* now check if any data was deleted or not (if so store it) */ len = (int) (txt_data->endPos - txt_data->startPos); /* amount of text deleted */ if(len > 0) { /* text deleting ...(or just replacing with pending delete) - store it but do not change text->length. it will be used to recognise both those cases */ undo_el->del_text = (String) XtMalloc((len+1) * sizeof(char)); /* get the address of the internally stored text */ XtSetArg(al[0], XmNvalue, &text_buffer); XtGetValues(w, al, 1); strncpy(undo_el->del_text, text_buffer+txt_data->startPos,len); XtFree(text_buffer); #if defined(vax11c) || defined(__DECC) undo_el->del_text[len] = '\0'; #else undo_el->del_text[len] = NULL; #endif /* vax11c */ } break; case XmCR_MOVING_INSERT_CURSOR: break; /* no text to store ... */ } /* set the rest of undo_el .... */ undo_el->reason = txt_data->reason; undo_el->currInsert = txt_data->currInsert; undo_el->newInsert = txt_data->newInsert; undo_el->startPos = txt_data->startPos; undo_el->endPos = txt_data->endPos; undo_el->undo_sequence = undo_sequence; undo_el->redo_sequence = redo_sequence; /* push the undo data on the undo stack ... */ push_edit_stack(&(win->undo_stack), undo_el); /* set sensitivity to the undo button */ XtSetSensitive(win->menu_undo_button, True); win->can_undo = True; } /* SaveActionForUndo */ /***************************** UndoRedo ************************************* ** ** Process undo or redo actions for edit_text widget. ** The kind of the action is set via action_type. ** !!!!! The structure of the procedure is nearly the same as TextCB ** so if you make changes there or here remeber to make the changes in ** the other procedure. For full comments see TextCB !!! */ #ifdef _NO_PROTO void UndoRedo (win, action_type) aseditWindowStruct *win; int action_type; #else /* _NO_PROTO */ void UndoRedo (aseditWindowStruct *win, int action_type) #endif { EditActionPtr txt_data=NULL; int reason; /* reason for the callback in TextCB */ XmTextPosition insert_len; /* length of text to be inserted */ XmTextPosition start, end; long ins_len, del_len; Boolean last_element = False; /* to show that last element from the stack has been just poped from the stack */ if(action_type == MENU_UNDO) { pop_edit_stack(&(win->undo_stack), &txt_data); if(win->undo_stack.bottom == win->undo_stack.top) /* last element was popped... */ { last_element = True; win->can_undo = False; XtSetSensitive(win->menu_undo_button, False); XFlush(display); /* sensitivity must be set before next keys are processed, otherwise with a busy system we could "skip over" the bottom of the stack and all track would be lost !!! */ } } else { pop_edit_stack(&(win->redo_stack), &txt_data); if(win->redo_stack.bottom == win->redo_stack.top) /* last element was popped... */ { last_element = True; win->can_redo = False; XtSetSensitive(win->menu_redo_button, False); XFlush(display); /* as above; be sure that the item is insensitive */ } } if(txt_data == NULL) return; /* should not happen, we are doing this just in case. If it did happen it would mean that the undo/redo was called when the appropriate stack was empty, i.e. the sensitivity of the appropriate button was set improperly (but now it will set correctly) in 1.27 this might happen as well when this procedure is called via actions !! */ /* push the obtained element onto the OPPOSITE stack & set sensitivity */ if(action_type == MENU_UNDO) { push_edit_stack(&(win->redo_stack), txt_data); win->can_redo = True; XtSetSensitive(win->menu_redo_button, True); } else { push_edit_stack(&(win->undo_stack), txt_data); win->can_undo = True; XtSetSensitive(win->menu_undo_button, True); } /* set the logical value do_undo_redo_action to True */ win->do_undo_redo_action = True; /* finally do the appropriate undo/redo operation .... */ reason = txt_data->reason; switch(reason) { case XmCR_MODIFYING_TEXT_VALUE: /* increase (redo) or decrease (undo) the changes counter ... */ if(action_type == MENU_UNDO) { (win->changes_counter) --; if(win->changes_counter == 0L ) { write_ls(win->changes_status, charset, "%s", " "); set_titles_mwindow_icon(win, win->filename, NULL); } if(win->changes_counter == -1L ) { write_ls(win->changes_status, charset, "%s", "*"); set_titles_mwindow_icon(win, win->filename, (char *)lstr.changeHint); } } else { (win->changes_counter) ++; /* redo */ if(win->changes_counter == 1L ) { write_ls(win->changes_status, charset, "%s", "*"); set_titles_mwindow_icon(win, win->filename, (char *)lstr.changeHint); } } /* for the undo operation the action must be reversed !!! (what was stored on the undo stack is a plain copy of the operation performed, with only one addition for the delete operation, when the text was stored but the length field was not changed) */ /* for SG there is a problem with a strlen function, if the argument is NULL we get the coredump !!! */ if(txt_data->ins_text) ins_len = strlen(txt_data->ins_text); else ins_len = 0; if(txt_data->del_text) del_len = strlen(txt_data->del_text); else del_len = 0; if(action_type == MENU_UNDO) { start = txt_data->startPos; end = txt_data->startPos + ins_len; XmTextReplace(win->edit_text, start, end, txt_data->del_text); } else /* redo */ { start = txt_data->startPos; end = txt_data->endPos; XmTextReplace(win->edit_text, start, end, txt_data->ins_text); } /* for highlighting we can use: XmTextSetHighlight(win->edit_text, left, right, XmHIGHLIGHT_SELECTED); XmHIGHLIGHT_NORMAL - not highlighted XmHIGHLIGHT_SECONDARY_SELECTED - underlined ****/ /******* TEMP DEBUG fprintf(stderr, "changes_counter = %ld\n", win->changes_counter); ****/ break; case XmCR_MOVING_INSERT_CURSOR: if(action_type == MENU_UNDO) XmTextSetInsertionPosition(win->edit_text, txt_data->currInsert); else XmTextSetInsertionPosition(win->edit_text, txt_data->newInsert); /* redo */ break; default: /* an unknown client_data was received and there is no setup to handle this - do nothing; end procedure nicely */ fprintf(stderr, "Warning: an unknown client_data in undo_redo callback\n"); fprintf(stderr,"Detected reason = %d \n", reason); break; } if(!last_element) { /* make a recursive call for a sequence of undo/redo */ if(action_type == MENU_UNDO && txt_data->undo_sequence || action_type == MENU_REDO && txt_data->redo_sequence ) UndoRedo(win, action_type); } } /* UndoRedo */