Initial import of the CDE 2.1.30 sources from the Open Group.
[oweals/cde.git] / cde / programs / dtfile / Command.c
1 /* $XConsortium: Command.c /main/9 1996/10/30 11:09:42 drk $ */
2 /************************************<+>*************************************
3  ****************************************************************************
4  *
5  *   FILE:           Command.c
6  *
7  *   COMPONENT_NAME: Desktop File Manager (dtfile)
8  *
9  *   Description:    Command processing functions used by the File Browser.
10  *
11  *   FUNCTIONS: ActionCallback
12  *              InvalidTrashDragDrop
13  *              ProcessAction
14  *              ProcessBufferDropOnFolder
15  *              ProcessMoveCopyLink
16  *              ProcessNewView
17  *              RunCommand
18  *              TimerEvent
19  *              UpdateActionMenuPane
20  *
21  *   (c) Copyright 1993, 1994, 1995 Hewlett-Packard Company
22  *   (c) Copyright 1993, 1994, 1995 International Business Machines Corp.
23  *   (c) Copyright 1993, 1994, 1995 Sun Microsystems, Inc.
24  *   (c) Copyright 1993, 1994, 1995 Novell, Inc.
25  *
26  ****************************************************************************
27  ************************************<+>*************************************/
28
29
30 #include <sys/types.h>
31 #include <sys/stat.h>
32 #include <Xm/PushBG.h>
33 #include <Xm/RowColumn.h>
34 #include <Xm/SeparatoG.h>
35 #include <Xm/XmP.h>
36 #include <X11/Shell.h>
37 #include <X11/Intrinsic.h>
38
39 #include <Dt/Action.h>
40 #include <Dt/ActionP.h>
41 #include <Dt/Connect.h>
42 #include <Dt/DtNlUtils.h>
43 #include <Dt/HourGlass.h>
44 #include <Dt/Dnd.h>
45
46 #include "Encaps.h"
47 #include "SharedProcs.h"
48 #include "FileMgr.h"
49 #include "Desktop.h"
50 #include "Main.h"
51 #include "Help.h"
52 #include "SharedMsgs.h"
53 #include "Prefs.h"
54
55 /********    Static Function Declarations    ********/
56
57 static void ActionCallback( Widget w,
58                             XtPointer client_data,
59                             XtPointer call_data) ;
60 static void TimerEvent(
61                         Widget widget,
62                         XtIntervalId *id) ;
63 void ProcessBufferDropOnFolder (
64                                 char *command,
65                                 FileMgrData *file_mgr_data,
66                                 FileViewData *file_view_data,
67                                 DtDndDropCallbackStruct *drop_parameters,
68                                 Widget drop_window);
69
70
71 /********    End Static Function Declarations    ********/
72
73 extern int G_dropx,G_dropy;
74
75 /************************************************************************
76  *
77  *  UpdateActionMenuPane
78  *      Build up a set of menu panes for the provided file manager rec
79  *      that contains as items each of the commands for each file type.
80  *
81  ************************************************************************/
82
83 void
84 UpdateActionMenuPane(
85         XtPointer client_data,
86         FileMgrRec *file_mgr_rec,
87         char *file_type,
88         int type,
89         int number,
90         Widget widget,
91         unsigned char physical_type)
92 {
93    XmManagerWidget action_pane;
94    FileMgrData *file_mgr_data = NULL;
95    DialogData * dialog_data;
96    FileViewData *file_view_data;
97    DesktopRec *desktopWindow;
98    Widget child;
99    register int i, menu_offset;
100    register int action_count;
101    int count, del_count;
102    int num_children;
103    char ** command_list;
104    XmString string;
105    char *action_label;
106
107    Arg args[2];
108
109
110    if(type == DESKTOP)
111       desktopWindow = (DesktopRec *)client_data;
112    else if(type == FM_POPUP)
113       file_view_data = (FileViewData *)client_data;
114
115    if (file_mgr_rec)
116    {
117       dialog_data = _DtGetInstanceData ((XtPointer)file_mgr_rec);
118       file_mgr_data = (FileMgrData *) dialog_data->data;
119    }
120
121    /*  Count the number of actions defined for the the file type  */
122
123    action_count = 0;
124    command_list = _DtCompileActionVector(file_type);
125
126    if(command_list != NULL)
127       while (command_list[action_count] != NULL &&
128                                     strlen (command_list[action_count]) != 0)
129          action_count++;
130
131    if (physical_type == DtDIRECTORY && type == DESKTOP)
132        ++action_count;
133
134
135    /*  If the menu pane is already set up for the file type then return.  */
136
137    if(type == NOT_DESKTOP)
138    {
139       XtFree(file_mgr_rec->action_pane_file_type);
140       file_mgr_rec->action_pane_file_type = XtNewString(file_type);
141
142       action_pane = (XmManagerWidget) file_mgr_rec->action_pane;
143       menu_offset = number + SELECTED_MENU_MAX;
144    }
145    else
146    {
147       action_pane = (XmManagerWidget) widget;
148       menu_offset = number;
149    }
150
151    num_children = action_pane->composite.num_children;
152
153    /*
154     * This is so that we can determine which icon was responsible for
155     * posting the menu, or requesting help.
156     */
157    if (type == FM_POPUP)
158       file_mgr_data->popup_menu_icon = file_view_data;
159 /*
160    else if (file_mgr_data)
161       file_mgr_data->popup_menu_icon = NULL;
162 */
163
164    if (action_count + menu_offset > num_children)
165    {
166       if(type == FM_POPUP)
167          for (i = number; i < num_children; i++)
168             XtManageChild (action_pane->composite.children[i]);
169       for (i = num_children; i < action_count + menu_offset; i++)
170       {
171          child = XmCreatePushButtonGadget ((Widget)action_pane,
172                                             "action_button", args, 0);
173          if(type == DESKTOP)
174          {
175             XtAddCallback (child, XmNactivateCallback, DTActionCallback,
176                                                     (XtPointer)desktopWindow);
177             XtAddCallback(child, XmNhelpCallback,
178                           (XtCallbackProc)DTHelpRequestCB, NULL);
179          }
180          else if(type == FM_POPUP)
181          {
182             XtAddCallback (child, XmNactivateCallback, ActionCallback, NULL);
183             XtAddCallback(child, XmNhelpCallback,
184                           (XtCallbackProc)HelpRequestCB, NULL);
185          }
186          else
187          {
188             XtAddCallback (child, XmNactivateCallback, ActionCallback,
189                                                     (XtPointer)file_mgr_rec);
190             XtAddCallback(child, XmNhelpCallback,
191                           (XtCallbackProc)HelpRequestCB, NULL);
192          }
193          XtManageChild (child);
194       }
195    }
196    else
197    {
198      for (i = menu_offset; i < num_children; i++)
199      {
200        if (i < action_count + menu_offset)
201        {
202           XtRemoveAllCallbacks(action_pane->composite.children[i],
203                                                    XmNactivateCallback);
204           if(type == DESKTOP)
205              XtAddCallback (action_pane->composite.children[i],
206                                      XmNactivateCallback, DTActionCallback,
207                                      (XtPointer)desktopWindow);
208           else if(type == FM_POPUP)
209              XtAddCallback (action_pane->composite.children[i],
210                                   XmNactivateCallback, ActionCallback, NULL);
211           else
212              XtAddCallback (action_pane->composite.children[i],
213                                         XmNactivateCallback, ActionCallback,
214                                         (XtPointer)file_mgr_rec);
215           XtManageChild (action_pane->composite.children[i]);
216         }
217         else
218           XtUnmanageChild (action_pane->composite.children[i]);
219      }
220    }
221
222    /*  For each action, set the label of the menu button  */
223    /*  and the user data.                                 */
224
225    num_children =  menu_offset + action_count;
226    del_count = 0;
227    for (i = 0; i < action_count; i++)
228    {
229       char *oldCommand;
230       Arg argsTmp[2];
231
232       if(type != DESKTOP)
233       {
234          char* strp;
235
236          strp = XtNewString(command_list[i]);
237          XtSetArg (args[1], XmNuserData, strp);
238
239          action_label = DtActionLabel(command_list[i]);
240
241          if(action_label != NULL)
242              string = XmStringCreateLocalized(action_label);
243          else
244              string = XmStringCreateLocalized(command_list[i]);
245
246          count = i + menu_offset;
247       }
248       else
249       {
250          if (physical_type == DtDIRECTORY && i == 0)
251          {
252             if((action_label = DtActionLabel(openNewView)) != NULL)
253                 string = XmStringCreateLocalized(action_label);
254             else
255                 string = XmStringCreateLocalized(openNewView);
256             XtSetArg (args[1], XmNuserData, openNewView);
257             count = i + number;
258          }
259          else
260          {
261             if (physical_type == DtDIRECTORY)
262             {
263                if(strcmp(command_list[i - 1], openNewView) == 0  ||
264                   strcmp(command_list[i - 1], openInPlace) == 0)
265                {
266                   del_count++;
267                   XtUnmanageChild (action_pane->composite.children[
268                                               num_children - del_count]);
269                   continue;
270                }
271                action_label = DtActionLabel(command_list[i - 1]);
272
273                if(action_label != NULL)
274                    string = XmStringCreateLocalized(action_label);
275                else
276                    string = XmStringCreateLocalized(command_list[i-1]);
277
278                XtSetArg (args[1], XmNuserData, XtNewString
279                                                  (command_list[i - 1]));
280                count = i + number - del_count;
281             }
282             else
283             {
284                action_label = DtActionLabel(command_list[i]);
285
286                if(action_label != NULL)
287                    string = XmStringCreateLocalized(action_label);
288                else
289                    string = XmStringCreateLocalized(command_list[i]);
290
291                XtSetArg (args[1], XmNuserData, XtNewString(command_list[i]));
292                count = i + number;
293             }
294          }
295       }
296
297       /* first we need to get the userData from the push button and
298          free it up */
299       XtSetArg (argsTmp[0], XmNuserData, &oldCommand);
300       XtGetValues (action_pane->composite.children[count], argsTmp, 1);
301
302       if(oldCommand != NULL && strcmp(oldCommand, openNewView) != 0)
303          XtFree(oldCommand);
304
305
306       XtSetArg (args[0], XmNlabelString, string);
307       XtSetValues (action_pane->composite.children[count], args, 2);
308       if(type != NOT_DESKTOP)
309       {
310          XtRemoveAllCallbacks(action_pane->composite.children[count],
311                                                    XmNactivateCallback);
312
313          if(type == DESKTOP)
314          {
315             XtAddCallback (action_pane->composite.children[count],
316                            XmNactivateCallback, DTActionCallback,
317                            (XtPointer)desktopWindow);
318          }
319          else
320             XtAddCallback (action_pane->composite.children[count],
321                            XmNactivateCallback, ActionCallback,
322                            (XtPointer)NULL);
323       }
324       XmStringFree (string);
325       XtFree(action_label);
326    }
327
328    _DtFreeStringVector(command_list);
329 }
330
331
332
333
334 /************************************************************************
335  *
336  *  ActionCallback
337  *      Callback function invoked upon the an actions menu button
338  *      being selected.
339  *
340  ************************************************************************/
341
342 static void
343 ActionCallback(
344         Widget w,
345         XtPointer client_data,
346         XtPointer call_data )
347 {
348    FileMgrRec   * file_mgr_rec;
349    DialogData   * dialog_data;
350    FileMgrData  * file_mgr_data;
351    FileViewData  * file_view_data;
352    char * command;
353    Arg args[1];
354    Widget mbar;
355    Boolean popup = False;
356    XmAnyCallbackStruct * callback;
357
358
359    XmUpdateDisplay (w);
360    mbar = XtParent(w);
361
362    callback = (XmAnyCallbackStruct *) call_data;
363    if(client_data == NULL)
364    {
365       popup = True;
366       XtSetArg(args[0], XmNuserData, &file_mgr_rec);
367       XtGetValues(mbar, args, 1);
368       dialog_data = _DtGetInstanceData ((XtPointer)file_mgr_rec);
369       file_mgr_data = (FileMgrData *) dialog_data->data;
370       file_view_data = file_mgr_data->popup_menu_icon;
371       if(!file_view_data) /* The object would have probably been delete */
372          return;
373       file_mgr_data->popup_menu_icon = NULL;  /* Just to make it unuseful */
374    }
375    else
376    {
377       file_mgr_rec = (FileMgrRec *) client_data;
378       dialog_data = _DtGetInstanceData ((XtPointer)file_mgr_rec);
379       file_mgr_data = (FileMgrData *) dialog_data->data;
380    }
381
382
383
384    /*  Find the file data for the file that is selected  */
385
386    XtSetArg (args[0], XmNuserData, (XtPointer) &command);
387    XtGetValues (w, args, 1);
388
389    if(strcmp(command, openNewView) == 0)
390    {
391       XButtonEvent *event = (XButtonEvent *)callback->event;
392       unsigned int modifiers;
393
394       modifiers = event->state;
395       if(popup)
396          RunCommand (command, file_mgr_data, file_view_data, NULL, NULL, NULL);
397       else
398          RunCommand (command, file_mgr_data, file_mgr_data->selection_list[0],
399                      NULL, NULL, NULL);
400
401       if((modifiers != 0) && ((modifiers & ControlMask) != 0))
402       {
403          DialogData  *dialog_data;
404
405          dialog_data = _DtGetInstanceData(file_mgr_data->file_mgr_rec);
406          CloseView(dialog_data);
407       }
408    }
409    else
410    {
411       if(popup)
412          RunCommand (command, file_mgr_data, file_view_data, NULL, NULL, NULL);
413       else
414          RunCommand (command, file_mgr_data, file_mgr_data->selection_list[0],
415                      NULL, NULL, NULL);
416    }
417 }
418
419
420 /************************************************************************
421  *
422  *  RunCommand
423  *
424  *    WARNING: when desktop links are passed in, this function will NOT
425  *             expect the links to have already been mapped to their
426  *             real files.
427  *
428  ************************************************************************/
429
430 void
431 RunCommand(
432         char *command,
433         FileMgrData *file_mgr_data,
434         FileViewData *file_view_data,
435         WindowPosition *position,
436         DtDndDropCallbackStruct *drop_parameters,
437         Widget drop_window )
438
439 {
440    if ((strcmp (command, openInPlace) == 0) ||
441        (strcmp (command, openNewView) == 0))
442    {
443       /* If the folder is locked, don't allow user to go into it */
444       if( strcmp( file_view_data->file_data->logical_type, LT_FOLDER_LOCK ) == 0 )
445       {
446         char *tmpStr, *title, *msg;
447
448         tmpStr = GETMESSAGE(9, 6, "Action Error");
449         title = XtNewString(tmpStr);
450         msg = (char *)XtMalloc(
451                    strlen( GETMESSAGE(30, 1, "Cannot read from %s") )
452                  + strlen( file_view_data->file_data->file_name )
453                  + 1 );
454         sprintf( msg, GETMESSAGE(30, 1, "Cannot read from %s"),
455                  file_view_data->file_data->file_name );
456         _DtMessage(((FileMgrRec*)file_mgr_data->file_mgr_rec)->file_window,
457                    title, msg, NULL, HelpRequestCB );
458         XtFree(title);
459         XtFree(msg);
460         return;
461       }
462
463       /* this statement applies to the case where a user traverses down the *
464        * part of the directory tree containing the application manager      *
465        * directories or the trash directory                                 */
466       if( ((strcmp(file_view_data->file_data->logical_type, LT_AGROUP) == 0) &&
467            (!(file_mgr_data->toolbox)))
468           ||
469           (strcmp(file_view_data->file_data->logical_type, LT_TRASH) == 0) )
470       {
471          ProcessAction(command,
472                        file_view_data,
473                        drop_parameters,
474                        file_mgr_data->host,
475                        file_mgr_data->current_directory,
476                        file_mgr_data->restricted_directory,
477                        ((FileMgrRec *) file_mgr_data->file_mgr_rec)->shell);
478       }
479       else
480       {
481          ProcessNewView(command, file_mgr_data, file_view_data, position);
482       }
483    }
484
485    else if ((strcmp (command, "FILESYSTEM_MOVE") == 0) ||
486             (strcmp (command, "FILESYSTEM_COPY") == 0) ||
487             (strcmp (command, "FILESYSTEM_LINK") == 0))
488    {
489       /* Check to see what was dropped (files or buffers) */
490       /* Call the appropriate routine to handle the drop  */
491       if (drop_parameters->dropData->protocol == DtDND_FILENAME_TRANSFER)
492          ProcessMoveCopyLink(command,
493                              file_mgr_data,
494                              file_view_data,
495                              drop_parameters,
496                              drop_window);
497       else
498          if (drop_parameters->dropData->protocol == DtDND_BUFFER_TRANSFER)
499            ProcessBufferDropOnFolder(command,
500                                      file_mgr_data,
501                                      file_view_data,
502                                      drop_parameters,
503                                      drop_window);
504
505    }
506
507    else
508    {
509       ProcessAction(command,
510                     file_view_data,
511                     drop_parameters,
512                     file_mgr_data->host,
513                     file_mgr_data->current_directory,
514                     file_mgr_data->restricted_directory,
515                     ((FileMgrRec *) file_mgr_data->file_mgr_rec)->shell);
516    }
517 }
518
519
520 /************************************************************************
521  *
522  *  ProcessNewView
523  *
524  ************************************************************************/
525
526 void
527 ProcessNewView (
528      char *command,
529      FileMgrData *file_mgr_data,
530      FileViewData *file_view_data,
531      WindowPosition *position)
532 {
533    DirectorySet * directory_set;
534    char host_name[MAX_PATH];
535    char directory_name[MAX_PATH];
536    char *tmpStr, *title, *msg;
537
538    /* we don't want to execute the default action if in trash ...  */
539    if( trashFileMgrData != NULL
540        && file_mgr_data == trashFileMgrData)
541    {
542       tmpStr = GETMESSAGE(27, 3, "Trash Can Error");
543       title = XtNewString(tmpStr);
544       tmpStr = GETMESSAGE(27, 87, "Object in the Trash cannot be opened.\n\nTo open an object use 'Put Back' to return it to the\nFile Manager then open it there.");
545       msg = XtNewString(tmpStr);
546
547       _DtMessage( ((FileMgrRec *)file_mgr_data->file_mgr_rec)->file_window,
548                   title, msg, NULL, HelpRequestCB);
549       XtFree(title);
550       XtFree(msg);
551       return;
552    }
553
554    strcpy (host_name, file_mgr_data->host);
555
556    directory_set = (DirectorySet *) (file_view_data->directory_set);
557    strcpy (directory_name, directory_set->name);
558
559    if (strcmp (directory_name, "/") != 0)
560       strcat (directory_name, "/");
561
562    strcat (directory_name, file_view_data->file_data->file_name);
563    DtEliminateDots (directory_name);
564
565    if (strcmp (directory_name, "/..") == 0)
566       strcpy (directory_name, "/");
567
568    if (strcmp (command, openInPlace) == 0)
569    {
570       FileMgrRec *file_mgr_rec;
571       Arg args[1];
572       Widget vb;
573       int value, size, increment, page;
574
575       file_mgr_rec = (FileMgrRec *)file_mgr_data->file_mgr_rec;
576       ShowNewDirectory (file_mgr_data, host_name, directory_name);
577
578       XtSetArg (args[0], XmNverticalScrollBar, &vb);
579       XtGetValues (file_mgr_rec->scroll_window, args, 1);
580
581          /* get scroll bar values */
582       (void)XmScrollBarGetValues(vb, &value, &size, &increment, &page);
583
584          /* set scroll bar values changing its position */
585       if(value != 0)
586         (void)XmScrollBarSetValues(vb, (int)0, size, increment, page, True);
587
588       if(strcmp(file_mgr_data->current_directory,
589                 file_mgr_data->restricted_directory) == 0)
590       {
591          XtSetSensitive(*upBarBtn, False);
592          currentMenuStates &= ~(MOVE_UP);
593          file_mgr_rec->menuStates &= ~(MOVE_UP);
594       }
595       else
596       {
597          file_mgr_rec->menuStates |= MOVE_UP;
598          XtSetSensitive(*upBarBtn, True);
599          currentMenuStates &= ~(MOVE_UP);
600       }
601    }
602    else
603    {
604       initiating_view = (XtPointer) file_mgr_data;
605       if(file_mgr_data->restricted_directory == NULL)
606       {
607          GetNewView (host_name, directory_name, NULL, position, 0);
608       }
609       else
610       {
611          special_view = True;
612          special_treeType = file_mgr_data->show_type;
613          special_viewType = file_mgr_data->view;
614          special_orderType = file_mgr_data->order;
615          special_directionType = file_mgr_data->direction;
616          special_randomType = file_mgr_data->positionEnabled;
617          special_restricted =
618             XtNewString(file_mgr_data->restricted_directory);
619          if(file_mgr_data->title == NULL)
620             special_title = NULL;
621          else
622             special_title = XtNewString(file_mgr_data->title);
623          special_helpVol = XtNewString(file_mgr_data->helpVol);
624          if(file_mgr_data->toolbox)
625             GetNewView (file_mgr_data->host, directory_name,
626                         file_mgr_data->restricted_directory, position, 0);
627          else
628             GetNewView (file_mgr_data->host, directory_name, NULL, position, 0);
629       }
630
631       initiating_view = (XtPointer) NULL;
632    }
633 }
634
635
636 /************************************************************************
637  *
638  *  ProcessMoveCopyLink
639  *
640  ************************************************************************/
641
642 void
643 ProcessMoveCopyLink (
644      char *command,
645      FileMgrData *file_mgr_data,
646      FileViewData *file_view_data,
647      DtDndDropCallbackStruct *drop_parameters,
648      Widget drop_window)
649
650 {
651    unsigned int modifiers = NULL;
652    int numFiles, i;
653    char ** file_set = NULL;
654    char ** host_set = NULL;
655    Boolean trashFile;
656
657
658      /***************************************************/
659      /* if no drop_parameters, there is nothing to move */
660      /***************************************************/
661    if (!drop_parameters)
662      return;
663
664
665      /**************************/
666      /* are these trash files? */
667      /**************************/
668    trashFile = FileFromTrash(drop_parameters->dropData->data.files[0]);
669
670
671      /***********************************************/
672      /* if trying to copy or link from trash return */
673      /***********************************************/
674    if (trashFile)
675    {
676      if (file_mgr_data != trashFileMgrData)
677         if (InvalidTrashDragDrop(drop_parameters->operation,
678               FROM_TRASH,
679               ((FileMgrRec *)file_mgr_data->file_mgr_rec)->file_window))
680            return;
681    }
682
683
684      /***************************************************/
685      /* extract file and host sets from drop parameters */
686      /***************************************************/
687    numFiles = drop_parameters->dropData->numItems;
688    _DtSetDroppedFileInfo(drop_parameters, &file_set, &host_set);
689
690
691       /******************************/
692       /* set movement modifier mask */
693       /******************************/
694    if( (initiating_view != NULL) &&
695        (((FileMgrData *)initiating_view)->toolbox) )
696    {
697       /* if initiating_view is a toolbox, the transfer must be */
698       /* a copy                                                */
699       modifiers = ControlMask;
700    }
701    else
702    {
703       if (strcmp(command, "FILESYSTEM_COPY") == 0)
704          modifiers = ControlMask;
705       else if (strcmp(command, "FILESYSTEM_LINK") == 0)
706          modifiers = ShiftMask;
707       else
708          modifiers = NULL;
709    }
710
711
712       /*****************************/
713       /* Files dropped on a window */
714       /*****************************/
715    if (drop_window)
716    {
717         /****************************************************************/
718         /* Files dropped in the trash -- move files  to trash directory */
719         /****************************************************************/
720       if(file_mgr_data == trashFileMgrData && !trashFile)
721       {
722          DPRINTF(("DropOnFileWindow:Dragging File(s) to Trash Can from NonTrash Window\n"));
723
724          DropOnTrashCan(numFiles, host_set, file_set, drop_parameters);
725       }
726
727         /****************************************************************/
728         /* Files dragged in the trash -- do nothing                     */
729         /****************************************************************/
730       else if(file_mgr_data == trashFileMgrData && trashFile)
731       {
732          DPRINTF(("DropOnFileWindow: Drag from Within Trash Can\n"));
733       }
734
735         /****************************************************************/
736         /* Files dragged from the trash -- move the files to their new  */
737         /* location                                                     */
738         /****************************************************************/
739       else if(trashFile && file_mgr_data != trashFileMgrData)
740       {
741          DPRINTF(("DropOnFileWindow: Dragging from Trash to Folder Window\n"));
742
743          MoveOutOfTrashCan(file_mgr_data,
744                            (FileMgrRec *)file_mgr_data->file_mgr_rec,
745                            XtWindow(drop_window),  numFiles, host_set,
746                            file_set, drop_parameters->x, drop_parameters->y);
747       }
748
749
750         /****************************************************************/
751         /* Files dropped on a non-trash window -- move files to new     */
752         /* location.                                                    */
753         /*                                                              */
754         /* Droppable windows must be handled like the desktop; i.e.     */
755         /* positioning is supported.                                    */
756         /****************************************************************/
757       else
758       {
759          FileMgrRec *file_mgr_rec = (FileMgrRec *)file_mgr_data->file_mgr_rec;
760
761 /*
762          if ((file_mgr_data->show_type == SINGLE_DIRECTORY) &&
763              (file_mgr_data->view != BY_ATTRIBUTES))
764 */
765          {
766             if (file_mgr_data == (FileMgrData *) initiating_view)
767             {
768                DPRINTF(("DropOnFileWindow: Dragging and Dropping File within same window\n"));
769
770                   /* Simple reposition in the same window */
771                XmDropSiteStartUpdate(file_mgr_rec->file_window);
772                RepositionIcons(file_mgr_data,
773                                file_set,
774                                numFiles,
775                                drop_parameters->x, drop_parameters->y,
776                                False);
777                LayoutFileIcons(file_mgr_rec, file_mgr_data, False, True);
778                XmDropSiteEndUpdate(file_mgr_rec->file_window);
779             }
780             else
781             {
782                DPRINTF (("DropOnFileWindow: Dragging file(s) and dropping from other folders\n"));
783
784                CheckMoveType(file_mgr_data, (FileViewData *)NULL,
785                              (DirectorySet *)NULL, (DesktopRec *)NULL,
786                              file_set, host_set, modifiers,
787                              numFiles,
788                              drop_parameters->x, drop_parameters->y,
789                              NOT_DESKTOP);
790             }
791          }
792 /*
793          else
794          {
795             DPRINTF(("DropOnFileWindow: Not Single Directory View\n"));
796
797             if (FileMoveCopy (file_mgr_data,
798                               NULL, file_mgr_data->current_directory, file_mgr_data->host,
799                               host_set, file_set, numFiles,
800                               modifiers, NULL, NULL))
801             {
802                DirectorySet * directory_data;
803                char * directory_name;
804                FileViewData * file_view_data;
805                int j;
806
807                DeselectAllFiles (file_mgr_data);
808
809                directory_data = file_mgr_data->directory_set[0];
810                for (i = 0; i < numFiles; i++)
811                {
812                   directory_name = DName (file_set[i]);
813                   for (j = 0; j < directory_data->file_count; j++)
814                   {
815                      file_view_data = directory_data->file_view_data[j];
816                      if ( (file_view_data->filtered != True) &&
817                           (strcmp(directory_name,
818                                   file_view_data->file_data->file_name) == 0) )
819                      {
820                         SelectFile (file_mgr_data, file_view_data);
821                         break;
822                      }
823                   }
824                }
825
826                PositionFileView(file_view_data, file_mgr_data);
827             }
828          }
829 */
830
831          if (file_mgr_data->selected_file_count == 0)
832             ActivateNoSelect (file_mgr_rec);
833          else if (file_mgr_data->selected_file_count == 1)
834             ActivateSingleSelect
835                (file_mgr_rec,
836                 file_mgr_data->selection_list[0]->file_data->logical_type);
837          else
838             ActivateMultipleSelect (file_mgr_rec);
839       }
840    }
841
842       /*****************************/
843       /* Files dropped on an icon  */
844       /*****************************/
845    else
846    {
847       CheckMoveType(file_mgr_data, file_view_data,
848                     (DirectorySet *) file_view_data->directory_set,
849                     (DesktopRec *)NULL,
850                     file_set, host_set, modifiers,
851                     numFiles, drop_parameters->x, drop_parameters->y,
852                     NOT_DESKTOP_DIR);
853    }
854
855       /***************************/
856       /* free file and host sets */
857       /***************************/
858    _DtFreeDroppedFileInfo(numFiles, file_set, host_set);
859
860 }
861
862 /************************************************************************
863  *
864  * ProcessBufferDropOnFolder
865  *
866  ************************************************************************/
867
868 void
869 ProcessBufferDropOnFolder (
870      char *command,
871      FileMgrData *file_mgr_data,
872      FileViewData *file_view_data,
873      DtDndDropCallbackStruct *drop_parameters,
874      Widget drop_window)
875
876 {
877    unsigned int modifiers = NULL;
878    int num_of_buffers, i;
879    char ** file_set = NULL;
880    char ** host_set = NULL;
881    BufferInfo *buffer_set = NULL;
882    char  directory[MAX_PATH];
883
884
885
886
887    /***************************************************/
888    /* if no drop_parameters, or invalid params        */
889    /* then disallow the drop                          */
890    /***************************************************/
891    if (!drop_parameters)
892      return;
893
894    /* if dropped on file window and file_mgr_data is null */
895    if (drop_window && (file_mgr_data == NULL))
896      return;
897
898    /* if dropped on a folder icon and file_view_data */
899    /* is NULL, disallow the drop                     */
900    if (!drop_window && (file_view_data == NULL))
901      return;
902
903
904
905
906
907   /****************************************************/
908   /* extract file and host sets from drop parameters  */
909   /* @@@...need to check with Linda about how host_set*/
910   /* is being handled                                 */
911   /****************************************************/
912
913    num_of_buffers = drop_parameters->dropData->numItems;
914
915    /* Allocate memory for file and buffer structures */
916    file_set = (char **)XtMalloc(sizeof(char **) * num_of_buffers );
917    host_set = (char **)XtMalloc(sizeof(char **) * num_of_buffers);
918    buffer_set = (BufferInfo * )XtMalloc (sizeof (BufferInfo) * num_of_buffers);
919
920
921    _DtSetDroppedBufferInfo(file_set, buffer_set, host_set, drop_parameters);
922
923
924
925
926   /*****************************************************/
927   /* If buffers were dropped on the window, determine  */
928   /* which MODE (AS PLACED, GRID, TREE VIEW) and call  */
929   /* the appropriate routines to handle the creation   */
930   /* of the buffers into files. Assuming dropping      */
931   /* on non-trash windows.                             */
932   /*****************************************************/
933
934   if (drop_window)
935   {
936
937     /* Single directory view */
938     if ((file_mgr_data->show_type == SINGLE_DIRECTORY ) &&
939         (file_mgr_data->view != BY_ATTRIBUTES))
940
941     {
942
943       DPRINTF (("ProcessDropOnBufferFolder: Dropping buffers on single directory view: %s\n", file_mgr_data->current_directory));
944
945       G_dropx = drop_parameters->x;
946       G_dropy = drop_parameters->y;
947
948       /* Reposition Icons if in in "AS PLACED" Mode */
949       if (file_mgr_data -> positionEnabled == RANDOM_ON)
950       {
951 /*
952         RepositionIcons (file_mgr_data,
953                          file_set,
954                          num_of_buffers,
955                          drop_parameters->x, drop_parameters->y,
956                          True);
957 */
958       }
959
960       /* Call MakeFileFromBuffer */
961       MakeFilesFromBuffers(file_mgr_data, file_mgr_data->current_directory,
962                            file_mgr_data->host, file_set,
963                            host_set, buffer_set, num_of_buffers,
964                            NULL, NULL);
965
966     }
967     else
968     {
969       DPRINTF (("ProcessDropOnBufferFolder: Dropping buffers in Tree View\n"));
970
971       MakeFilesFromBuffers(file_mgr_data, file_mgr_data->current_directory,
972                            file_mgr_data->host, file_set,
973                            host_set, buffer_set, num_of_buffers,
974                            NULL, NULL);
975
976       /* Do Tree View Stuff      */
977       {
978          DirectorySet * directory_data;
979          char * directory_name;
980          FileViewData * file_view_data;
981          int j;
982
983          DeselectAllFiles (file_mgr_data);
984
985          directory_data = file_mgr_data->directory_set[0];
986          for (i = 0; i < num_of_buffers; i++)
987          {
988             directory_name = DName (file_set[i]);
989             for (j = 0; j < directory_data->file_count; j++)
990             {
991                file_view_data = directory_data->file_view_data[j];
992                if ( (file_view_data->filtered != True) &&
993                      (strcmp(directory_name,
994                      file_view_data->file_data->file_name) == 0) )
995                {
996                   SelectFile (file_mgr_data, file_view_data);
997                   break;
998                }
999             }
1000          }
1001          PositionFileView(file_view_data, file_mgr_data);
1002       }
1003    } /* endif for Tree View */
1004
1005   } /* endif drop buffers on window */
1006   else
1007   {
1008     /* Buffers were dropped on a Folder icon */
1009     /* Call MakeFileFromBuffer */
1010
1011
1012     DPRINTF(("ProcessBufferDropOnFolder...Buffers dropped on Folder icon %s\n",
1013              file_view_data ->file_data -> file_name));
1014
1015     if (file_mgr_data->show_type != SINGLE_DIRECTORY &&
1016         file_mgr_data->tree_root == file_view_data)
1017     {
1018         /* dropped on the top level folder in the tree view */
1019         sprintf (directory,"%s",file_mgr_data->current_directory);
1020     }
1021     else
1022         sprintf (directory,"%s/%s",file_mgr_data->current_directory,
1023                  file_view_data->file_data->file_name);
1024     DtEliminateDots(directory);
1025
1026     DPRINTF (("Copying buffer to %s\n", directory));
1027     MakeFilesFromBuffers(file_mgr_data, directory,
1028                          file_mgr_data->host, file_set,
1029                          host_set, buffer_set, num_of_buffers,
1030                          NULL, NULL);
1031
1032   }
1033
1034
1035   /***********************************/
1036   /* free file_set + buffer_set      */
1037   /***********************************/
1038   _DtFreeDroppedBufferInfo (file_set, buffer_set, host_set, num_of_buffers);
1039
1040
1041 }
1042
1043
1044 /************************************************************************
1045  *
1046  *  InvalidTrashDragDrop
1047  *
1048  ************************************************************************/
1049
1050 Boolean
1051 InvalidTrashDragDrop (
1052      int drag_op,
1053      int trash_context,
1054      Widget w)
1055 {
1056    Boolean rc = False;
1057
1058    if ( (drag_op == XmDROP_COPY) ||
1059         (drag_op == XmDROP_LINK) )
1060    {
1061       char *tmpStr, *title, *msg;
1062
1063       tmpStr = GETMESSAGE(18, 22, "Drag Error");
1064       title = XtNewString(tmpStr);
1065
1066       switch(trash_context)
1067       {
1068          case TO_TRASH:
1069          case WITHIN_TRASH:
1070             tmpStr = (GETMESSAGE(18,36, "You can't copy or link a file or folder out of the Trash Can.\nMove the object out of the Trash and put it into the File Manager.\nYou can then copy or link it from there."));
1071             break;
1072          case FROM_TRASH:
1073             tmpStr = (GETMESSAGE(18,37, "You can't copy or link a file or folder out of the Trash Can.\nMove the object out of the Trash and put it into the File Manager.\nYou can then copy or link it from there."));
1074             break;
1075          default:
1076             tmpStr = NULL;
1077             break;
1078       }
1079
1080       if (tmpStr)
1081       {
1082          msg = XtNewString(tmpStr);
1083          _DtMessage (w, title, msg, NULL, HelpRequestCB);
1084          XtFree(msg);
1085       }
1086
1087       XtFree(title);
1088
1089       rc = True;
1090    }
1091
1092    return(rc);
1093 }
1094
1095
1096 /************************************************************************
1097  *
1098  *  ProcessAction
1099  *
1100  ************************************************************************/
1101
1102 void
1103 ProcessAction (
1104      char *action,
1105      FileViewData *file_view_data,
1106      DtDndDropCallbackStruct *drop_parameters,
1107      char *cur_host,
1108      char *cur_dir,
1109      char *restricted_dir,
1110      Widget w)
1111
1112 {
1113    FileViewData *first_arg = NULL;
1114    DtActionArg * action_args = NULL;
1115    int arg_count = 0;
1116    char * pwd_host = NULL;
1117    char * pwd_dir = NULL;
1118    DirectorySet *directory_set;
1119    FileMgrData *file_mgr_data;
1120
1121    /* We don't want to execute the default action if in trash ...  */
1122    directory_set = (DirectorySet *) file_view_data->directory_set;
1123    file_mgr_data = (FileMgrData *) directory_set->file_mgr_data;
1124    if( trashFileMgrData != NULL
1125        && file_mgr_data == trashFileMgrData)
1126    {
1127       char *tmpStr, *title, *msg;
1128
1129       /* we don't want to execute the default action if in trash ...  */
1130       tmpStr = GETMESSAGE(27, 3, "Trash Can Error");
1131       title = XtNewString(tmpStr);
1132       tmpStr = GETMESSAGE(27, 105, "Default action of a trash object will not be executed.\n\nTo execute the default action of this object\n use 'Put Back' to return it to the File Manager\nthen execute it there.");
1133       msg = XtNewString(tmpStr);
1134
1135       _DtMessage(((FileMgrRec *)file_mgr_data->file_mgr_rec)->file_window,
1136                  title, msg, NULL, HelpRequestCB);
1137       XtFree(title);
1138       XtFree(msg);
1139       return;
1140    }
1141
1142    /* Build action arguments:
1143     *   First, test for redundant action information --
1144     *   file_view_data contains information for the object that the user
1145     *       a) dropped files on
1146     *       b) activated a popup menu over (note that the Actions piece
1147     *          of both the Selected and popup menus is only active when
1148     *          a single file is selected; therefore, this function doesn't
1149     *          deal with cases where multiple files are selected and a
1150     *          popup menu is activated
1151     *       c) doubled-clicked
1152     *
1153     *   If file_view_data contains information for the action that we
1154     *   are processing, then this information is not included as an
1155     *   argument to DtActionInvoke; otherwise, the information is
1156     *   included as the first argument to DtActionInvoke.
1157     */
1158    if ( !DtDtsDataTypeIsAction(file_view_data->file_data->logical_type) ||
1159         (strcmp(action, file_view_data->file_data->logical_type) != 0) ||
1160         (strcmp(action, file_view_data->file_data->file_name)    != 0) )
1161       first_arg = file_view_data;
1162
1163    if (drop_parameters)
1164    {
1165       if (drop_parameters->dropData->protocol == DtDND_FILENAME_TRANSFER)
1166          _DtBuildActionArgsWithDroppedFiles(first_arg, drop_parameters,
1167                                             &action_args, &arg_count);
1168       else
1169          _DtBuildActionArgsWithDroppedBuffers(first_arg, drop_parameters,
1170                                               &action_args, &arg_count);
1171    }
1172    else
1173    {
1174       if (first_arg)
1175          _DtBuildActionArgsWithSelectedFiles(&first_arg, 1,
1176                                              &action_args, &arg_count);
1177    }
1178
1179
1180    /* Retrieve context dir for action -- in the case of toolboxes, the
1181       root toolbox */
1182    SetPWD(cur_host, cur_dir, &pwd_host, &pwd_dir, restricted_dir);
1183
1184
1185    /* Turn on hour glass */
1186    _DtTurnOnHourGlass(w);
1187
1188
1189    /* Invoke action */
1190    DtActionInvoke(w, action, action_args, arg_count,
1191                   NULL, NULL, pwd_dir, True, NULL, NULL);
1192
1193
1194    /* Add timer event to turn off hour glass */
1195    XtAppAddTimeOut(XtWidgetToApplicationContext(w), 1500,
1196                    (XtTimerCallbackProc) TimerEvent, (XtPointer) w);
1197
1198
1199    XtFree(pwd_host);
1200    XtFree(pwd_dir);
1201    _DtFreeActionArgs(action_args, arg_count);
1202 }
1203
1204
1205 /************************************************************************
1206  *
1207  *  TimerEvent
1208  *      This function is called when dtfile does an _DtActionInvoke. All
1209  *      it does is turn off the Hourglass cursor.
1210  *
1211  ************************************************************************/
1212
1213 static void
1214 TimerEvent(
1215         Widget widget,
1216         XtIntervalId *id )
1217 {
1218    _DtTurnOffHourGlass (widget);
1219 }
1220
1221 /************************************************************************
1222  *
1223  * This Handle's the FILESYSTEM_MOVE, FILESYSTEM_COPY, or FILESYSTEM_LINK
1224  * ToolTalk messages.  The operation type is passed in via opType. It is
1225  * either MOVE_FILE, COPY_FILE, or LINK_FILE.  Arg 0 of the ToolTalk message
1226  * contains the folder the operation is taking place TO and arg 1 contains
1227  * the files that the operation  is happening to.
1228  *
1229  ************************************************************************/
1230
1231 void
1232 MoveCopyLinkHandler(
1233                     Tt_message ttMsg,
1234                     int opType)
1235 {
1236    struct stat fileInfo;
1237    char title[256];
1238    int numArgs, i;
1239    char *ptr, *toName, *fileNames = NULL, *type = NULL, *fileList;
1240    char *files = NULL;
1241    char ** file_set = NULL;
1242    char ** host_set = NULL;
1243    unsigned int modifiers = NULL;
1244    int file_count = 0;
1245    int file_set_size = 0;
1246    int errorCount = 0;
1247
1248    toName = tt_message_file( ttMsg );
1249    fileNames = fileList = tt_message_arg_val( ttMsg, 1 );
1250
1251    if( tt_is_err( tt_ptr_error( toName ) ) )
1252    { /* No file name */
1253       tt_message_reply( ttMsg );
1254       tttk_message_destroy( ttMsg );
1255       return;
1256    }
1257
1258    /* let's loop through the fileName passed to get the files which the
1259     * operation is to happen on.  The file's are separated by spaces.  What
1260     * happens if a file has a space in it: We parse on the spaces and if the
1261     * next char after a space is a '/' then we assume that is the end of the
1262     * file name.  What this implies is that this won't work for files with
1263     * spaces at the end of the name
1264     */
1265    while(1)
1266    {
1267       /* build the arrary of char pointer's */
1268       if (file_count == file_set_size)
1269       {
1270          file_set_size += 10;
1271          file_set =
1272            (char **) XtRealloc ((char *)file_set,
1273                                 sizeof (char **) * file_set_size);
1274       }
1275
1276       /* find the next space */
1277       ptr = DtStrchr(fileList, ' ');
1278
1279       /* if ptr is NULL, we have our last file name (no spaces found) */
1280       if(ptr == NULL)
1281       {
1282          file_set[file_count] = XtNewString(fileList);
1283          file_count++;
1284          break;
1285       }
1286       else
1287       {
1288          /* Let's check if the next char is a '/'. If it is then we know it's
1289           * the next file name, else the space found is part of a filename.
1290           */
1291          if(ptr[1] == '/')
1292          {
1293             *ptr = '\0';
1294             file_set[file_count] = XtNewString(fileList);
1295             file_count++;
1296          }
1297          fileList = ptr+1;
1298       }
1299    }
1300
1301
1302   /* go clean up all the '.' and '..' */
1303    for(i = 0; i < file_count; i++)
1304       DtEliminateDots( file_set[i] );
1305    DtEliminateDots( toName );
1306
1307   /* Set up the modifier key's */
1308    if( opType == MOVE_FILE)
1309       modifiers = NULL;
1310    else if ( opType == COPY_FILE)
1311       modifiers = ControlMask;
1312    else
1313       modifiers = ShiftMask;
1314
1315
1316    /*
1317     * Let's check make sure the Folder the operation is happening to exists.
1318     */
1319    if( stat( toName, &fileInfo ) != 0
1320        && lstat( toName, &fileInfo ) != 0 )
1321    { /* to directory does not exist */
1322       char *dialogTitle;
1323
1324       if(opType == MOVE_FILE)
1325       {
1326          dialogTitle = XtNewString(GETMESSAGE(33, 3, "Move Object Error"));
1327          sprintf(title, GETMESSAGE(33, 6, "The location you are trying to Move to:\n\n   %s\n\ndoes not exist in the file system."), toName);
1328       }
1329       else if(opType == COPY_FILE)
1330       {
1331          dialogTitle = XtNewString(GETMESSAGE(33, 4, "Copy Object Error"));
1332          sprintf(title, GETMESSAGE(33, 7, "The location you are trying to Copy to:\n\n   %s\n\ndoes not exist in the file system."), toName);
1333       }
1334       else
1335       {
1336          dialogTitle = XtNewString(GETMESSAGE(33, 5, "Link Object Error"));
1337          sprintf(title, GETMESSAGE(33, 8, "The location you are trying to Link to:\n\n   %s\n\ndoes not exist in the file system."), toName);
1338       }
1339       _DtMessage( toplevel, dialogTitle, title, NULL, HelpRequestCB );
1340
1341       tt_free( toName );
1342       tt_message_reply( ttMsg );
1343       tttk_message_destroy( ttMsg );
1344       return;
1345    }
1346
1347
1348    /*
1349     * Let's check make sure the object the operation is happening to is a
1350     * Folder.
1351     */
1352    if ((fileInfo.st_mode & S_IFMT) != S_IFDIR)     /* target not directory */
1353    { /* File that the user is doing the operation to is not a directory */
1354       char *dialogTitle;
1355
1356       if(opType == MOVE_FILE)
1357       {
1358          dialogTitle = XtNewString(GETMESSAGE(33, 3, "Move Object Error"));
1359          sprintf(title, GETMESSAGE(33, 9, "The location you are trying to Move to:\n\n   %s\n\nis not a folder."), toName);
1360       }
1361       else if(opType == COPY_FILE)
1362       {
1363          dialogTitle = XtNewString(GETMESSAGE(33, 4, "Copy Object Error"));
1364          sprintf(title, GETMESSAGE(33, 10, "The location you are trying to Copy to:\n\n   %s\n\nis not a folder."), toName);
1365       }
1366       else
1367       {
1368          dialogTitle = XtNewString(GETMESSAGE(33, 5, "Link Object Error"));
1369          sprintf(title, GETMESSAGE(33, 11,"The location you are trying to Link to:\n\n   %s\n\nis not a folder."), toName);
1370       }
1371       _DtMessage( toplevel, dialogTitle, title, NULL, HelpRequestCB );
1372
1373       tt_free( toName );
1374       tt_message_reply( ttMsg );
1375       tttk_message_destroy( ttMsg );
1376       return;
1377    }
1378
1379    /*
1380     *
1381     *
1382     */
1383    for(i = 0; i < file_count; i++)
1384    {
1385       if( stat( file_set[i], &fileInfo ) != 0
1386           && lstat( file_set[i], &fileInfo ) != 0 )
1387       { /* File does not exist */
1388          char *dialogTitle;
1389
1390          if(opType == MOVE_FILE)
1391          {
1392             dialogTitle = XtNewString(GETMESSAGE(33, 3, "Move Object Error"));
1393             if(file_count == 1)
1394                sprintf(title, GETMESSAGE(33, 12, "The object you are trying to Move:\n\n   %s\n\ndoes not exist in the file system."), file_set[i]);
1395             else
1396                sprintf(title, GETMESSAGE(33, 13, "One of the objects you are trying to Move:\n\n   %s\n\ndoes not exist in the file system.\nNot Moving any of them."), file_set[i]);
1397          }
1398          else if(opType == COPY_FILE)
1399          {
1400             dialogTitle = XtNewString(GETMESSAGE(33, 4, "Copy Object Error"));
1401             if(file_count == 1)
1402                sprintf(title, GETMESSAGE(33, 14, "The object you are trying to Copy:\n\n   %s\n\ndoes not exist in the file system."), file_set[i]);
1403             else
1404                sprintf(title, GETMESSAGE(33, 15, "One of the objects you are trying to Copy:\n\n   %s\n\ndoes not exist in the file system.\nNot Copying any of them."), file_set[i]);
1405          }
1406          else
1407          {
1408             dialogTitle = XtNewString(GETMESSAGE(33, 5, "Link Object Error"));
1409             if(file_count == 1)
1410                sprintf(title, GETMESSAGE(33, 16, "The object you are trying to Link:\n\n   %s\n\ndoes not exist in the file system."), file_set[i]);
1411             else
1412                sprintf(title, GETMESSAGE(33, 17, "One of the objects you are trying to Link:\n\n   %s\n\ndoes not exist in the file system.\nNot Linking any of them."), file_set[i]);
1413          }
1414          _DtMessage( toplevel, dialogTitle, title, NULL, HelpRequestCB );
1415
1416          tt_free( toName );
1417          tt_message_reply( ttMsg );
1418          tttk_message_destroy( ttMsg );
1419          return;
1420       }
1421    }
1422
1423
1424    /* set all the hosts to the home host name since ToolTalk messages pass
1425     * the filenames in host relative paths (Is this correct?)
1426     */
1427    host_set = (char **)XtMalloc(sizeof(char *) * file_count);
1428    for(i = 0; i < file_count; i++)
1429       host_set[i] = home_host_name;
1430
1431    /* Go do the Move/Copy/Link... this function will do proper error checking */
1432    FileMoveCopy(NULL, NULL, toName, home_host_name,
1433                 host_set, file_set, file_count,
1434                 modifiers, NULL, NULL);
1435
1436    tt_free( toName );
1437    tt_message_reply( ttMsg );
1438    tttk_message_destroy( ttMsg );
1439    return;
1440 }
1441