cdesktopenv/cde/programs/dtappbuilder/src/ab/abobj_edit.c

3875 lines
91 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these librararies and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/*
* $XConsortium: abobj_edit.c /main/3 1995/11/06 17:15:24 rswiston $
*
* @(#)abobj_edit.c 1.6 15 Feb 1994
*
* RESTRICTED CONFIDENTIAL INFORMATION:
*
* The information in this document is subject to special
* restrictions in a confidential disclosure agreement between
* HP, IBM, Sun, USL, SCO and Univel. Do not distribute this
* document outside HP, IBM, Sun, USL, SCO, or Univel without
* Sun's specific written approval. This document and all copies
* and derivative works thereof must be returned or destroyed at
* Sun's request.
*
* Copyright 1993 Sun Microsystems, Inc. All rights reserved.
*
*/
/*
* File: abobj_edit.c - Editing functions for the app builder i.e.
* cut, copy, paste, undo
*
* This file also contains the functions to manipulate the clipboard.
*/
#include <stdio.h>
#include <sys/param.h>
#include <Xm/Xm.h>
#include <ab_private/trav.h>
#include <ab_private/ui_util.h>
#include <ab_private/brws.h>
#include <ab_private/pal.h>
#include <ab_private/proj.h>
#include <ab_private/objxm.h>
#include <ab_private/obj_notify.h>
#include <ab_private/abobj_edit.h>
#include <ab_private/ab.h>
#include "abobjP.h"
#include "palette_ui.h"
/*
* Offset to use when pasting an object and there is already
* an object at the same (x, y) position.
*/
#define STACKING_X_OFFSET 10
#define STACKING_Y_OFFSET 10
/*************************************************************************
** **
** Private Function Declarations **
** **
**************************************************************************/
static void init_clipboard(
);
static void verify_clipboard_space(
int count
);
static void resolve_clipboard_connections(
ABObj *obj,
int count,
ABClipboardRec clipboard
);
static void resolve_clipboard_attachments(
ABObj *obj,
int count,
ABClipboardRec clipboard
);
static void resolve_attach_one_obj(
ABObj obj,
ABObj origObj,
ABObj *new_list,
int new_count
);
static void resolve_attach_one_attachment(
AB_COMPASS_POINT dir,
ABObj obj,
ABObj origObj,
ABObj *new_list,
int new_count
);
static void resolve_attach_parent(
ABObj obj,
ABObj new_parent
);
static void resolve_attach_parent_dir(
AB_COMPASS_POINT dir,
ABObj obj,
ABObj new_parent
);
static void resolve_none_attachments(
ABObj obj
);
static void resolve_undo_rec_connections(
ABObj *obj,
int count,
ABUndoRec undo_rec
);
static void resolve_undo_rec_attachments(
ABObj *obj,
int count,
ABUndoRec undo_rec
);
static void init_undo_rec(
ABUndoRec *undo_rec_ptr
);
static void init_undo(
);
static void verify_undo_rec_space(
ABUndoRec *undo_rec_ptr,
int count
);
static void verify_undo_space(
int count
);
static void remove_clipboard_descendants(
ABSelectedRec *sel
);
static BOOL any_object_at_my_position(
ABObj obj,
ABObj parent
);
static void stack_objects(
ABObj newObj,
ABObj parent
);
static int set_undo_rec (
ABUndoRec *undo_rec_ptr,
ABObj *obj,
int count,
ABUndoFunc undo_func,
AB_UNDO_TYPE undo_type
);
static void clear_undo_rec(
ABUndoRec *undo_rec_ptr
);
static void undo_cut(
ABUndoRec undo_rec
);
static void undo_paste(
ABUndoRec undo_rec
);
static int edit_destroyOCB(
ObjEvDestroyInfo info
);
static ABObj get_ctrl_pane_or_group(
ABObj paste_target
);
static DTB_MODAL_ANSWER show_modal_msg_relative_initiator(
ABObj parent_obj,
AB_PASTE_INITIATOR_TYPE initiator,
DtbMessageData mbr,
XmString override_msg,
DtbObjectHelpData override_help,
Widget *modal_dlg_pane_out_ptr
);
/*************************************************************************
** **
** Data **
** **
**************************************************************************/
static ABClipboardRec ABClipboard = NULL;
static ABUndoRec ABUndo = NULL;
static ABUndoRec ABUndo_backup = NULL;
static void (*Undo_func)();
BOOL in_undo = FALSE;
/*************************************************************************
** **
** Function Definitions **
** **
**************************************************************************/
/*
* Called from main()...
*/
void
abobj_edit_init(
)
{
obj_add_destroy_callback(edit_destroyOCB, "ATTCH_ED");
}
static int
edit_destroyOCB(
ObjEvDestroyInfo info
)
{
ABObj obj,
parent_obj,
project;
int i;
obj = info->obj;
/*
* Return if obj is NULL or obj is not salient
*/
if (!obj || !obj_is_salient(obj))
return (0);
project = obj_get_project(obj);
/*
* Return if project is NULL
*/
if (!project)
return (0);
/*
* If the entire project is curently being destroyed,
* skip this
*/
if (obj_has_flag(project, BeingDestroyedFlag))
{
/*
* Do one time only during project destroy
* -> clipboard and undo record cleanup
* so, pick the case when the project ABObj is passed in
*/
if (obj_is_project(obj))
{
/*
* Clear clipboard
*/
abobj_clipboard_clear();
/*
* Clear undo record
*/
clear_undo_rec(&ABUndo);
}
return (0);
}
/*
* Check if destroyed object is in connection list stored in
* clipboard.
*/
if (ABClipboard && ABClipboard->action_count > 0)
{
for (i=0; i < ABClipboard->count; ++i)
{
ABClipbInfo clip_info;
clip_info = &(ABClipboard->info_list[i]);
if (clip_info->action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
for (trav_open(&trav, clip_info->action_list,
AB_TRAV_ACTIONS | AB_TRAV_MOD_SAFE);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj to_obj;
to_obj = action_obj->info.action.to;
/*
* If the object being destroyed is the 'to' object OR
* it is a descendant of the 'to' obj, delete the
* connection object
*/
if ((obj == to_obj) || (obj_is_descendant_of(to_obj, obj)))
{
obj_destroy(action_obj);
ABClipboard->action_count--;
}
}
trav_close(&trav);
}
}
}
if (ABUndo && ABUndo->action_count > 0)
{
for (i=0; i < ABUndo->count; ++i)
{
ABUndoInfo undo_info;
undo_info = &(ABUndo->info_list[i]);
if ((undo_info->type != AB_UNDO_CUT) &&
(undo_info->type != AB_UNDO_DELETE))
continue;
if (undo_info->info.cut.from_action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
for (trav_open(&trav, undo_info->info.cut.from_action_list,
AB_TRAV_ACTIONS | AB_TRAV_MOD_SAFE);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj to_obj;
to_obj = action_obj->info.action.to;
/*
* If the object being destroyed is the 'to' object OR
* it is a descendant of the 'to' obj, delete the
* connection object
*/
if ((obj == to_obj) || (obj_is_descendant_of(to_obj, obj)))
{
obj_destroy(action_obj);
ABClipboard->action_count--;
}
}
trav_close(&trav);
}
if (undo_info->info.cut.to_action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
for (trav_open(&trav, undo_info->info.cut.to_action_list,
AB_TRAV_ACTIONS | AB_TRAV_MOD_SAFE);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj from_obj;
from_obj = action_obj->info.action.from;
/*
* If the object being destroyed is the 'from' object OR
* it is a descendant of the 'from' obj, delete the
* connection object
*/
if ((obj == from_obj) || (obj_is_descendant_of(from_obj, obj)))
{
obj_destroy(action_obj);
ABClipboard->action_count--;
}
}
trav_close(&trav);
}
}
}
return (0);
}
/*
* Initializes the clipboard
*/
static void
init_clipboard(
)
{
if (!ABClipboard)
{
ABClipboard = (ABClipboardRec)malloc(sizeof(AB_CLIPBOARD_REC));
ABClipboard->list = NULL;
ABClipboard->info_list = NULL;
ABClipboard->count = 0;
ABClipboard->action_count = 0;
ABClipboard->size = 0;
}
}
/*
* verify_clipboard_space()
* Makes sure the clipboard has enough nodes for 'count' more
* objects.
*/
static void
verify_clipboard_space(
int count
)
{
int num_nodes;
init_clipboard();
/*
* Return immediately if have enuff space
*/
if (ABClipboard->size - ABClipboard->count >= count)
return;
/*
* Calculate min # of blocks/nodes for new count
*/
num_nodes = ABOBJ_CLIPBOARD_BLOCK_SIZE *
((count/ABOBJ_CLIPBOARD_BLOCK_SIZE) + 1);
/*
* Realloc space for new count
*/
ABClipboard->list = (ABObj *)realloc((void *)ABClipboard->list,
(num_nodes * sizeof(ABObj)));
ABClipboard->info_list = (ABClipbInfo)realloc(ABClipboard->info_list,
(num_nodes * sizeof(AB_CLIPB_INFO)));
/*
* If realloc went OK
*/
if (ABClipboard->list)
{
int remainder = num_nodes - ABClipboard->count;
/*
* Set 'new' blocks to zero. Leave existing
* blocks untouched.
*/
memset((void *)&ABClipboard->list[ABClipboard->count],
0,
(remainder * sizeof(ABObj)));
memset((void *)&ABClipboard->info_list[ABClipboard->count],
0,
(remainder * sizeof(AB_CLIPB_INFO)));
/*
* Set new size to new node count
*/
ABClipboard->size = num_nodes;
}
}
/*
* resolve_none_attachments()
* If 'obj' has opposite attachments of type AB_ATTACH_NONE, then
* AB_ATTACH_POINT attachments are created on one of the sides.
*/
static void
resolve_none_attachments(
ABObj obj
)
{
AB_ATTACH_TYPE north_type,
south_type,
east_type,
west_type;
Position x = 0,
y = 0;
if (!obj)
return;
/*
* If no attachments exist, create them
*/
if (obj->attachments == NULL)
obj_init_attachments(obj);
/*
* If this object has a widget associated with it, get
* (x, y) position
*/
if (objxm_get_widget(obj))
{
XtVaGetValues(objxm_get_widget(obj),
XmNx, &x,
XmNy, &y,
NULL);
}
/*
* Get all attachment types
*/
north_type = obj_get_attach_type(obj, AB_CP_NORTH);
south_type = obj_get_attach_type(obj, AB_CP_SOUTH);
east_type = obj_get_attach_type(obj, AB_CP_EAST);
west_type = obj_get_attach_type(obj, AB_CP_WEST);
/*
* If north/south attachments == NONE, create north point attachment
*/
if ((north_type == AB_ATTACH_NONE) && (south_type == AB_ATTACH_NONE))
{
if (objxm_get_widget(obj))
obj->y = (int)y;
else
{
if (obj->y < 0)
obj->y = 0;
}
obj_set_attachment(obj, AB_CP_NORTH, AB_ATTACH_POINT, NULL, obj->y);
}
/*
* If east/west attachments == NONE, create west point attachment
*/
if ((east_type == AB_ATTACH_NONE) && (west_type == AB_ATTACH_NONE))
{
if (objxm_get_widget(obj))
obj->x = (int)x;
else
{
if (obj->x < 0)
obj->x = 0;
}
obj_set_attachment(obj, AB_CP_WEST, AB_ATTACH_POINT, NULL, obj->x);
}
}
/*
* resolve_clipboard_connections()
* This function resolves the connections issues to do with cut/copy
* of objects.
*
* Currently, it makes sure the target ('to') object of the connections
* on the clipboard are correct. If the target object was also
* copied onto the clipboard, then the the connection's target field
* is updated to be the clipboard object.
*
* It is important that this function is called during abobj_clipboard_add()
* since at this point, the copied objects are not deleted yet. This is
* because the 'live' pointers ( clipboard->list[j] ) on the clipboard are
* manipulated in this function.
*/
static void
resolve_clipboard_connections(
ABObj *obj,
int count,
ABClipboardRec clipboard
)
{
AB_TRAVERSAL trav;
ABClipbInfo info;
ABObj newObj,
origObj,
action_obj;
int i,
j;
if (!obj || (count <= 0) || !clipboard)
return;
for (i=0; i<clipboard->count; ++i)
{
/*
* Get ONE clipboard record
*/
info = &clipboard->info_list[i];
if (!info->action_list)
continue;
newObj = info->dup_obj;
origObj = clipboard->list[i];
/*
* For all the connections that were copied to the clipboard
* for this object ('newObj')...
*/
for (trav_open(&trav, info->action_list, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABClipbInfo to_info;
BOOL to_obj_copied = FALSE;
ABObj orig_to_obj,
new_to_obj;
int save_index;
/*
* Check if the target ('to') object was copied into the
* clipboard
*/
/*
* Skip connections if the action type is not 'predefined' or
* if the source/target are the same. These connections should
* have the target field set up properly when they were first
* dup'd in abobj_clipboard_add()
*/
if ((obj_get_func_type(action_obj) != AB_FUNC_BUILTIN) ||
(action_obj->info.action.to == action_obj->info.action.from))
continue;
orig_to_obj = action_obj->info.action.to;
/*
* Scan clipboard and check if 'orig_to_obj' is there.
* (or if it is a descendant of anything there)
*/
for (j = 0; j < clipboard->count; ++j)
{
if ((orig_to_obj == clipboard->list[j]) ||
(obj_is_descendant_of(orig_to_obj, clipboard->list[j])))
{
to_obj_copied = TRUE;
save_index = j;
break;
}
}
if (!to_obj_copied)
continue;
/*
* Target object was copied onto clipboard
*/
to_info = &clipboard->info_list[save_index];
/*
* Search for the dup'd object. It may not be the copied object,
* itself, but a descendant of it.
*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*
* This also depends on the fact that the 'info_list', and 'list' fields
* on the clipboard have identical indices.
*/
new_to_obj = obj_find_by_name_and_type(to_info->dup_obj,
obj_get_name(orig_to_obj),
obj_get_type(orig_to_obj));
action_obj->info.action.to = new_to_obj;
}
trav_close(&trav);
}
}
/*
* resolve_clipboard_attachments()
* This function resolves the attachments issues to do with cut/copy
* of objects.
*
* Currently, it makes sure the attached object of all the attachments
* on the clipboard are correct. If the attached object was also
* copied onto the clipboard, then the attachment is updated to
* point to the clipboard object. Otherwise, the attachment is changed
* to AB_ATTACH_NONE.
*
* It is important that this function is called during abobj_clipboard_add()
* since at this point, the copied objects are not deleted yet. This is
* because the 'live' pointers ( clipboard->list[j] ) on the clipboard are
* manipulated in this function.
*/
static void
resolve_clipboard_attachments(
ABObj *obj,
int count,
ABClipboardRec clipboard
)
{
AB_TRAVERSAL trav;
ABClipbInfo info;
ABObj *new_list = NULL;
ABObj newObj,
origObj,
childObj,
action_obj;
int i,
j;
if (!obj || (count <= 0) || !clipboard)
return;
/*
* 'new_list' is a list of all the new (i.e. dup'd)
* objects. It is used to resolve object type attachments
*/
if (count > 0)
new_list = (ABObj *)util_malloc(count * sizeof(ABObj));
/*
* Populate the new list of objects
*/
for (i=0; i<clipboard->count; ++i)
{
/*
* Get ONE clipboard record
*/
info = &clipboard->info_list[i];
/*
* Get handle to dup'd object and put in on the new list
*/
newObj = info->dup_obj;
new_list[i] = newObj;
}
/*
* Resolve all attachments of every object on the clipboard. Do the
* same for the descendants of these objects.
*/
for (i=0; i<clipboard->count; ++i)
{
/*
* Get ONE clipboard record
*/
info = &clipboard->info_list[i];
/*
* newObj -> dup'd object
* origObj -> the object in the UI that was cut/copied.
* This ptr is live.
*/
newObj = info->dup_obj;
origObj = clipboard->list[i];
/*
* Resolve attachment for clipboard 'top level' object.
*/
resolve_attach_one_obj(newObj, origObj, new_list, clipboard->count);
/*
* Do the same for all salient descendants
*/
for (trav_open(&trav, newObj, AB_TRAV_SALIENT);
(childObj = trav_next(&trav)) != NULL; )
{
ABObj origChildObj = NULL;
/*
* Skip the top level object - taken care of above
*/
if (childObj == newObj)
continue;
/*
* Get the handle to the child object that
* corresponds to the new dup'd one
*/
origChildObj = obj_find_by_name_and_type(origObj,
obj_get_name(childObj),
obj_get_type(childObj));
/*
* Resolve attachment for descendant object.
*/
resolve_attach_one_obj(childObj, origChildObj, new_list, clipboard->count);
}
trav_close(&trav);
}
/*
* Free new list if it was allocated
*/
if (new_list)
util_free(new_list);
}
/*
* resolve_attach_one_obj()
* This function resolves the attachments of the object 'obj'
*
* obj - obj whose attachments need resolving
* origObj - the 'original' object which 'obj' is
* a copy of. This list may be 'live' i.e.
* point to actual objects in the UI. In
* cut operations, these objects will be
* deleted after being copied onto the
* clipboard.
* new_list - the entire list of objects that was
* copied in this copy/cut operation.
* This is a list of copied/static objects.
* new_count - number of objects in 'new_list'.
*/
static void
resolve_attach_one_obj(
ABObj obj,
ABObj origObj,
ABObj *new_list,
int new_count
)
{
if (!obj || !origObj)
return;
resolve_attach_one_attachment(AB_CP_NORTH, obj, origObj, new_list, new_count);
resolve_attach_one_attachment(AB_CP_SOUTH, obj, origObj, new_list, new_count);
resolve_attach_one_attachment(AB_CP_EAST, obj, origObj, new_list, new_count);
resolve_attach_one_attachment(AB_CP_WEST, obj, origObj, new_list, new_count);
}
/*
* resolve_attach_one_attachment()
* This function resolves the attachments of the object 'obj' in the
* 'dir' direction.
*
* Currently, it makes sure the attached object of all the attachments
* on the clipboard are correct. If the attached object was also
* copied onto the clipboard, then the attachment is updated to
* point to the clipboard object. Otherwise, the attachment is changed
* to AB_ATTACH_NONE. For attach-to-parent type attachments, top level
* clipboard objects will have the attached object field set to NULL
* since the new parent will not be known until paste time.
*
* dir - direction of attachment to be resolved
* obj - obj whose attachments need resolving
* origObj - the 'original' object which 'obj' is
* a copy of. This list may be 'live' i.e.
* point to actual objects in the UI. In
* cut operations, these objects will be
* deleted after being copied onto the
* clipboard. On the other hand this can point
* to objects on the clipboard. During a paste
* operation, objects are copied from the
* clipboard. During a copy operation, objects
* are copied from the UI (to the clipboard).
* new_list - the entire list of objects that was
* copied in this copy/cut operation.
* This is a list of copied/static objects.
* new_count - number of objects in 'new_list'.
*/
static void
resolve_attach_one_attachment(
AB_COMPASS_POINT dir,
ABObj obj,
ABObj origObj,
ABObj *new_list,
int new_count
)
{
ABAttachment *one_attachment = NULL;
AB_ATTACH_TYPE attachType;
ABObj attachObj = NULL,
parentObj = NULL,
origAttachObj = NULL,
origParentObj = NULL;
int offset = 0;
if (!obj || !origObj || !new_list)
return;
/*
* Get attachment in the specified direction
*/
one_attachment = obj_get_attachment(obj, dir);
if (!one_attachment)
return;
/*
* Get the attachment type
*/
attachType = obj_get_attach_type(obj, dir);
switch (attachType)
{
case AB_ATTACH_POINT:
case AB_ATTACH_GRIDLINE:
case AB_ATTACH_CENTER_GRIDLINE:
case AB_ATTACH_NONE:
/* No resolution needed */
break;
/*
* These are the only 2 attachment types which contain
* object references
*/
case AB_ATTACH_ALIGN_OBJ_EDGE:
case AB_ATTACH_OBJ:
origAttachObj = obj_get_attach_value(origObj, dir);
origParentObj = obj_get_parent(origObj);
offset = obj_get_attach_offset(origObj, dir);
attachObj = obj_get_attach_value(obj, dir);
parentObj = obj_get_parent(obj);
/*
* Check if original object had a parent
*/
if (origParentObj)
{
/*
* Orignal object has parent.
* This can mean:
* obj is a copy of a live object
* obj is a copy of a descendant of a clipboard object
*/
/*
* Check if the attached object is the parent
*/
if (origAttachObj == origParentObj)
{
/*
* Attach to parent
*/
if (parentObj)
{
/*
* The dup'd object does have a parent.
* Use this as the new attached object.
*/
attachObj = parentObj;
}
else
{
/*
* obj does not have a parent.
* This means that it is a top level object on
* the new list/clipboard
* Since the actual parent object will not be known
* until paste time, set this to NULL for now.
*/
attachObj = NULL;
}
}
else
{
/*
* Attach to sibling
*/
if (parentObj)
{
/*
* obj has a parent. This means that it's parent
* (and all it's siblings were copied onto the new list).
* Search for corresponding sibling object starting at
* the parent object.
*/
attachObj = obj_find_by_name_and_type(parentObj,
obj_get_name(origAttachObj),
obj_get_type(origAttachObj));
/*
* Make sure the found object is a sibling of obj
* If not, nullify attachObj.
*/
if (attachObj && (obj_get_parent(attachObj) != parentObj))
{
attachObj = NULL;
}
/*
* If attached sibling not found, change attachment
* type to AB_ATTACH_NONE
*/
if (attachObj == NULL)
{
attachType = AB_ATTACH_NONE;
offset = 0;
}
}
else
{
int j;
/*
* obj does not have a parent.
* This means that it is a top level object on
* the new list.
* Check if the sibling object was also copied
* onto the new list as a top level object.
*/
attachObj = NULL;
for (j=0; j < new_count; ++j)
{
if (istr_equal(obj_get_name_istr(origAttachObj),
obj_get_name_istr(new_list[j])) &&
(obj_get_type(origAttachObj) ==
obj_get_type(new_list[j])))
{
attachObj = new_list[j];
}
}
if (attachObj == NULL)
{
attachType = AB_ATTACH_NONE;
offset = 0;
}
}
}
}
else
{
/*
* Orignal object has no parent.
* This can mean:
* obj is a copy of a top level clipboard object
*
* If any of the top level objects had attach-to-parent
* type attachments, the value field would be NULL. This
* will be resolved later, during paste. For now, we will
* only worry about sibling attachments.
*/
if (origAttachObj != NULL)
{
int j;
/*
* Check if the sibling object was also copied
* onto the new list as a top level object.
*/
attachObj = NULL;
for (j=0; j < new_count; ++j)
{
if (istr_equal(obj_get_name_istr(origAttachObj),
obj_get_name_istr(new_list[j])) &&
(obj_get_type(origAttachObj) ==
obj_get_type(new_list[j])))
{
attachObj = new_list[j];
}
}
/*
* If the sibling was not found, change the attachment type
* to AB_ATTACH_NONE.
*/
if (attachObj == NULL)
{
attachType = AB_ATTACH_NONE;
offset = 0;
}
}
}
/*
* Set new values of attachment
*/
(void)obj_set_attachment(obj, dir, attachType, (void *)attachObj, offset);
break;
}
}
/*
* resolve_attach_parent()
* For top level objects on the clipboard, if the attachment is
* of type 'attach to parent', the attachment value field is set to
* NULL since we don't know what the actual parent will be until
* the paste is made. This function resolves all the NULL attachment
* values after the paste is done.
*/
static void
resolve_attach_parent(
ABObj obj,
ABObj new_parent
)
{
if (!obj || !new_parent)
return;
resolve_attach_parent_dir(AB_CP_NORTH, obj, new_parent);
resolve_attach_parent_dir(AB_CP_SOUTH, obj, new_parent);
resolve_attach_parent_dir(AB_CP_EAST, obj, new_parent);
resolve_attach_parent_dir(AB_CP_WEST, obj, new_parent);
}
/*
* resolve_attach_parent_dir()
* Same as resolve_attach_parent() but for a specific dir
*/
static void
resolve_attach_parent_dir(
AB_COMPASS_POINT dir,
ABObj obj,
ABObj new_parent
)
{
ABAttachment *one_attachment;
AB_ATTACH_TYPE attachType;
ABObj attachObj = NULL;
if (!obj || !new_parent)
return;
one_attachment = obj_get_attachment(obj, dir);
attachType = obj_get_attach_type(obj, dir);
attachObj = (void *)obj_get_attach_value(obj, dir);
if ((attachType == AB_ATTACH_OBJ) && (attachObj == NULL))
{
obj_set_attach_value(obj, dir, new_parent);
}
}
/*
* resolve_undo_rec_connections()
* This function resolves the connections issues to do
* with undoing cut/delete
*
* It figures out if any of the source/target of the connections
* have to be reassigned to objects that were also cut/deleted,
* and now live on the undo record.
*/
static void
resolve_undo_rec_connections(
ABObj *obj,
int count,
ABUndoRec undo_rec
)
{
AB_TRAVERSAL trav;
ABUndoInfo undo_info;
ABObj origObj,
from_action_list,
to_action_list,
action_obj;
int i,
j;
if (!obj || (count <= 0) || !undo_rec)
return;
if (undo_rec->action_count <= 0)
return;
for (i=0; i<undo_rec->count; ++i)
{
undo_info = &undo_rec->info_list[i];
from_action_list = undo_info->info.cut.from_action_list;
if (from_action_list)
{
for (trav_open(&trav, from_action_list, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABUndoInfo to_info,
from_info;
BOOL to_obj_copied = FALSE,
from_obj_copied = FALSE;
ABObj orig_to_obj,
new_to_obj,
orig_from_obj,
new_from_obj;
int from_save_index,
to_save_index;
/*
* Check if the 'to' object was copied into the
* clipboard
*/
orig_to_obj = action_obj->info.action.to;
orig_from_obj = action_obj->info.action.from;
/*
* Skip if source == target
*/
if ((obj_get_func_type(action_obj) != AB_FUNC_BUILTIN) ||
(orig_to_obj == orig_from_obj))
continue;
for (j = 0; j < undo_rec->count; ++j)
{
if ((orig_to_obj == undo_rec->list[j]) ||
(obj_is_descendant_of(orig_to_obj, undo_rec->list[j])))
{
to_obj_copied = TRUE;
to_save_index = j;
}
}
if (!to_obj_copied)
continue;
to_info = &undo_rec->info_list[to_save_index];
/*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_to_obj = obj_find_by_name_and_type(to_info->info.cut.dup_obj,
obj_get_name(orig_to_obj),
obj_get_type(orig_to_obj));
action_obj->info.action.to = new_to_obj;
}
trav_close(&trav);
}
to_action_list = undo_info->info.cut.to_action_list;
if (to_action_list)
{
for (trav_open(&trav, to_action_list, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABUndoInfo to_info,
from_info;
BOOL to_obj_copied = FALSE,
from_obj_copied = FALSE;
ABObj orig_to_obj,
new_to_obj,
orig_from_obj,
new_from_obj;
int from_save_index,
to_save_index;
/*
* Check if the 'from' object was copied into the
* clipboard
*/
orig_to_obj = action_obj->info.action.to;
orig_from_obj = action_obj->info.action.from;
if (orig_to_obj == orig_from_obj)
continue;
for (j = 0; j < undo_rec->count; ++j)
{
if ((orig_from_obj == undo_rec->list[j]) ||
(obj_is_descendant_of(orig_from_obj, undo_rec->list[j])))
{
from_obj_copied = TRUE;
from_save_index = j;
}
}
if (!from_obj_copied)
continue;
from_info = &undo_rec->info_list[from_save_index];
/*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_from_obj = obj_find_by_name_and_type(from_info->info.cut.dup_obj,
obj_get_name(orig_from_obj),
obj_get_type(orig_from_obj));
action_obj->info.action.from = new_from_obj;
}
trav_close(&trav);
}
}
}
/*
* resolve_undo_rec_attachments()
* This function resolves the attachments issues to do
* with undoing cut/delete
*
* Currently, it makes sure the attached object of all the attachments
* on the undo record are correct. If the attached object was also
* copied onto the undo record, then the attachment is updated to
* point to the undo record object. Otherwise, the attachment is changed
* to AB_ATTACH_NONE.
*
* It is important that this function is called during
* abobj_set_undo()/set_undo_rec() since at this point, the copied
* objects are not deleted yet. This is because the 'live' pointers
* ( undo_rec->list[j] ) on the undo record are manipulated in this function.
*/
static void
resolve_undo_rec_attachments(
ABObj *obj,
int count,
ABUndoRec undo_rec
)
{
AB_TRAVERSAL trav;
ABClipbInfo info;
ABUndoInfo undo_info;
ABObj *new_list = NULL;
ABObj newObj,
origObj,
childObj,
action_obj;
int i,
j;
if (!obj || (count <= 0) || !undo_rec)
return;
/*
* 'new_list' is a list of all the new (i.e. dup'd)
* objects. It is used to resolve object type attachments
*/
if (count > 0)
new_list = (ABObj *)util_malloc(count * sizeof(ABObj));
/*
* Populate the new list of objects
*/
for (i=0; i<undo_rec->count; ++i)
{
/*
* Get ONE undo info record
*/
undo_info = &(undo_rec->info_list[i]);
if ((undo_info->type != AB_UNDO_CUT) ||
(undo_info->type != AB_UNDO_DELETE))
util_dprintf(1,
"resolve_undo_rec_attachments: Warning - undo type is not cut or delete!\n");
/*
* Get handle to dup'd object and put in on the new list
*/
newObj = undo_info->info.cut.dup_obj;
new_list[i] = newObj;
}
/*
* Resolve all attachments of every object on the undo record. Do the
* same for the descendants of these objects.
*/
for (i=0; i<undo_rec->count; ++i)
{
/*
* Get ONE undo info record
*/
undo_info = &(undo_rec->info_list[i]);
/*
* newObj -> dup'd object
* origObj -> the object in the UI that was cut/copied.
* This ptr is live.
*/
newObj = undo_info->info.cut.dup_obj;
origObj = undo_rec->list[i];
/*
* Resolve attachment for clipboard 'top level' object.
*/
resolve_attach_one_obj(newObj, origObj, new_list, undo_rec->count);
/*
* Do the same for all salient descendants
*/
for (trav_open(&trav, newObj, AB_TRAV_SALIENT);
(childObj = trav_next(&trav)) != NULL; )
{
ABObj origChildObj = NULL;
/*
* Skip the top level object - taken care of above
*/
if (childObj == newObj)
continue;
/*
* Get the handle to the child object that
* corresponds to the new dup'd one
*/
origChildObj = obj_find_by_name_and_type(origObj,
obj_get_name(childObj),
obj_get_type(childObj));
/*
* Resolve attachment for descendant object.
*/
resolve_attach_one_obj(childObj, origChildObj, new_list, undo_rec->count);
}
trav_close(&trav);
}
/*
* Free new list if it was allocated
*/
if (new_list)
util_free(new_list);
}
/*
* Initializes one undo record buffer
*/
static void
init_undo_rec(
ABUndoRec *undo_rec_ptr
)
{
ABUndoRec undo_rec;
if (!undo_rec_ptr)
return;
if (!(*undo_rec_ptr))
{
undo_rec = (ABUndoRec)malloc(sizeof(AB_UNDO_REC));
undo_rec->list = NULL;
undo_rec->info_list = NULL;
undo_rec->undo_func = NULL;
undo_rec->count = 0;
undo_rec->action_count = 0;
undo_rec->size = 0;
*undo_rec_ptr = undo_rec;
}
}
/*
* Initializes undo buffer
*/
static void
init_undo(
)
{
init_undo_rec(&ABUndo);
}
/*
* verify_undo_rec_space()
* Makes sure the undo buffer has enough nodes for 'count' more
* objects.
*/
static void
verify_undo_rec_space(
ABUndoRec *undo_rec_ptr,
int count
)
{
ABUndoRec undo_rec;
int num_nodes;
init_undo_rec(undo_rec_ptr);
undo_rec = *undo_rec_ptr;
if (undo_rec->size - undo_rec->count >= count)
return;
/*
* Calculate min # of blocks/nodes for new count
*/
num_nodes = ABOBJ_UNDO_BLOCK_SIZE * ((count/ABOBJ_UNDO_BLOCK_SIZE) + 1);
/*
* Realloc space for new count
*/
undo_rec->list = (ABObj *)realloc((void *)undo_rec->list,
(num_nodes * sizeof(ABObj)));
undo_rec->info_list = (ABUndoInfo)realloc(undo_rec->info_list,
(num_nodes * sizeof(AB_UNDO_INFO)));
if (undo_rec->list)
{
int remainder = num_nodes - undo_rec->count;
/*
* Set 'new' blocks to zero. Leave existing
* blocks untouched.
*/
memset((void *)&undo_rec->list[undo_rec->count],
0,
(remainder * sizeof(ABObj)));
memset((void *)&undo_rec->info_list[undo_rec->count],
0,
(remainder * sizeof(AB_UNDO_INFO)));
/*
* Set new size to new node count
*/
undo_rec->size = num_nodes;
}
}
static void
verify_undo_space(
int count
)
{
verify_undo_rec_space(&ABUndo, count);
}
/*
* remove_clipboard_descendants()
* When copying/cutting objects onto the clipboard,
* any object that is a descendant of another is removed.
* For example, if a button and it's container are copied,
* the button is removed from the copy list since it will
* be copied anyways since it is in the container's
* subtree.
*/
static void
remove_clipboard_descendants(
ABSelectedRec *sel
)
{
ABObj curObj,
curAncestor,
*buf;
int i,
j,
numNullified = 0;
if (!sel)
return;
/*
* Make copy of select list
*/
buf = (ABObj*)XtMalloc(sel->count * sizeof(ABObj));
memcpy(buf, sel->list, (sel->count * sizeof(ABObj)));
for (i = 0; i < sel->count; ++i)
{
/*
* Current object to test if it needs to be deleted
* (set to NULL) from list
*/
curObj = sel->list[i];
for (j = 0; j < sel->count; ++j)
{
curAncestor = buf[j];
/*
* Skip if already set to NULL, or if this is the
* same object as curObj
*/
if (!curObj || !curAncestor || (curObj == curAncestor))
continue;
/*
* Check if curAncestor is an ancestor of curObj
*/
if (obj_is_descendant_of(curObj, curAncestor))
{
buf[i] = NULL;
/*
* Keep track of number of objects deleted from list
*/
++numNullified;
break;
}
}
}
/*
* Update original selection list
*/
for (i = 0, j = 0; i < sel->count; ++i)
{
if (buf[i])
sel->list[j++] = buf[i];
}
/*
* Update original selection count
*/
sel->count -= numNullified;
free(buf);
}
/*
* any_object_at_my_position()
* Returns TRUE if there are any children of 'parent'
* that lies EXACTLY at the same (x, y) position as 'obj'.
*/
static BOOL
any_object_at_my_position(
ABObj obj,
ABObj parent
)
{
AB_TRAVERSAL trav;
ABObj child;
/*
* Check if parent already has a child exactly at
* the same position as the pasted object
*/
for (trav_open(&trav, parent, AB_TRAV_SALIENT_CHILDREN);
(child = trav_next(&trav)) != NULL; )
{
if ((child->x == obj->x) &&
(child->y == obj->y))
{
return (TRUE);
}
}
return (FALSE);
}
/*
* stack_objects()
* Changes the (x, y) position of 'newobj' if there are
* objects in 'parent' already at the same position.
* The search/test is done, incrementing X and Y by
* STACKING_X_OFFSET, STACKING_Y_OFFSET.
*/
static void
stack_objects(
ABObj newObj,
ABObj parent
)
{
BOOL xy_changed = FALSE;
if (!newObj || !parent)
return;
while (any_object_at_my_position(newObj, parent))
{
/*
* Offset the pasted object
*/
newObj->x += STACKING_X_OFFSET;
newObj->y += STACKING_Y_OFFSET;
xy_changed = TRUE;
}
if (xy_changed)
{
obj_set_attachment(newObj, AB_CP_NORTH,
AB_ATTACH_POINT, NULL, newObj->y);
obj_set_attachment(newObj, AB_CP_WEST,
AB_ATTACH_POINT, NULL, newObj->x);
}
}
/*
* set_undo_rec()
* Sets the passed undo record to contain the list of
* objects and related info with regards to 'undo_type'.
*/
static int
set_undo_rec(
ABUndoRec *undo_rec_ptr,
ABObj *obj,
int count,
ABUndoFunc undo_func,
AB_UNDO_TYPE undo_type
)
{
ABUndoRec undo_rec;
int action_count = 0,
i,
j;
Position x = 0,
y = 0;
Dimension width = 0,
height = 0;
if (!undo_rec_ptr || !obj || (count <= 0))
return (0);
clear_undo_rec(undo_rec_ptr);
verify_undo_rec_space(undo_rec_ptr, count);
undo_rec = *undo_rec_ptr;
for (i=undo_rec->count, j=0; i<undo_rec->count + count; ++i, ++j)
{
ABUndoInfo undo_info;
AB_TRAVERSAL trav;
ABObj project, action_obj;
BOOL match_to, match_from, connect_to_self;
undo_rec->list[i] = obj[j];
undo_info = &(undo_rec->info_list[i]);
undo_info->type = undo_type;
switch (undo_type)
{
case AB_UNDO_CUT: /* undo for cut == undo for delete */
case AB_UNDO_DELETE:
/*
* Dup cut object and store parent
*/
undo_info->info.cut.dup_obj = abobj_dup_tree(obj[j]);
undo_info->info.cut.parent = obj_get_parent(obj[j]);
project = obj_get_project(obj[j]);
if (!project)
break;
/*
* Duplicate all ACTIONs of cut/deleted object
* Search for all ACTIONS that have this object (or any of it's
* descendants) as the source/target.
*/
for (trav_open(&trav, project, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
/*
* Does this action have a source ('from') that
* matches the cut object or any of it's descendants ?
*/
match_from = ((action_obj->info.action.from == obj[j]) ||
(obj_is_descendant_of(action_obj->info.action.from,
obj[j])));
/*
* Does this action have a target ('to') that
* matches the cut object or any of it's descendants ?
* Note: The 'to' field in actions is relevant only for
* predefined action types.
*/
match_to =
(obj_get_func_type(action_obj) == AB_FUNC_BUILTIN) &&
((action_obj->info.action.to == obj[j]) ||
(obj_is_descendant_of(action_obj->info.action.to, obj[j])));
/*
* Is the source in this action also the target ?
*/
connect_to_self =
(obj_get_func_type(action_obj) == AB_FUNC_BUILTIN) &&
(action_obj->info.action.to == action_obj->info.action.from);
/*
* If any match ...
*/
if (match_from || match_to)
{
ABObj new_action, new_from_obj, new_to_obj;
/*
* Duplicate action object
*/
new_action = abobj_dup_tree(action_obj);
/*
* A 'from' match
*/
if (match_from)
{
/*
* Create action list to hold all the actions
*/
if (!undo_info->info.cut.from_action_list)
undo_info->info.cut.from_action_list =
obj_create(AB_TYPE_ACTION_LIST, NULL);
/*
* Since the action's source ('from') is the object
* that is copied onto the undo record, it will
* need to be reassigned to the new (copied) 'from'
* object.
*/
/*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_from_obj =
obj_find_by_name_and_type(undo_info->info.cut.dup_obj,
obj_get_name(action_obj->info.action.from),
obj_get_type(action_obj->info.action.from));
new_action->info.action.from = new_from_obj;
/*
* If connected to itself, reassign the target ('to')
* object as well.
*/
if (connect_to_self)
new_action->info.action.to = new_from_obj;
obj_add_action(undo_info->info.cut.from_action_list,
new_action);
action_count++;
}
/*
* A 'to' match
*/
if (!connect_to_self && match_to)
{
/*
* Create action list to hold all the actions
*/
if (!undo_info->info.cut.to_action_list)
undo_info->info.cut.to_action_list =
obj_create(AB_TYPE_ACTION_LIST, NULL);
/*
* Since the action's target ('to') is the object
* that is copied onto the undo record, it will
* need to be reassigned to the new (copied) 'to'
* object.
*/
new_to_obj =
obj_find_by_name_and_type(undo_info->info.cut.dup_obj,
obj_get_name(action_obj->info.action.to),
obj_get_type(action_obj->info.action.to));
new_action->info.action.to = new_to_obj;
obj_add_action(undo_info->info.cut.to_action_list,
new_action);
action_count++;
}
}
}
trav_close(&trav);
/*
fprintf(stderr,
"abobj_set_undo(%s, AB_UNDO_CUT/DELETE): X=%d, Y=%d, W=%d, H=%d\n",
obj_get_name(obj[j]),
obj_get_x(obj[j]), obj_get_y(obj[j]),
obj_get_width(obj[j]), obj_get_height(obj[j]));
*/
break;
case AB_UNDO_PASTE:
/*
* No extra info need to be stored for paste.
*/
break;
case AB_UNDO_MOVE:
/*
* Get (x, y) from widget.
* If ui handle is bogus, get from ABObj
*/
if (objxm_get_widget(obj[j]))
{
XtVaGetValues(objxm_get_widget(obj[j]),
XmNx, &x,
XmNy, &y,
NULL);
}
else
{
x = (int)obj_get_x(obj[j]);
y = (int)obj_get_y(obj[j]);
}
undo_info->info.move.x = x;
undo_info->info.move.y = y;
/*
fprintf(stderr, "abobj_set_undo(%s): X=%d, Y=%d\n",
obj_get_name(obj[j]), x, y);
*/
break;
case AB_UNDO_RESIZE:
/*
* Get current size from widget.
* If ui handle is bogus, get from ABObj
*/
if (objxm_get_widget(obj[j]))
{
width = abobj_get_actual_width(obj[j]);
height = abobj_get_actual_height(obj[j]);
}
else
{
width = (int)obj_get_width(obj[j]);
height = (int)obj_get_height(obj[j]);
}
undo_info->info.resize.width = width;
undo_info->info.resize.height = height;
/*
fprintf(stderr, "abobj_set_undo(%s): W=%d, H=%d\n",
obj_get_name(obj[j]), width, height);
*/
break;
case AB_UNDO_GROUP:
/*
* No extra info need to be stored for undoing group.
*/
break;
case AB_UNDO_UNGROUP:
{
AB_TRAVERSAL trav;
int member_count, member_index;
ABObj *member_list;
ABObj member;
/*
* Check if obj is a group ??
*/
/*
* Dup group object.
* Also store pointers to all it's members.
*/
undo_info->info.ungroup.dup_old_group = abobj_dup(obj[j]);
member_count = trav_count(obj[j],
AB_TRAV_SALIENT_CHILDREN | AB_TRAV_MOD_SAFE);
member_list = (ABObj *)malloc(member_count * sizeof(ABObj));
for (trav_open(&trav, obj[j], AB_TRAV_SALIENT_CHILDREN |
AB_TRAV_MOD_SAFE), member_index = 0;
(member = trav_next(&trav)) != NULL; ++member_index)
{
member_list[member_index] = member;
}
undo_info->info.ungroup.member_list = member_list;
undo_info->info.ungroup.member_count = member_count;
}
break;
default:
fprintf(stderr, "abobj_set_undo_rec: Bad undo type %d\n",
undo_type);
}
}
undo_rec->undo_func = undo_func;
undo_rec->count += count;
undo_rec->action_count += action_count;
if ((undo_type == AB_UNDO_CUT) || (undo_type == AB_UNDO_DELETE))
{
resolve_undo_rec_connections(obj, count, undo_rec);
resolve_undo_rec_attachments(obj, count, undo_rec);
}
return(0);
}
/*
* clear_undo_rec()
* Clears passed undo record.
* The nodes for the info_list are cleared but not free'd
*/
static void
clear_undo_rec(
ABUndoRec *undo_rec_ptr
)
{
ABUndoRec undo_rec;
int i;
ABObj *list;
if (!undo_rec_ptr)
return;
init_undo_rec(undo_rec_ptr);
undo_rec = *undo_rec_ptr;
if ((undo_rec->size <= 0) || (undo_rec->count <= 0))
return;
list = undo_rec->list;
for (i = 0; i < undo_rec->count; ++i)
{
ABUndoInfo undo_info;
undo_info = &(undo_rec->info_list[i]);
list[i] = NULL;
switch (undo_info->type)
{
case AB_UNDO_CUT: /* undo for cut == undo for delete */
case AB_UNDO_DELETE:
/*
* Destroy cut object and NULLify ptrs
*/
if (undo_info->info.cut.dup_obj)
obj_destroy(undo_info->info.cut.dup_obj);
if (undo_info->info.cut.from_action_list)
obj_destroy(undo_info->info.cut.from_action_list);
if (undo_info->info.cut.to_action_list)
obj_destroy(undo_info->info.cut.to_action_list);
undo_info->info.cut.dup_obj = NULL;
undo_info->info.cut.from_action_list = NULL;
undo_info->info.cut.to_action_list = NULL;
undo_info->info.cut.parent = NULL;
undo_info->info.cut.pane_sibling = NULL;
break;
case AB_UNDO_PASTE:
/*
* No cleanup needed for paste
*/
break;
case AB_UNDO_MOVE:
/*
* Set (x, y) to (0, 0)
*/
undo_info->info.move.x = 0;
undo_info->info.move.y = 0;
break;
case AB_UNDO_RESIZE:
/*
* Set current size to 0 by 0
*/
undo_info->info.resize.width = 0;
undo_info->info.resize.height = 0;
break;
case AB_UNDO_GROUP:
/*
* No cleanup needed for group.
*/
break;
case AB_UNDO_UNGROUP:
{
int member_count, member_index;
ABObj *member_list;
ABObj member;
/*
* Dup group object.
* Also store pointers to all it's members.
*/
if (undo_info->info.ungroup.dup_old_group)
{
obj_destroy(undo_info->info.ungroup.dup_old_group);
undo_info->info.ungroup.dup_old_group = NULL;
}
if (undo_info->info.ungroup.member_list)
{
free(undo_info->info.ungroup.member_list);
undo_info->info.ungroup.member_list = NULL;
}
undo_info->info.ungroup.member_count = 0;
}
break;
default:
/* catch-all to avoid compiler warnings */
break;
}
undo_info->type = AB_UNDO_NO_TYPE;
}
/*
* Set count to 0.
* Set undo func to NULL.
*/
undo_rec->count = 0;
undo_rec->action_count = 0;
undo_rec->undo_func = NULL;
}
/*
* PUBLIC:
* Editing functions: Cut, Copy, Paste, Delete, Undo
*/
int
abobj_cut(
)
{
ABObj project = proj_get_project();
ABObj newObj = NULL;
ABObj *clipb_list = NULL;
ABSelectedRec sel;
int i;
int clipb_count = 0;
int iRet = 0;
if (!project)
iRet = -1;
else
{
abobj_get_selected(project, FALSE, FALSE, &sel);
remove_clipboard_descendants(&sel);
/*
* This depends on the sel.list being ordered in TOP-DOWN
* order!!!
*/
/*
* Copy objects to clipboard
*/
abobj_clipboard_set(sel.list, sel.count);
(void)abobj_set_undo(sel.list, sel.count, undo_cut, AB_UNDO_CUT);
for (i = (sel.count - 1); i >= 0; i--)
{
/* Set the dirty bit on the module */
abobj_set_save_needed(obj_get_module(sel.list[i]), TRUE);
obj_destroy(sel.list[i]);
}
if (sel.count > 0)
XtFree((char *)sel.list);
}
return (iRet);
}
int
abobj_copy(
)
{
ABObj project = proj_get_project(),
newObj;
ABSelectedRec sel;
int i;
int iRet = 0;
if (!project)
iRet = -1;
else
{
abobj_get_selected(project, FALSE, FALSE, &sel);
remove_clipboard_descendants(&sel);
abobj_clipboard_set(sel.list, sel.count);
if (sel.count > 0)
XtFree((char *)sel.list);
}
return (iRet);
}
int
abobj_paste(
AB_PASTE_INITIATOR_TYPE initiator
)
{
ABObj project = proj_get_project(),
newObj,
root,
newroot,
parent;
ABSelectedRec sel;
STRING name = (STRING) NULL;
int i = 0, j = 0;
int iRet = 0;
STRING errmsg = (STRING) NULL;
STRING i18n_msg = (STRING) NULL;
char Buf[MAXPATHLEN] = "";
BOOL Err = False;
BOOL CancelPaste = False;
DTB_MODAL_ANSWER answer = DTB_ANSWER_NONE;
ABObj obj_parent = (ABObj) NULL;
XmString xm_buf = (XmString) NULL;
if (!project)
iRet = -1;
else
{
abobj_get_selected(project, FALSE, FALSE, &sel);
/*
* Don't paste if number of selected objects is not one,
* or if clipboard is empty
*/
if (abobj_clipboard_is_empty() || (sel.count != 1))
iRet = -1;
else
{
ABObj *new_list = NULL,
*new_action_list = NULL,
obj;
int action_index = 0;
/* Need to check if the selected object is a legal
* parent for the objects in the clipboard. If not,
* then "Edit->Paste" should be made insensitive.
*/
root = obj_get_root(sel.list[0]);
for (i=0; i < ABClipboard->count && !Err; ++i)
{
ABClipbInfo info;
info = &(ABClipboard->info_list[i]);
obj = info->dup_obj;
newroot = NULL;
if (obj_is_window(obj) || obj_is_menu(obj) || obj_is_message(obj))
{
if (root)
root = obj_get_module(root);
else
root = proj_get_cur_module();
}
else if (obj_is_menubar(obj))
{
AB_TRAVERSAL trav;
ABObj winobj = NULL;
ABObj nobj = NULL;
winobj = root;
while(!(obj_is_window(winobj) && !obj_is_sub(winobj)))
winobj = obj_get_parent(winobj);
if (!obj_is_base_win(winobj))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 37,
"Menubar can be pasted to a Main Window only.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
else
/* Check to make sure there isn't already a Menubar for
* this window
*/
{
for (trav_open(&trav, winobj, AB_TRAV_UI);
(nobj = trav_next(&trav)) != NULL; )
{
if (obj_is_menubar(nobj))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 27,
"There is already a Menubar for the selected window.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
}
}
}
else if (obj_is_container(obj) && !obj_is_control_panel(obj) &&
!obj_is_group(obj))
{
if (root == NULL || (!(obj_is_container(root) &&
obj_is_sub(root) && obj_is_window(obj_get_root(root))
&& !obj_is_file_chooser(obj_get_root(root)))))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 28,
"Containers must be pasted to\na Main Window or Custom Dialog.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
}
else if (obj_is_group(obj))
{
if (root == NULL || (!(obj_is_control_panel(obj_get_root(root))
|| obj_is_group(obj_get_root(root)))))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 57,
"Groups must be pasted to\na group or control panel.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
}
else if (obj_is_pane(obj))
{
if (root != NULL && (obj_is_pane(root) &&
!obj_is_window(obj_get_root(root))))
{
if (!obj_is_control_panel(obj) && obj_is_control_panel(root))
{
/* Popup Modal Message and wait for answer */
dtb_palette_chld_or_layr_msg_initialize(
&dtb_palette_chld_or_layr_msg);
answer = show_modal_msg_relative_initiator(
root, initiator, &dtb_palette_chld_or_layr_msg,
NULL, NULL, NULL);
switch(answer)
{
case DTB_ANSWER_ACTION1: /* As Child */
break;
case DTB_ANSWER_ACTION2: /* As Layered Pane */
obj_parent = abobj_handle_layered_pane(obj, root);
break;
case DTB_ANSWER_CANCEL:
CancelPaste = TRUE;
break;
}
}
else
{
/* If Pane is being pasted on a Pane which is a NORMAL
* child of a Control Panel, then use the Control
* Panel as the actual parent instead of the pane.
*/
if (obj_is_control_panel(
obj_get_root(
obj_get_parent(
obj_get_root(root))))
)
{
obj_parent = obj_get_parent(obj_get_root(root));
}
else
{
obj_parent = root;
}
/* Popup Modal Message and wait for answer */
dtb_palette_layered_pane_msg_initialize(
&dtb_palette_layered_pane_msg);
answer = show_modal_msg_relative_initiator(
obj_parent, initiator, &dtb_palette_layered_pane_msg,
NULL, NULL, NULL);
switch(answer)
{
case DTB_ANSWER_ACTION2: /* Layered Pane */
obj_parent = abobj_handle_layered_pane(obj, obj_parent);
break;
case DTB_ANSWER_CANCEL: /* Cancel */
CancelPaste = TRUE;
break;
}
}
}
/*
* Pasting a pane into a group
*/
else if (root != NULL && obj_is_group(root))
{
if (obj_is_control_panel(obj))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 59,
"Control Panes must be pasted to a Main Window,\nCustom Dialog, or another pane.");
/* If we have an old buffer lying around, free it */
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
else
{
dtb_palette_child_of_group_msg_initialize(
&dtb_palette_child_of_group_msg);
answer = show_modal_msg_relative_initiator(
root, initiator, &dtb_palette_child_of_group_msg,
NULL, NULL, NULL);
switch(answer)
{
/* OK - Create as a child of group */
case DTB_ANSWER_ACTION1:
break;
case DTB_ANSWER_CANCEL:
CancelPaste = TRUE;
break;
}
}
}
else if (root == NULL || (!obj_is_window(obj_get_root(root)) &&
!(obj_is_container(root) && !obj_is_menubar(root))))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 29,
"Panes must be pasted to\na Main Window, Custom Dialog or Container.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
} /* End if obj_is_pane() */
else if (obj_is_control(obj))
{
newroot = get_ctrl_pane_or_group(root);
if (newroot == NULL ||
(!obj_is_control_panel(obj_get_root(newroot)) &&
!obj_is_group(obj_get_root(newroot))))
{
Err = True;
i18n_msg = catgets(Dtb_project_catd, 100, 30,
"Controls must be pasted to\na Control Panel or Group.");
if (errmsg != (STRING) NULL)
util_free(errmsg);
errmsg = (STRING) util_malloc(strlen(i18n_msg) + 1);
strcpy(errmsg, i18n_msg);
}
}
} /* End for loop */
if (Err)
{
xm_buf = XmStringCreateLocalized(errmsg);
dtb_palette_wrn_msg_initialize(&dtb_palette_wrn_msg);
(void) show_modal_msg_relative_initiator(
root, initiator, &dtb_palette_wrn_msg,
xm_buf, NULL, NULL);
XmStringFree(xm_buf);
return -1;
}
if (!CancelPaste)
{
parent = objxm_comp_get_subobj(newroot ? newroot : root, AB_CFG_PARENT_OBJ);
if (!parent)
parent = newroot ? newroot : root;
/*
* Allocate buffer to store ptrs to new objects pasted for undo
*/
new_list = (ABObj *)malloc(ABClipboard->count * sizeof(ABObj));
if (ABClipboard->action_count)
new_action_list = (ABObj *)malloc(ABClipboard->action_count * sizeof(ABObj));
abobj_deselect_all(project);
/*
* Perform the paste operation for all objects on clipboard
*/
for (i=0; i < ABClipboard->count; ++i)
{
ABClipbInfo info;
/*
* Get a handle to one object on the clipboard
* (includes it's descendants)
*/
info = &(ABClipboard->info_list[i]);
/*
* Duplicate clipboard object
*/
newObj = abobj_dup_tree(info->dup_obj);
/*
* Check if this object has any connections that we
* should also duplicate
*/
if (info->action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
/*
* Traverse the action list which stores all the
* duplicated connections on the clipboard
*/
for (trav_open(&trav, info->action_list, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj new_action,
orig_from_obj,
new_from_obj;
/*
* Duplicate connection on clipboard
*/
new_action = abobj_dup(action_obj);
/*
* The connection that is duplicated has, as the source,
* an object that was on the clipboard. We need to
* change the source to be the duplicated object
* i.e. 'newObj' above. The problem, is that the
* source can either be 'newObj' or any of it's
* descendants.
*/
orig_from_obj = new_action->info.action.from;
/*
* We search for the new source object by name/type.
*
* This depends on the fact that the dup'd tree
* maintains objs with same names
*/
new_from_obj =
obj_find_by_name_and_type(newObj,
obj_get_name(orig_from_obj),
obj_get_type(orig_from_obj));
new_action->info.action.from = new_from_obj;
/*
* Catch the case where the source == target
*/
if ((obj_get_func_type(action_obj) == AB_FUNC_BUILTIN) &&
(action_obj->info.action.from ==
action_obj->info.action.to))
new_action->info.action.to = new_from_obj;
new_action_list[action_index++] = new_action;
}
trav_close(&trav);
}
new_list[i] = newObj;
}
/*
* At this point, all the objects that need to be pasted have been
* dupicated, but not parented to the proper parent yet.
* Also, all the connection objects have been created, but similarly,
* not added to the proper module/project.
*
* Before we do the above, we need to resolve the targets of the
* duplicated connections. If the target object was also on the
* clipboard, then the target has to be changed to it's corresponding
* pasted object. This needs to be done, in fact before we parent the
* newly created objects because once that is done, the names of the
* object may change. obj_append_child() makes sure the child names
* are unique.
*
* Also, the attachments of the newly dup'd objects still contain
* references to objects on the clipboard. This needs to be resolved
* as well.
*/
/*
* Resolve the pasted connection targets
*/
for (i=0; i < ABClipboard->action_count; ++i)
{
ABObj action,
orig_to_obj;
action = new_action_list[i];
if (obj_get_func_type(action) != AB_FUNC_BUILTIN)
continue;
orig_to_obj = action->info.action.to;
/*
* Check if the pasted connection target ('to')
* is on the clipboard
*/
for (j=0; j < ABClipboard->count; ++j)
{
ABClipbInfo info;
info = &(ABClipboard->info_list[j]);
if ((orig_to_obj == info->dup_obj) ||
(obj_is_descendant_of(orig_to_obj, info->dup_obj)))
{
ABObj new_to_obj;
/*
* The target is on the clipboard.
* It can either be new_list[j] or any of it's
* descendants.
*/
/*
* Search for the new target object by name/type.
*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_to_obj = obj_find_by_name_and_type(new_list[j],
obj_get_name(orig_to_obj),
obj_get_type(orig_to_obj));
action->info.action.to = new_to_obj;
break;
}
}
}
/*
* Resolve the pasted objects' (and their descendants')
* attachments
*/
for (i=0; i < ABClipboard->count; ++i)
{
ABClipbInfo info;
AB_TRAVERSAL trav;
ABObj origObj = NULL,
childObj = NULL;
info = &(ABClipboard->info_list[i]);
origObj = info->dup_obj;
resolve_attach_one_obj(new_list[i], origObj, new_list, ABClipboard->count);
for (trav_open(&trav, new_list[i], AB_TRAV_SALIENT);
(childObj = trav_next(&trav)) != NULL; )
{
ABObj origChildObj = NULL;
/*
* Skip top level object - taken care of above
*/
if (childObj == new_list[i])
continue;
/*
* Get the handle to the child object that
* corresponds to the new dup'd one
*/
origChildObj = obj_find_by_name_and_type(origObj,
obj_get_name(childObj),
obj_get_type(childObj));
resolve_attach_one_obj(childObj, origChildObj,
new_list, ABClipboard->count);
}
trav_close(&trav);
}
/*
* Parent the new objects to the right parent
*/
for (i=0; i < ABClipboard->count; ++i)
{
ABObj proper_parent = parent;
BOOL manage_last = TRUE;
/*
* If an object already exists at (x, y),
* offset the new object.
*/
stack_objects(new_list[i], parent);
/*
* Add obj to parent, ensure unique names.
*/
/*
* If pasted object is menubar, paste to proper
* config child of main window
*/
if (obj_is_menubar(new_list[i]))
{
ABObj winobj = parent;
/*
* Get window object
*/
while(!(obj_is_window(winobj) && !obj_is_sub(winobj)))
winobj = obj_get_parent(winobj);
if (winobj)
proper_parent =
objxm_comp_get_subobj(winobj, AB_CFG_MENU_PARENT_OBJ);
}
/*
* Resolve attachments where the attached object is the parent
* object. This is for top level clipboard objects. The new
* parent object is only known at paste time. Until now, the value
* field of the attachment was NULL.
*/
resolve_attach_parent(new_list[i], proper_parent);
/*
* Last for attchments - resolve NONE attachments
* If 2 opposite sides have NONE attachments, create
* an AB_ATTACH_POINT attachment on one side.
*/
resolve_none_attachments(new_list[i]);
obj_append_child(proper_parent, new_list[i]);
if (obj_is_layers(new_list[i]) ||
obj_is_group(new_list[i]) ||
obj_is_menu(new_list[i]))
manage_last = FALSE;
abobj_show_tree(new_list[i], manage_last);
/*
* Set the dirty bit on the module
*/
abobj_set_save_needed(obj_get_module(new_list[i]), TRUE);
abobj_select(new_list[i]);
}
/*
* Add connections to module/project.
*/
for (i=0; i < ABClipboard->action_count; ++i)
{
BOOL is_cross_module;
ABObj action;
action = new_action_list[i];
is_cross_module = (obj_get_func_type(action) == AB_FUNC_BUILTIN) &&
obj_is_cross_module(action);
if (is_cross_module)
obj_add_action(obj_get_project(action->info.action.from), action);
else
obj_add_action(obj_is_project(action->info.action.from) ?
action->info.action.from :
obj_get_module(action->info.action.from), action);
}
(void)abobj_set_undo(new_list, ABClipboard->count,
undo_paste, AB_UNDO_PASTE);
if (new_list)
free(new_list);
if (new_action_list)
free(new_action_list);
}
if (sel.count > 0)
XtFree((char *)sel.list);
}
}
return (iRet);
}
int
abobj_delete(
)
{
ABObj project = proj_get_project(),
parent = NULL;
ABSelectedRec sel;
int i;
if (!project)
return (0);
abobj_get_selected(project, FALSE, FALSE, &sel);
/* REMIND: aim,11/23/93 - this is too simplistic ---
* doesn't handle UNDO
*/
/* This depends on the sel.list being ordered in TOP-DOWN
* order!!!
*/
abobj_set_save_needed( proj_get_project(), TRUE);
(void)abobj_set_undo(sel.list, sel.count,
undo_cut, AB_UNDO_CUT);
for (i = (sel.count - 1); i >= 0; i--)
{
/* Set the dirty bit on the module */
abobj_set_save_needed(obj_get_module(sel.list[i]), TRUE);
parent = obj_get_parent(sel.list[i]);
if ((parent != NULL) &&
(obj_is_paned_win(parent) || obj_is_group(parent))
&& (obj_get_num_children(parent) == 1) )
{
/* If the last child is being deleted from a
* paned window or a group, then delete the
* paned window or group as well.
*/
obj_destroy(parent);
}
else
{
obj_destroy(sel.list[i]);
}
}
if (sel.count > 0)
XtFree((char *)sel.list);
return (0);
}
int
abobj_undo(
)
{
int iRet = 0;
if (!ABUndo)
return (iRet);
in_undo = TRUE;
if (ABUndo->undo_func)
ABUndo->undo_func(ABUndo);
abobj_cancel_undo();
in_undo = FALSE;
#ifdef AB_UNDO_UNDO
if (ABUndo_backup && (ABUndo_backup->count > 0))
{
ABUndoRec tmp;
tmp = ABUndo;
ABUndo = ABUndo_backup;
ABUndo_backup = tmp;
}
#endif
return (iRet);
}
int
abobj_set_undo(
ABObj *obj,
int count,
ABUndoFunc undo_func,
AB_UNDO_TYPE undo_type
)
{
int i;
Position x = 0,
y = 0;
Dimension width = 0,
height = 0;
if (!obj || (count <= 0))
return (0);
/*
* Ifdef out undo undo until fix memory trash
*/
#ifdef AB_UNDO_UNDO
if (in_undo)
set_undo_rec(&ABUndo_backup, obj, count, undo_func, undo_type);
else
set_undo_rec(&ABUndo, obj, count, undo_func, undo_type);
#else
if (!in_undo)
set_undo_rec(&ABUndo, obj, count, undo_func, undo_type);
#endif
return(0);
}
int
abobj_cancel_undo(
)
{
clear_undo_rec(&ABUndo);
return 0;
}
BOOL
abobj_undo_active(
)
{
BOOL i;
return (ABUndo && (ABUndo->undo_func != NULL));
}
/*
* PUBLIC:
* Functions to manipulate the ABObj clipboard
*/
/*
* abobj_clipboard_is_empty()
* Is clipboard empty ?
*/
BOOL
abobj_clipboard_is_empty(
)
{
return(!ABClipboard || (ABClipboard->count == 0));
}
/*
* abobj_in_clipboard()
* Is object on clipboard
*/
BOOL
abobj_in_clipboard(
ABObj obj
)
{
int i;
if (!obj || !ABClipboard || (ABClipboard->count <= 0))
return (FALSE);
for (i = 0; i < ABClipboard->count; ++i)
{
if (ABClipboard->list[i] == obj)
return (TRUE);
}
return (FALSE);
}
/*
* abobj_clipboard_clear()
* Clears the clipboard
*/
void
abobj_clipboard_clear(
)
{
int i;
init_clipboard();
if ((ABClipboard->size <= 0) || (ABClipboard->count <= 0))
return;
for (i = 0; i < ABClipboard->count; ++i)
{
ABClipbInfo info;
ABClipboard->list[i] = NULL;
info = &(ABClipboard->info_list[i]);
obj_destroy(info->dup_obj);
info->dup_obj = NULL;
if (info->action_list)
{
obj_destroy(info->action_list);
info->action_list = NULL;
}
/*
* Clear any data for "other stuff here"
*/
}
/*
* Reset obj/action count to zero
*/
ABClipboard->count = 0;
ABClipboard->action_count = 0;
}
/*
* abobj_clipboard_add()
* Copies passed objects onto clipboard
*/
void
abobj_clipboard_add(
ABObj *obj,
int count
)
{
int action_count = 0,
i,
j;
verify_clipboard_space(count);
for (i=ABClipboard->count, j=0; i<ABClipboard->count + count; ++i, ++j)
{
ABObj project, action_obj;
ABClipbInfo info;
AB_TRAVERSAL trav;
info = &(ABClipboard->info_list[i]);
/*
* Duplicate/copy ABObj tree onto clipboard
*/
info->dup_obj = abobj_dup_tree(obj[j]);
ABClipboard->list[i] = obj[j];
project = obj_get_project(obj[j]);
/*
* Duplicate all connections of objects
* Search for all connections that have, as the source object
* the current object being added to the clipboard or any of it's
* descendants.
*/
for (trav_open(&trav, project, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
if (!action_obj->info.action.from)
{
util_dprintf(1,
"abobj_clipboard_add: action_obj->info.action.from = NIL!\n");
continue;
}
if ((action_obj->info.action.from == obj[j]) ||
(obj_is_descendant_of(action_obj->info.action.from, obj[j])))
{
ABObj new_action, new_from_obj, new_to_obj;
/*
* Create the ACTION_LIST that will hold all the connections
* that we will store on the clipboard for one particular object
* (and it's descendants).
*/
if (!info->action_list)
info->action_list = obj_create(AB_TYPE_ACTION_LIST, NULL);
/*
* Duplicate action
*/
new_action = abobj_dup(action_obj);
/*
* OK, at this point we have duplicatd the object tree and
* one connection that has it as the source.
* The problem is the source, as seen by the connection is
* the object that is still part of the project. We need
* to make the source be the dup'd object that is in the
* clipboard.
*/
/*
* Search for the dup'd object. It may not be the copied object,
* but a descendant of it. We search by name/type.
*
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_from_obj =
obj_find_by_name_and_type(info->dup_obj,
obj_get_name(action_obj->info.action.from),
obj_get_type(action_obj->info.action.from));
new_action->info.action.from = new_from_obj;
/*
* Catch the case where to/from are the same
* The target ('to') is important only if this is
* a 'predefined' action type
*/
if ((obj_get_func_type(action_obj) == AB_FUNC_BUILTIN) &&
(action_obj->info.action.from == action_obj->info.action.to))
new_action->info.action.to = new_from_obj;
/*
* Append action to action lists on clipboard
*/
obj_add_action(info->action_list, new_action);
action_count++;
}
}
trav_close(&trav);
}
ABClipboard->count += count;
ABClipboard->action_count += action_count;
resolve_clipboard_connections(obj, count, ABClipboard);
resolve_clipboard_attachments(obj, count, ABClipboard);
}
/*
* abobj_clipboard_set()
* Sets clipboard to passed objects
*/
void
abobj_clipboard_set(
ABObj *obj,
int count
)
{
int i,
j;
/*
* Clear clipboard first
*/
abobj_clipboard_clear();
abobj_clipboard_add(obj, count);
}
static ABObj
get_ctrl_pane_or_group(
ABObj paste_target
)
{
ABObj ret_obj = NULL,
parent = paste_target;
if (!paste_target)
return (ret_obj);
while (parent)
{
if (obj_is_control_panel(obj_get_root(parent)) ||
obj_is_group(obj_get_root(parent)))
{
return (parent);
}
if (obj_is_window(parent))
return (NULL);
parent = obj_get_parent(parent);
}
return (ret_obj);
}
/*
* abobj_setup_undo_cut_layer()
* This function is used for setting/updating information
* used when undoing cuts/deletes of panes in layers.
* When a cut/delete causes only one pane to be left
* as a child of a lyer, the layer is deleted. This is done
* in obj_destroyedOCB() in abobj_layers.c. So, to undo
* the cut/delete, a layer would have to be recreated.
*
* This function NULLifies the parent (layer) field in the undo
* record since it would have been deleted. Instead, the last pane
* is stored in the undo record. The new layer will then be
* created with this last pane as it's first child.
*/
void
abobj_setup_undo_cut_layer(
ABObj layer,
ABObj last_pane
)
{
int i;
for (i = 0; i < ABUndo->count; ++i)
{
ABUndoInfo undo_info;
ABObj parent;
/*
* If undo record is not of type CUT, skip
*/
if (ABUndo->info_list[i].type != AB_UNDO_CUT)
continue;
undo_info = &(ABUndo->info_list[i]);
parent = undo_info->info.cut.parent;
/*
* If parent object stored in undo record matches
* the layer object passed in, NULLify it, and store
* the last_pane object ptr passed in.
*/
if (parent == layer)
{
undo_info->info.cut.parent = NULL;
undo_info->info.cut.pane_sibling = last_pane;
}
}
}
/*
* Undo functions for CUT and PASTE
*/
static void
undo_cut(
ABUndoRec undo_rec
)
{
ABUndoInfo undo_info;
ABObj *new_list = NULL,
*new_from_action_list = NULL,
*new_to_action_list = NULL;
int i,
j,
from_action_count = 0,
to_action_count = 0;
ABObj dup_obj,
parent,
newObj;
if (!undo_rec)
return;
abobj_deselect_all(proj_get_project());
/*
* List to remember what new objects were created
*/
new_list = (ABObj *)malloc(undo_rec->count * sizeof(ABObj));
if (undo_rec->action_count > 0)
{
/*
* Malloc space for from/to lists
* We malloc more space than we need here instead of
* traversing the lists yet again to figure out how much we need
* exactly for each list.
*/
new_from_action_list = (ABObj *)malloc(undo_rec->action_count * sizeof(ABObj));
new_to_action_list = (ABObj *)malloc(undo_rec->action_count * sizeof(ABObj));
}
/*
* Loop thru CUT undo records and re-create the saved object and it's
* actions
*/
for (i = 0; i < undo_rec->count; ++i)
{
/*
* If undo record is not the right type, something is WRONG !!
*/
if (undo_rec->info_list[i].type != AB_UNDO_CUT)
continue;
undo_info = &(undo_rec->info_list[i]);
dup_obj = undo_info->info.cut.dup_obj;
parent = undo_info->info.cut.parent;
/*
* Duplicate object in undo record
*/
newObj = abobj_dup_tree(dup_obj);
/*
* Add connections from 'from' list
* These are actions that have the re-created object (or any of it's
* descendants) as the source ('from').
*/
if (undo_info->info.cut.from_action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
for (trav_open(&trav, undo_info->info.cut.from_action_list,
AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj orig_from_obj,
new_from_obj,
new_action;
new_action = abobj_dup_tree(action_obj);
/*
* Since the from object is re-created, we need to
* reassign the 'from' field of the action to the new 'from'
* object.
*/
orig_from_obj = new_action->info.action.from;
/*
* The new 'from' object is searched for by name/type
* The search is rooted at the object that was re-created.
*/
new_from_obj =
obj_find_by_name_and_type(newObj,
obj_get_name(orig_from_obj),
obj_get_type(orig_from_obj));
new_action->info.action.from = new_from_obj;
/*
* If source == target, reassign the 'to' field as well
*/
if (action_obj->info.action.from == action_obj->info.action.to)
new_action->info.action.to = new_from_obj;
/*
* Keep all 'from' connections in a list
*/
new_from_action_list[from_action_count++] = new_action;
}
trav_close(&trav);
}
/*
* Add connections from 'to' list
* These are actions that have the re-created object (or any of it's
* descendants) as the target ('to').
*/
if (undo_info->info.cut.to_action_list)
{
AB_TRAVERSAL trav;
ABObj action_obj;
for (trav_open(&trav, undo_info->info.cut.to_action_list, AB_TRAV_ACTIONS);
(action_obj = trav_next(&trav)) != NULL; )
{
ABObj orig_to_obj,
new_to_obj,
new_action;
new_action = abobj_dup_tree(action_obj);
/*
* Since the 'to' object is re-created, we need to
* reassign the 'to' field of the action to the new 'to'
* object.
*/
orig_to_obj = new_action->info.action.to;
/*
* The new 'to' object is searched for by name/type
* The search is rooted at the object that was re-created.
*/
new_to_obj =
obj_find_by_name_and_type(newObj,
obj_get_name(orig_to_obj),
obj_get_type(orig_to_obj));
new_action->info.action.to = new_to_obj;
/*
* Keep all 'to' connections in a list
*/
new_to_action_list[to_action_count++] = new_action;
}
trav_close(&trav);
}
/*
* Keep newly created object in a list
*/
new_list[i] = newObj;
}
/*
* At this point:
* All objects have been created but not parented to the proper object
* All connection objects have been created, but their to/from fields
* need to be resolved.
*/
/*
* Resolve 'from' action list
* Actions on this list may have as a target ('to') an object that
* was dup'd onto the undo record (and re-created again in the first loop
* above) . If this is the case, the action's target must be set to the
* recreated object.
*/
for (i=0; i < from_action_count; ++i)
{
ABObj action,
orig_to_obj;
action = new_from_action_list[i];
if (obj_get_func_type(action) != AB_FUNC_BUILTIN)
continue;
orig_to_obj = action->info.action.to;
for (j = 0; j < undo_rec->count; ++j)
{
ABObj dup_obj;
undo_info = &(undo_rec->info_list[j]);
dup_obj = undo_info->info.cut.dup_obj;
if ((orig_to_obj == dup_obj) ||
(obj_is_descendant_of(orig_to_obj, dup_obj)))
{
ABObj new_to_obj;
/*
* Search by name/type
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_to_obj = obj_find_by_name_and_type(new_list[j],
obj_get_name(orig_to_obj),
obj_get_type(orig_to_obj));
action->info.action.to = new_to_obj;
break;
}
}
}
/*
* Resolve 'to' action list
* Actions on this list may have as a source ('from') an object that
* was dup'd onto the undo record (and re-created again in the first loop
* above) . If this is the case, the action's source must be set to the
* recreated object.
*/
for (i=0; i < to_action_count; ++i)
{
ABObj action,
orig_from_obj;
action = new_to_action_list[i];
orig_from_obj = action->info.action.from;
for (j = 0; j < undo_rec->count; ++j)
{
ABObj dup_obj;
undo_info = &(undo_rec->info_list[j]);
dup_obj = undo_info->info.cut.dup_obj;
if ((orig_from_obj == dup_obj) ||
(obj_is_descendant_of(orig_from_obj, dup_obj)))
{
ABObj new_from_obj;
/*
* Search by name/type
* This depends on the fact that the dup'd tree maintains objs
* with same names
*/
new_from_obj = obj_find_by_name_and_type(new_list[j],
obj_get_name(orig_from_obj),
obj_get_type(orig_from_obj));
action->info.action.from = new_from_obj;
break;
}
}
}
/*
* Resolve newly created objects' (and their descendants)
* attachments.
*/
for (i = 0; i < undo_rec->count; ++i)
{
AB_TRAVERSAL trav;
ABObj origObj = NULL,
childObj = NULL;
undo_info = &(undo_rec->info_list[i]);
if (!undo_info)
continue;
origObj = undo_info->info.cut.dup_obj;
resolve_attach_one_obj(new_list[i], origObj, new_list, undo_rec->count);
for (trav_open(&trav, new_list[i], AB_TRAV_SALIENT);
(childObj = trav_next(&trav)) != NULL; )
{
ABObj origChildObj = NULL;
/*
* Skip top level object - taken care of above
*/
if (childObj == new_list[i])
continue;
/*
* Get the handle to the child object that
* corresponds to the new dup'd one
*/
origChildObj = obj_find_by_name_and_type(origObj,
obj_get_name(childObj),
obj_get_type(childObj));
resolve_attach_one_obj(childObj, origChildObj,
new_list, undo_rec->count);
}
trav_close(&trav);
}
/*
* Add newly created objects to parent hierarchy
* Create the object's widgets and manage/map them
*/
for (i = 0; i < undo_rec->count; ++i)
{
BOOL manage_last = TRUE;
ABObj last_pane,
proper_parent;
/*
* parent -> parent of cut object
* last_pane -> last pane of layer. This case is used
* if/when parent was a layer and a cut/delete
* caused it to have only one pane left. The
* logic in abobj_layers.c would delete the
* layer, invalidating the parent field in
* the undo record. To make undo still work,
* the last pane in the layer is stored instead
* (and the parent field NULLified).
*/
parent = undo_rec->info_list[i].info.cut.parent;
last_pane = undo_rec->info_list[i].info.cut.pane_sibling;
if (parent)
{
proper_parent = parent;
/*
* If pasted object is menubar, paste to proper
* config child of main window
*/
if (obj_is_menubar(new_list[i]))
{
ABObj winobj = parent;
/*
* Get window object
*/
while(!(obj_is_window(winobj) && !obj_is_sub(winobj)))
winobj = obj_get_parent(winobj);
if (winobj)
proper_parent =
objxm_comp_get_subobj(winobj, AB_CFG_MENU_PARENT_OBJ);
}
}
else
{
/*
* parent == NULL
* This is the layer case described above.
* Use 'last_pane' to create a layer and append the new
* dup'd object to it.
*/
ABObj layer;
layer = abobj_handle_layered_pane(new_list[i], last_pane);
proper_parent = layer;
}
/*
* Resolve attachments where the attached object is the parent
* object. This is for top level clipboard objects. The new
* parent object is only known at paste time. Until now, the value
* field of the attachment was NULL.
*/
resolve_attach_parent(new_list[i], proper_parent);
/*
* Last for attchments - resolve NONE attachments
* If 2 opposite sides have NONE attachments, create
* an AB_ATTACH_POINT attachment on one side.
*/
resolve_none_attachments(new_list[i]);
obj_append_child(proper_parent, new_list[i]);
if (obj_is_layers(new_list[i]) ||
obj_is_group(new_list[i]) ||
obj_is_menu(new_list[i]))
manage_last = FALSE;
abobj_show_tree(new_list[i], manage_last);
abobj_select(new_list[i]);
abobj_set_save_needed(obj_get_module(new_list[i]), TRUE);
}
/*
* Go thru to/from action lists and add the actions to
* the proper module or project
*/
for (i=0; i < from_action_count; ++i)
{
BOOL is_cross_module;
ABObj action;
action = new_from_action_list[i];
is_cross_module = (obj_get_func_type(action) == AB_FUNC_BUILTIN) &&
obj_is_cross_module(action);
if (is_cross_module)
{
obj_add_action(obj_get_project(action->info.action.from), action);
abobj_set_save_needed(obj_get_project(action->info.action.from), TRUE);
}
else
{
obj_add_action(obj_is_project(action->info.action.from) ?
action->info.action.from :
obj_get_module(action->info.action.from), action);
}
}
for (i=0; i < to_action_count; ++i)
{
BOOL is_cross_module;
ABObj action;
action = new_to_action_list[i];
is_cross_module = (obj_get_func_type(action) == AB_FUNC_BUILTIN) &&
obj_is_cross_module(action);
if (is_cross_module)
{
obj_add_action(obj_get_project(action->info.action.from), action);
abobj_set_save_needed(obj_get_project(action->info.action.from), TRUE);
}
else
{
obj_add_action(obj_is_project(action->info.action.from) ?
action->info.action.from :
obj_get_module(action->info.action.from), action);
}
}
/*
* Set undo to undo this 'paste'
*/
(void)abobj_set_undo(new_list, undo_rec->count,
undo_paste, AB_UNDO_PASTE);
if (new_list)
free(new_list);
if (new_from_action_list)
free(new_from_action_list);
if (new_to_action_list)
free(new_to_action_list);
}
static void
undo_paste(
ABUndoRec undo_rec
)
{
int x,
y,
width,
height,
i = 0;
ABObj obj,
dup_obj,
parent_obj,
newObj;
if (!undo_rec)
return;
/*
* Set undo to undo this 'cut'
*/
(void)abobj_set_undo(undo_rec->list, undo_rec->count,
undo_cut, AB_UNDO_CUT);
for (i = 0; i < undo_rec->count; ++i)
{
/*
* If undo record is not the right type, something is WRONG !!
*/
if (undo_rec->info_list[i].type != AB_UNDO_PASTE)
continue;
obj = undo_rec->list[i];
abobj_set_save_needed(obj_get_module(obj), TRUE);
/*
* Remove the object from to the hierarchy
*/
obj_destroy(obj);
}
}
/*
* Xt Callbacks for editing functions:
* undo
* cut
* copy
* paste
* delete
*/
void
abobj_undo_cb(
Widget widget,
XtPointer client_data,
XtPointer call_data
)
{
(void)abobj_undo();
}
void
abobj_cut_cb(
Widget widget,
XtPointer client_data,
XtPointer call_data
)
{
(void)abobj_cut();
}
void
abobj_copy_cb(
Widget widget,
XtPointer client_data,
XtPointer call_data
)
{
(void)abobj_copy();
}
void
abobj_paste_cb(
Widget widget,
XtPointer client_data,
XtPointer call_data
)
{
(void)abobj_paste((AB_PASTE_INITIATOR_TYPE) client_data);
}
void
abobj_delete_cb(
Widget widget,
XtPointer client_data,
XtPointer call_data
)
{
(void)abobj_delete();
}
/*
** If the window the parent object is on is iconified, show the modal dialog
** relative to where the paste was initiated, otherwise parent the dialog off
** of the parent_obj.
*/
static DTB_MODAL_ANSWER
show_modal_msg_relative_initiator(
ABObj parent_obj,
AB_PASTE_INITIATOR_TYPE initiator,
DtbMessageData mbr,
XmString override_msg,
DtbObjectHelpData override_help,
Widget *modal_dlg_pane_out_ptr
)
{
ABObj winobj = NULL;
Widget parent = NULL;
if (!parent_obj || !mbr)
return DTB_ANSWER_CANCEL;
winobj = obj_get_window(parent_obj);
if ((winobj != NULL) && obj_has_flag(winobj, IconifiedFlag))
{
/*
** The window is iconified, determine who to parent the dialog off
** of.
*/
switch(initiator)
{
case AB_PASTE_INITIATOR_OBJ_MENU:
/*
** paste initiated from obj menu! The window shouldn't be iconified.
** popup over the palette to be safe.
*/
parent = dtb_get_toplevel_widget();
break;
case AB_PASTE_INITIATOR_BRWS_MENU:
case AB_PASTE_INITIATOR_BRWS_EDIT_MENU:
parent = brws_get_browser_shell_for_obj(parent_obj);
if (!parent)
parent = dtb_get_toplevel_widget();
break;
case AB_PASTE_INITIATOR_PAL_EDIT_MENU:
parent = dtb_get_toplevel_widget();
break;
default:
return DTB_ANSWER_CANCEL;
}
}
else
{
/*
** The window parent_obj is on is not iconified, parent the dialog
** off it.
*/
parent = objxm_get_widget(parent_obj);
}
return(dtb_show_modal_message(
parent, mbr, override_msg,
override_help, modal_dlg_pane_out_ptr)
);
}