dtcm: Resolve CID 87408
[oweals/cde.git] / cde / programs / dtcm / dtcm / dtcm_editor.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $TOG: dtcm_editor.c /main/14 1999/09/20 10:32:17 mgreess $ */
24 /*
25  *  (c) Copyright 1993, 1994 Hewlett-Packard Company
26  *  (c) Copyright 1993, 1994 International Business Machines Corp.
27  *  (c) Copyright 1993, 1994 Novell, Inc.
28  *  (c) Copyright 1993, 1994 Sun Microsystems, Inc.
29  */
30
31 #include <EUSCompat.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <unistd.h>
35 #include <Xm/XmAll.h>
36 #include <Dt/EnvControlP.h>
37 #include <Dt/Dnd.h>
38 #include <Dt/Icon.h>
39 #include <Dt/UserMsg.h>
40 #include <Tt/tttk.h>
41 #include <csa.h>
42 #include "calendar.h"
43 #include "props.h"
44 #include "props_pu.h"
45 #include "dssw.h"
46 #include "rfp.h"
47 #include "dnd.h"
48 #include "util.h"
49 #include "cm_tty.h"
50 #include <nl_types.h>
51 #include <locale.h>
52 #if !defined(NL_CAT_LOCALE)
53 #define NL_CAT_LOCALE       0
54 #endif
55
56 int debug = 0;
57 static Tt_message load_cb();
58
59 static const char *ptype = "Dt_AppointmentEditor";
60
61 typedef enum {no_tt, file_tt, buffer_tt} Dtcm_editor_start;
62
63 /* Absolute value macro */
64 #ifndef ABS
65 #define ABS(x) (((x) > 0) ? (x) : (-(x)))
66 #endif
67  
68
69 #include "drag_xbm"
70 #include "drag_mask_xbm"
71
72 /*
73 **  Structure for stand-alone editor
74 */
75 typedef struct {
76         Widget          attach_button;
77         Calendar        *c;
78         DSSW            *dssw;
79         char            *file;
80         char            *vtype;
81         Dtcm_editor_start init;
82         Boolean         read_only;
83         Tt_pattern      *contract_pats;
84         Tt_message      contract;
85         Widget          form;
86         Props           *p;
87         Props_pu        *pu;
88         Widget          reset_button;
89         RFP             *rfp;
90         Widget          top_level;
91         Boolean         modified;
92         Dtcm_appointment *orig_appt;
93         Widget          drag_source;
94         int             initialX;
95         int             initialY;
96         Boolean         doing_drag;
97         Widget          drag_icon;
98         Pixmap          drag_bitmap;
99         Pixmap          drag_mask;
100         int             dsswFlags;
101         int             rfpFlags;
102 } DTCM_editor;
103
104 static void de_apply_proc(Widget , XtPointer , XtPointer );
105
106 /*
107 **  Hate like hell to make this global, but I can find no way to get at it
108 **  when I need it in other methods ...
109 **
110 **  Also, help uses a global reference to calendar which hoses us ...
111 */
112 XtAppContext    app;
113 Calendar        *calendar;
114
115 static void
116 de_mark_change(Widget w, XtPointer data, XtPointer cbs) {
117  
118         DTCM_editor        *de = (DTCM_editor *)data;
119
120         de->modified = True;
121
122         if ((w == de->dssw->start_text) ||
123             (w == de->dssw->start_am) ||
124             (w == de->dssw->start_pm) ||
125             (w == de->dssw->stop_text) ||
126             (w == de->dssw->stop_am) ||
127             (w == de->dssw->stop_pm))
128           de->dsswFlags = 0;
129
130         if (w == de->rfp->repeat_menu)
131           de->rfpFlags = 0;
132 }
133
134 static void
135 merge_old_values(Dtcm_appointment *original, Dtcm_appointment *new) {
136
137         /* This routine takes in the original appointment structure 
138            from the edited appointment, and merges back in any attributes 
139            that aren't controlled by our editor, so that when the 
140            appointment is written back out to the buffering agent, those 
141            attributes are not lost. */
142
143         int     source_count, dest_count, new_attrs;
144         int     dest_attr_num = new->count;
145         Boolean found;
146
147         if (original == NULL)
148                 return;
149         
150         /* We need to see how much larger the attribute array needs to 
151            get.  For each of the guys in the source attribute array, 
152            we need to look and see if it's already a member of the 
153            destination array.  Those that don't exist, we count, and 
154            later move. */
155
156         for (source_count = 0, new_attrs = 0; source_count < original->count; source_count++) {
157                 found = False;
158                 for (dest_count = 0; dest_count < dest_attr_num; dest_count++) {
159                         if (original->attrs[source_count].name && new->attrs[dest_count].name) {
160                                 if (strcmp(original->attrs[source_count].name, new->attrs[dest_count].name) == 0) {
161                                         found = True;
162                                         break;
163                                 }
164                         }
165                 }
166
167                 if (found == False)
168                         new_attrs++;
169         }
170
171         if (new_attrs == 0)
172                 return;
173
174         /* realloc the attrs array to be large enough to accomodate the new 
175            attibutes */
176
177         new->attrs = (CSA_attribute *) realloc(new->attrs, (dest_attr_num + new_attrs) * sizeof(CSA_attribute));
178
179         /* Copy in the new attribute values */
180         for (source_count = 0; source_count < original->count; source_count++) {
181                 found = False;
182                 for (dest_count = 0; dest_count < new->count; dest_count++) {
183                         if (original->attrs[source_count].name && new->attrs[dest_count].name) {
184                                 if (strcmp(original->attrs[source_count].name, new->attrs[dest_count].name) == 0) {
185                                         found = True;
186                                         break;
187                                 }
188                         }
189                 }
190
191                 if (found == False)
192                 {
193                         new->attrs[new->count] = original->attrs[source_count];
194                         new->count++;
195                 }
196         }
197
198         /* reset the appointment links, as the old ones have been 
199            thrashed by reallocing the attribute array. */
200
201         set_appt_links(new);
202 }
203
204 Widget
205 CreateDragSourceIcon(
206         Widget          widget,
207         Pixmap          pixmap,
208         Pixmap          mask)
209 {
210         Widget          dragIcon;
211         Window          rootWindow;
212         int             pixmapX, pixmapY;
213         unsigned int    pixmapWidth, pixmapHeight, pixmapBorder, pixmapDepth;
214         Arg             args[20];
215         Cardinal        nn = 0;
216  
217         XGetGeometry (XtDisplayOfObject(widget), pixmap, &rootWindow,
218                 &pixmapX, &pixmapY, &pixmapWidth, &pixmapHeight,
219                 &pixmapBorder, &pixmapDepth);
220  
221         XtSetArg(args[nn], XmNwidth, pixmapWidth);  nn++;
222         XtSetArg(args[nn], XmNheight, pixmapHeight);  nn++;
223         XtSetArg(args[nn], XmNmaxWidth, pixmapWidth);  nn++;
224         XtSetArg(args[nn], XmNmaxHeight, pixmapHeight);  nn++;
225         XtSetArg(args[nn], XmNpixmap, pixmap);  nn++;
226         XtSetArg(args[nn], XmNmask, mask);  nn++;
227         XtSetArg(args[nn], XmNdepth, pixmapDepth);  nn++;
228         dragIcon = XmCreateDragIcon(widget, "sourceIcon", args, nn);
229
230         return(dragIcon);
231 }
232
233 /*
234  * getIcon
235  *
236  * Returns a new IconInfo structure with bitmap, mask, width, height,
237  * icon type and name.
238  */
239 static void
240 GetIcon(DTCM_editor *de)
241 {
242  
243         Display        *display = XtDisplay(calendar->frame);
244         Window          window = XtWindow(calendar->frame);
245         unsigned char  *bitmapData, *bitmapMask;
246  
247         if (de->drag_bitmap == 0) {
248                 de->drag_bitmap = XCreateBitmapFromData(display,
249                         window, (char *) drag_xbm_bits,
250                         drag_xbm_width, drag_xbm_height);
251                 if (de->drag_bitmap == 0) {
252
253                         printf("%s", catgets(calendar->DT_catd, 1, 237, "XCreateBitmapFromData() failed for bitmap.\n"));
254                         return;
255                 }
256         }
257         if (de->drag_mask == 0) {
258                 de->drag_mask = XCreateBitmapFromData(display,
259                         window, (char *) drag_mask_xbm_bits,
260                         drag_mask_xbm_width, drag_mask_xbm_height);
261                 if (de->drag_mask == 0) {
262                         printf("%s", catgets(calendar->DT_catd, 1, 238, "XCreateBitmapFromData() failed for mask.\n"));
263                         return;
264                 }
265         }
266 }
267  
268  
269 /*
270  * DragFinishCB
271  *
272  * Resets drag state to indicate the drag is over. Free memory allocated
273  * with the drag.
274  */
275 static void
276 DragFinishCB(
277         Widget          widget,
278         XtPointer       clientData,
279         XtPointer       callData)
280 {
281         DragContext     *context = (DragContext *) clientData;
282
283         if (!context)
284                 return;
285
286         if (context->editor_type == StandAloneEditor)
287             ((DTCM_editor *) context->editor)->doing_drag = False;
288
289         if (context->data)
290             free(context->data);
291
292         free(context);
293 }
294
295 /*
296  * ApptConvertCB
297  *
298  * Fills in data object with calendar appointment string based on which
299  * appointment in the list was under the pointer when the drag started.
300  */
301 static void
302 ApptConvertCB(
303         Widget          dragContext,
304         XtPointer       clientData,
305         XtPointer       callData)
306 {
307         DtDndConvertCallbackStruct *convertInfo 
308                                         = (DtDndConvertCallbackStruct*)callData;
309         DtDndBuffer     *data           = &(convertInfo->dragData->data.buffers[0]);
310         DragContext     *context        = (DragContext *)clientData;
311         Display         *display        = XtDisplay(dragContext);
312         Atom            CMAPPOINTMENT   
313                         = XmInternAtom(display, "CalendarAppointment", False);
314         Calendar        *c = context->calendar;
315
316         if (convertInfo->reason != DtCR_DND_CONVERT_DATA)
317                 return;
318
319         /* REMIND: Need to check convertInfo->reason, handle DELETE, etc */
320
321         data->bp   = XtNewString(context->data);
322         data->size = strlen(data->bp);
323         data->name = XtNewString(catgets(c->DT_catd, 1, 236, "CalendarAppointment"));
324 }
325
326 void
327 StandaloneApptDragStart(
328         Widget          widget,
329         XEvent          *event,
330         DTCM_editor     *de,
331         EditorType      editor_type)
332 {
333         static XtCallbackRec convertCBRec[] = { {ApptConvertCB, NULL},
334                                                 {NULL, NULL} };
335         static XtCallbackRec dragFinishCBRec[] =  { {DragFinishCB, NULL},
336                                                     {NULL, NULL} };
337  
338         Display        *display         = XtDisplay(widget);
339         int             itemCount, selectedPos;
340         DragContext     *context = calloc(sizeof(DragContext), 1);
341         Calendar        *c = de->c;
342         Dtcm_appointment        *appt;
343         int             old_attr_count;
344         char            *apptstr;
345         int             preDsswFlags, preRfpFlags;
346
347         /* Convert appointment into string.  If not successful, don't start drag. */
348         appt = allocate_appt_struct(appt_write, DATAVER_ARCHIVE, NULL);
349         load_appt_defaults(appt, de->p);
350         preDsswFlags = de->dsswFlags;
351         preRfpFlags = de->rfpFlags;
352         if (!dssw_form_flags_to_appt(de->dssw, appt, de->c->calname,
353                                      now(), &de->dsswFlags) ||
354             !rfp_form_flags_to_appt(de->rfp, appt, de->c->calname,
355                                     &de->rfpFlags) ||
356             (preDsswFlags != de->dsswFlags) ||
357             (preRfpFlags != de->rfpFlags))
358         {
359             de->doing_drag = False;
360             free_appt_struct(&appt);
361             free(context);
362             return;
363         }
364
365         /* save the old count of attributes so that when this appointment 
366            gets freed, we can prevent the added attribute references from 
367            being freed. */
368
369         old_attr_count = appt->count;
370         merge_old_values(de->orig_appt, appt);
371         apptstr = parse_attrs_to_string(appt, de->p,
372                                         attrs_to_string(appt->attrs, appt->count));
373         appt->count = old_attr_count;
374         free_appt_struct(&appt);
375
376         context->data = apptstr;
377         context->calendar = c;
378         context->editor_type = editor_type;
379         context->editor = (caddr_t) de;
380  
381         GetIcon(de);
382  
383         convertCBRec[0].closure = (XtPointer)context;
384         dragFinishCBRec[0].closure = (XtPointer)context;
385  
386         if (de->drag_icon == NULL) {
387                 de->drag_icon = CreateDragSourceIcon(widget, de->drag_bitmap, de->drag_mask);
388         }
389  
390         if (DtDndVaDragStart(widget, event, DtDND_BUFFER_TRANSFER, 1,
391                 XmDROP_COPY, 
392                 convertCBRec, dragFinishCBRec,
393                 DtNsourceIcon,          de->drag_icon,
394                 NULL)
395             == NULL) {
396  
397                 printf("%s", catgets(c->DT_catd, 1, 239, "DragStart returned NULL.\n"));
398         }
399 }
400
401 /*
402  * dragMotionHandler
403  *
404  * Determine if the pointer has moved beyond the drag threshold while button 1
405  * was being held down.
406  */
407 static void
408 EditApptDragMotionHandler(
409         Widget          dragInitiator,
410         XtPointer       clientData,
411         XEvent         *event)
412 {
413         int             diffX, diffY;
414         DTCM_editor     *de = (DTCM_editor *) clientData;
415         Calendar        *c = de->c;
416         Dimension       source_height, source_width;
417         Position        source_x, source_y;
418  
419         if (!de->doing_drag) {
420
421                 /* check to see if the iniital value was within the
422                    bounds for the drag source icon. */
423  
424                 XtVaGetValues(de->drag_source,
425                                 XmNx, &source_x,
426                                 XmNy, &source_y,
427                                 XmNheight, &source_height,
428                                 XmNwidth, &source_width,
429                                 NULL);
430  
431                 if ((event->xmotion.x < source_x) ||
432                     (event->xmotion.y < source_y) ||
433                     (event->xmotion.x > (int) (source_x + source_width)) ||
434                     (event->xmotion.y > (int) (source_y + source_height)))
435                         return;
436  
437
438                 /*
439                  * If the drag is just starting, set initial button down coords
440                  */
441                 if (de->initialX == -1 && de->initialY == -1) {
442                         de->initialX = event->xmotion.x;
443                         de->initialY = event->xmotion.y;
444                 }
445                 /*
446                  * Find out how far pointer has moved since button press
447                  */
448                 diffX = de->initialX - event->xmotion.x;
449                 diffY = de->initialY - event->xmotion.y;
450  
451                 if ((ABS(diffX) >= DRAG_THRESHOLD) ||
452                     (ABS(diffY) >= DRAG_THRESHOLD)) {
453                         de->doing_drag = True;
454                         StandaloneApptDragStart(dragInitiator, event, de, StandAloneEditor);
455                         de->initialX = -1;
456                         de->initialY = -1;
457                 }
458         }
459 }
460
461 /*
462 **  Static callbacks
463 */
464 static void
465 de_quit_handler(Widget w, XtPointer cdata, XtPointer data) {
466         DTCM_editor *de = (DTCM_editor *)cdata;
467         int answer;
468
469         if (de->modified == True) {
470                 char *title = XtNewString(catgets(de->c->DT_catd, 1, 1008, "Calendar Appointment : Help"));
471                 char *text = XtNewString(catgets(de->c->DT_catd, 1, 451, "You have made unsaved changes.\nYou may save your changes, discard your changes, \nor return to your previous place in the dialog."));
472                 char *ident1 = XtNewString(catgets(de->c->DT_catd, 1, 452, "Save"));
473                 char *ident2 = XtNewString(catgets(de->c->DT_catd, 1, 700, "Discard"));
474                 char *ident3 = XtNewString(catgets(de->c->DT_catd, 1, 923, "Cancel"));
475                 answer = dialog_popup(de->top_level,
476                         DIALOG_TITLE, title,
477                         DIALOG_TEXT, text,
478                         BUTTON_IDENT, 1, ident1,
479                         BUTTON_IDENT, 2, ident2,
480                         BUTTON_IDENT, 3, ident3,
481                         DIALOG_IMAGE, de->pu->xm_warning_pixmap,
482                         NULL);
483                 XtFree(ident3);
484                 XtFree(ident2);
485                 XtFree(ident1);
486                 XtFree(text);
487                 XtFree(title);
488
489                 if (answer == 1) {
490                         de_apply_proc(NULL, (XtPointer)de, NULL);
491                         XtPopdown(de->top_level);
492                 }
493                 else  if (answer == 2) {
494                         if (de->contract)
495                                 tttk_message_fail(de->contract, TT_DESKTOP_ECANCELED, NULL, True);
496                         XtPopdown(de->top_level);
497                 }
498                 else  if (answer == 3) {
499                         return;
500                 }
501
502         }
503         else {
504                 if (de->contract)
505                         tttk_message_fail(de->contract, TT_DESKTOP_ECANCELED, NULL, True);
506                 XtPopdown(de->top_level);
507         }
508
509         free(de->c);
510         free(de->dssw);
511         if (de->file)
512                 free(de->file);
513         free(de->p);
514         free(de->pu);
515         free(de->rfp);
516         free(de);
517         exit(0);
518 }
519
520 static void
521 load_from_file(DTCM_editor *de) {
522         int                     i;
523         CmDataList              *list = NULL;
524         Dtcm_appointment        *appt;
525
526         list = CmDataListCreate();
527         parse_appt_from_file(de->c->DT_catd, de->file, list, de->p, 
528                              query_user, de->c, DATAVER_ARCHIVE);
529         if (appt = (Dtcm_appointment *)CmDataListGetData(list, 1)) {
530                 dssw_attrs_to_form(de->dssw, appt);
531                 rfp_attrs_to_form(de->rfp, appt);
532         }
533         for (i = 1; i <= list->count; i++)
534                 if (appt = (Dtcm_appointment *)
535                     CmDataListGetData(list, i)) {
536                         if (de->orig_appt)
537                                 free_appt_struct(&de->orig_appt);
538
539                         de->orig_appt = appt;
540                 }
541         CmDataListDestroy(list, B_FALSE);
542         de->dsswFlags = de->rfpFlags = 0;
543 }
544
545 static void
546 de_set_defaults(DTCM_editor *de) {
547
548         if (de->file && (access(de->file, R_OK) == 0)) {
549                 load_from_file(de);
550         } else {
551                 set_dssw_defaults(de->dssw, now(), True);
552                 set_rfp_defaults(de->rfp);
553                 de->dsswFlags = de->rfpFlags = 0;
554         }
555
556         de->modified = False;
557 }
558
559 static void
560 de_apply_proc(Widget w, XtPointer client_data, XtPointer data) {
561         char                    *str;
562         FILE                    *fp;
563         DTCM_editor             *de = (DTCM_editor *)client_data;
564         Dtcm_appointment        *appt;
565         int                     old_attr_count;
566         Display                 *dpy = XtDisplayOfObject(w);
567
568         appt = allocate_appt_struct(appt_write, 
569                                         DATAVER_ARCHIVE,
570                                         NULL);
571         load_appt_defaults(appt, de->p);
572         de->dsswFlags = 0;
573         dssw_form_to_appt(de->dssw, appt, de->c->calname, now());
574         de->rfpFlags = 0;
575         rfp_form_to_appt(de->rfp, appt, de->c->calname);
576
577         /* save the old count of attributes so that when this appointment 
578            gets freed, we can prevent the added attribute references from 
579            being freed. */
580
581         old_attr_count = appt->count;
582         merge_old_values(de->orig_appt, appt);
583         str = parse_attrs_to_string(appt, de->p, attrs_to_string(appt->attrs, appt->count));
584         appt->count = old_attr_count;
585         de->modified = False;
586
587         if (!str)
588                 return;
589
590         /* we need to distinguish between whether the data is being 
591            saved as part of application termination, or as part of 
592            pressing the "Apply" button.  If it's is app termination, 
593            we need to call ttmedia_load_reply, and if it is an 
594            intermediate save, we are supposed to call ttmedia_Deposit.  
595            Hmmph.  We also need to be pretty clear about saving it back 
596            in the same fashion is was supplied.  If it came as part of 
597            a buffer, it needs to go back that way. */
598
599
600         /* The first case is from application exit */
601
602         if (w == NULL) {
603
604                 /* if it was a file transfer, we should write out the 
605                    file, and set the buffer to null.  If it came from 
606                    a buffer, we don't bother to write out the file. */
607
608                 if (de->read_only) {
609                         ttmedia_load_reply(de->contract, NULL, 0, True);
610                 }
611                 else {
612                         if (de->init == file_tt) {
613                                 fp = fopen(de->file, "w");
614
615                                 if (!fp) {
616                                         XBell(dpy, 50);
617                                         return;
618                                 }
619
620                                 fprintf(fp, "%s", str);
621                                 fclose(fp);
622                                 ttmedia_load_reply(de->contract, NULL, 0, True);
623                         }
624                         else {
625                                 ttmedia_load_reply(de->contract, (unsigned char *) str, strlen(str), True);
626                         }
627                 }
628         }
629         else {
630
631                 /* This case is from the "Apply" button.  In this case, 
632                    if the app was started normally (no tt), then we 
633                    just write out the file.  If it was from tt, we then 
634                    use ttmedia_Deposit to send the data back. */
635
636                 if ((de->init == no_tt) || (de->init == file_tt)) {
637                         fp = fopen(de->file, "w");
638
639                         if (!fp) {
640                                 XBell(dpy, 50);
641                                 return;
642                         }
643
644                         fprintf(fp, "%s", str);
645                         fclose(fp);
646
647                         if (de->init == file_tt)
648                                 ttmedia_Deposit(de->contract,
649                                                 NULL,
650                                                 de->vtype,
651                                                 NULL,
652                                                 0,
653                                                 de->file,
654                                                 app,
655                                                 30000);
656                 }
657                 else {
658                         ttmedia_Deposit(de->contract,
659                                         NULL,
660                                         de->vtype,
661                                         (const unsigned char *)str,
662                                         strlen(str),
663                                         NULL,
664                                         app,
665                                         30000);
666                 }
667
668         }
669
670         free(str);
671         free_appt_struct(&appt);
672 }
673
674 static void
675 de_reset_proc(Widget w, XtPointer client_data, XtPointer data) {
676         DTCM_editor     *de = (DTCM_editor *)client_data;
677
678         de_set_defaults(de);
679         de->dsswFlags = de->rfpFlags = 0;
680 }
681
682 static void
683 display_command_usage() {
684         fprintf(stderr, "\ndtcm_editor Usage:  dtcm [filename]\n\n");
685         exit(0);
686 }
687
688 static void
689 OKCB (Widget dialog, XtPointer client_data, XtPointer call_data)
690 {
691     XtUnmanageChild((Widget) client_data);
692 }
693
694 void
695 DieFromToolTalkError(DTCM_editor *de, char *errfmt, Tt_status status)
696 {
697     Arg          args[10];
698     Widget       dialog, dialogShell;
699     char        *errmsg, *statmsg, *title;
700     XmString     xms_errmsg, xms_ok, xms_title;
701     int          n;
702
703     if (! tt_is_err(status)) return;
704
705     statmsg = tt_status_message(status);
706     errmsg = XtMalloc(strlen(errfmt) + strlen(statmsg) + 2);
707     sprintf(errmsg, errfmt, statmsg);
708
709     xms_ok = XmStringCreateLocalized(catgets(de->c->DT_catd, 2, 3, "OK"));
710     xms_errmsg = XmStringCreateLocalized(errmsg);
711     xms_title = XmStringCreateLocalized(catgets(de->c->DT_catd, 2, 4,
712                         "Calendar : Appointment Editor - Warning"));
713
714     n = 0;
715     XtSetArg(args[n], XmNautoUnmanage, False); n++;
716     XtSetArg(args[n], XmNokLabelString, xms_ok); n++;
717     XtSetArg(args[n], XmNdialogTitle, xms_title); n++;
718     XtSetArg(args[n], XmNmessageString, xms_errmsg); n++;
719     XtSetArg(args[n], XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL); n++;
720     XtSetArg(args[n], XmNdialogType, XmDIALOG_WARNING); n++;
721
722     dialog = XmCreateMessageDialog(de->top_level, "IconEditorError", args, n);
723     XtAddCallback(dialog, XmNokCallback, OKCB, (XtPointer) dialog);
724     XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_CANCEL_BUTTON));
725     XtUnmanageChild(XmMessageBoxGetChild(dialog, XmDIALOG_HELP_BUTTON));
726
727     /*
728      * Disable the frame menu from dialog since we don't want the user
729      * to be able to close dialogs with the frame menu
730      */
731     dialogShell = XtParent(dialog);
732     n = 0;
733     XtSetArg(args[n], XmNmwmDecorations, MWM_DECOR_ALL | MWM_DECOR_MENU); n++;
734     XtSetValues(dialogShell, args, n);
735     XtManageChild(dialog);
736     XtRealizeWidget(dialogShell);
737
738     _DtSimpleError("Dtcm", DtWarning, NULL, errmsg);
739
740     XtFree(errmsg);
741     XmStringFree(xms_ok);
742     XmStringFree(xms_errmsg);
743     XmStringFree(xms_title);
744 }
745
746
747
748 /* 
749  * Initialize tooltalk.  Can be called multiple times: the first call
750  * initializes tooltalk, subsequent calls are no-ops.
751  *
752  * Returns
753  *              -1      Error.  Tooltalk not initialized
754  *              0       Tooltalk already initialized
755  *              1       Tooltalk succussfully intialized
756  */
757
758 Tt_status
759 cmtt_init(
760         char            *toolname,
761         DTCM_editor     *de,
762         XtAppContext    context,
763         Widget           shell)
764
765 {
766         static int      initialized = 0;
767         int             ttfd;
768         Tt_status       status;
769         char            *ttenv;
770         char            *session;
771
772         if (de->c->tt_procid) {
773                 return 0;
774         }
775
776         ttenv = (char *)getenv("TT_SESSION");
777         if (!ttenv || strlen(ttenv) == 0) {
778                 session = tt_X_session(XDisplayString(XtDisplay(shell)));
779                 tt_default_session_set(session);
780                 tt_free(session);
781         }
782
783         de->c->tt_procid = ttdt_open(&ttfd, toolname, "SunSoft", "%I", 1);
784         status = tt_ptr_error(de->c->tt_procid);
785         if (tt_is_err(status)) {
786                 de->c->tt_procid = NULL;
787                 return status;
788         }
789
790         /*
791          * Declare our ptype, and register the callback to handle
792          * Edit/Display/Compose requests
793          */
794         status = ttmedia_ptype_declare(ptype, 0, load_cb, (void *)de, 1);
795
796         if (tt_is_err(status)) {
797                 fprintf(stderr, "cmtt_init could not declare ptype: %s\n",
798                         tt_status_message(status));
799                 return status;
800         }
801
802         status = ttmedia_ptype_declare(ptype, 1000, load_cb, (void *)de, 0);
803
804         if (tt_is_err(status)) {
805                 fprintf(stderr, "cmtt_init could not declare ptype: %s\n",
806                         tt_status_message(status));
807         }
808
809         ttdt_session_join(0, NULL, shell, de->c, 1);
810
811         XtAppAddInput(context, ttfd, (XtPointer)XtInputReadMask,
812                         tttk_Xt_input_handler, de->c->tt_procid);
813
814         tttk_Xt_input_handler( 0, 0, 0 );
815
816         return TT_OK;
817 }
818
819 Tt_message
820 reply_cb(Tt_message m, 
821         void *c_data, 
822         Tttk_op op, 
823         unsigned char *contents, 
824         int len, 
825         char *file)
826 {
827         char *client_procID = tt_message_handler(m);
828         if ( debug && (client_procID != NULL) ) {
829                 fprintf(stderr, "DEBUG: reply_cb():client_procID = %s\n", client_procID);
830                 fprintf(stderr, "DEBUG: reply_cb():message_op = %s\n", tt_message_op(m));
831         }
832         return(m);
833 }
834
835 static Tt_message
836 contract_cb(
837         Tt_message      msg,
838         void            *clientdata,
839         Tt_message      contract)
840 {
841
842         /* For now do nothing 
843
844         switch (op) {
845         case TTDT_QUIT:
846         case TTDT_GET_STATUS:
847         case TTDT_PAUSE:
848         case TTDT_RESUME:
849                 break;
850         }
851 */
852         return msg;
853 }
854
855 /*
856  * Handle Edit, Display and Compose requests
857  */
858 static Tt_message
859 load_cb(
860         Tt_message      msg,
861         void            *clientdata,
862         Tttk_op         op,
863         Tt_status       diagnosis,
864         unsigned char   *contents,
865         int             len,
866         char            *file,
867         char            *docname
868 )
869
870 {
871         Tt_status status;
872         char    *p;
873         DTCM_editor     *de;
874         FILE            *fp;
875         char            filename[20];
876         CmDataList              *list = NULL;
877         Dtcm_appointment        *appt;
878         int             i;
879
880         de = (DTCM_editor *)clientdata;
881
882         de->vtype = strdup(tt_message_arg_type(msg, 0));
883
884         if (diagnosis != TT_OK) {
885                 if (tt_message_status(msg) == TT_WRN_START_MESSAGE) {
886                         /*
887                          * Error in start message!  we may want to exit
888                          * here, but for now let toolkit handle error
889                          */
890                          return msg;
891                 }
892
893                 /* Let toolkit handle the error */
894                 return msg;
895         }
896
897         de->contract_pats = ttdt_message_accept(msg, contract_cb, clientdata,
898                                                 de->c->frame, 1, 1);
899
900         tt_ptype_undeclare(ptype);
901
902         if ((status = tt_ptr_error(de->contract_pats)) != TT_OK) {
903                 fprintf(stderr, "dtcm: load_cb could not accept message: %s\n",
904                         tt_status_message(status));
905         } else {
906         /*
907                 tttk_patterns_destroy(de->contract_pats);
908         */
909         }
910
911         de->read_only = False;
912
913         switch (op) {
914
915         case TTME_COMPOSE:
916                         XtSetSensitive(de->attach_button, True);
917                         break;
918         case TTME_DISPLAY:
919         case TTME_EDIT:
920                         /* for Display only messages, the "Attach" 
921                            button makes no sense */
922
923                         if (op == TTME_EDIT)
924                                 XtSetSensitive(de->attach_button, True);
925                         else {
926                                 XtSetSensitive(de->attach_button, False);
927                                 de->read_only = True;
928                         }
929                         list = CmDataListCreate();
930                         if (file == NULL)
931                         {
932                                 /*
933                                  * Save data to a file so we can pass it to parse_appt_from_file
934                                  */
935                                 
936                                 strcpy(filename, "/tmp/cmXXXXXX");
937                                 mktemp(filename);
938                                 if ((fp = fopen(filename, "w")) == 0) {
939                                         tttk_message_fail( msg, TT_DESKTOP_ENODATA, 0, 1 );
940                                         return 0;
941                                 }
942
943                                 fwrite(contents, 1, len, fp);
944                                 fclose(fp);
945                                 parse_appt_from_file(de->c->DT_catd, filename, 
946                                                 list, de->p, query_user, 
947                                                 de->c, DATAVER_ARCHIVE);
948                                 unlink(filename);
949                                 de->init = buffer_tt;
950                         }
951                         else
952                         {
953                                 de->file = strdup(file);
954                                 parse_appt_from_file(de->c->DT_catd, de->file, 
955                                                 list, de->p, query_user, 
956                                                 de->c, DATAVER_ARCHIVE);
957                                 de->init = file_tt;
958                         }
959
960                         if (appt = (Dtcm_appointment *)CmDataListGetData(list, 1)) {
961                                 dssw_attrs_to_form(de->dssw, appt);
962                                 rfp_attrs_to_form(de->rfp, appt);
963                         }
964                         for (i = 1; i <= list->count; i++)
965                                 if (appt = (Dtcm_appointment *)
966                                 CmDataListGetData(list, i)) {
967                                         if (de->orig_appt)
968                                                 free_appt_struct(&de->orig_appt);
969
970                                         de->orig_appt = appt;
971                                 }
972                         CmDataListDestroy(list, B_FALSE);
973
974                         break;
975         }
976
977         de->contract = msg;
978         de->modified = False;
979         de->dsswFlags = de->rfpFlags = 0;
980
981         tt_free((caddr_t)contents);
982         tt_free(file);
983         tt_free(docname);
984
985         return 0;
986 }
987
988 static void
989 handle_drop_cb(
990         Widget          w,
991         XtPointer       client_data,
992         XtPointer       call_data)
993 {
994         Display         *display = XtDisplay(w);
995         DtDndDropCallbackStruct *transfer_info = (DtDndDropCallbackStruct *)call_data;
996         DTCM_editor     *de;
997         char            filename[20];
998         char            *data;
999         int             size;
1000         FILE            *fp;
1001         int             i;
1002
1003         de = (DTCM_editor *)client_data;
1004
1005         transfer_info->status = DtDND_SUCCESS;
1006
1007         for (i = 0; i < transfer_info->dropData->numItems; i++) {
1008                 switch(transfer_info->dropData->protocol) {
1009                 case DtDND_FILENAME_TRANSFER:
1010                         /* REMIND -- handle multiple filenames */
1011                         data = transfer_info->dropData->data.files[0];
1012         
1013                         de->file = strdup(data);
1014                         load_from_file(de);
1015                         break;
1016                 case DtDND_BUFFER_TRANSFER:
1017         
1018                         /*
1019                          * Save data to a file so we can pass it to drag_load_proc().
1020                          */
1021                         strcpy(filename, "/tmp/cmXXXXXX");
1022                         mktemp(filename);
1023         
1024                         if ((fp = fopen(filename, "w")) == 0) {
1025                                 transfer_info->status = DtDND_FAILURE;
1026                                 return;
1027                         }
1028         
1029                         data = transfer_info->dropData->data.buffers[0].bp;
1030                         size = transfer_info->dropData->data.buffers[0].size;
1031                         fwrite(data, 1, size, fp);
1032                         fclose(fp);
1033         
1034                         de->file = strdup(filename);
1035                         load_from_file(de);
1036         
1037                         unlink(filename);
1038                         break;
1039                 default:
1040                         transfer_info->status = DtDND_FAILURE;
1041                         return;
1042                 }
1043         }
1044
1045         return;
1046 }
1047
1048 void
1049 de_register_drop_site(
1050         DTCM_editor     *de,
1051         Widget          w,
1052         Boolean         registerchildren)
1053
1054 {
1055         XtCallbackRec   transfer_cb_rec[] = { {handle_drop_cb, NULL},
1056                                               {NULL, NULL} };
1057         Display         *display = XtDisplayOfObject(w);
1058
1059         transfer_cb_rec[0].closure = (XtPointer)de;
1060
1061         DtDndVaDropRegister(w, DtDND_FILENAME_TRANSFER | DtDND_BUFFER_TRANSFER,
1062                         XmDROP_COPY | XmDROP_MOVE,
1063                         transfer_cb_rec, 
1064                         DtNregisterChildren, registerchildren,  
1065                         NULL);
1066         return;
1067 }
1068
1069 /*
1070 **  Main line
1071 */
1072 int 
1073 main(int argc, char **argv) {
1074         int             dssw_loffset, rfp_loffset, start, stop;
1075         Dimension       dssw_x, rfp_x;
1076         DTCM_editor     *de;
1077         XmString        xmstr;
1078         Boolean         btn1_transfer;
1079         Dimension       width, longest_dssw_label, longest_rfp_label;
1080         WidgetList      children;
1081         Widget          widgets[20];
1082         int             i = 0;
1083         int             j = 0;
1084         int             n;
1085         char            *title;
1086         XmString        label_str;
1087         Tt_status       status;
1088
1089         de = (DTCM_editor *)ckalloc(sizeof(DTCM_editor));
1090         de->file = NULL;
1091
1092         if (argc > 1) {
1093                 if (strcasecmp(argv[1], "-h") == 0)
1094                         display_command_usage();
1095                 else if (argv[1][0] != '-')
1096                         de->file = cm_strdup(argv[1]);
1097         }
1098         
1099         XtSetLanguageProc(NULL, NULL, NULL);
1100         _DtEnvControl(DT_ENV_SET); /* set up environment variables */
1101
1102         de->top_level = XtVaAppInitialize(&app,
1103                 "Dtcm", NULL, 0, &argc, argv, NULL,
1104                 XmNallowShellResize, True,
1105                 XmNdeleteResponse, XmDO_NOTHING,
1106                 NULL);
1107         setup_quit_handler(de->top_level, de_quit_handler, (caddr_t)de);
1108
1109         /*
1110         **  First, create a calendar and fill only the stuff we're going to
1111         **  use.
1112         */
1113         calendar = (Calendar *)ckalloc(sizeof(Calendar));
1114         de->c = calendar;
1115         de->p = (Props *)ckalloc(sizeof(Props));
1116         de->c->properties = (caddr_t)de->p;
1117         de->c->general = (General*) ckalloc(sizeof(General));
1118         de->c->general->version = DATAVER_ARCHIVE;
1119         de->c->frame = de->top_level;
1120         de->pu = (Props_pu *)ckalloc(sizeof(Props_pu));
1121         de->c->properties_pu = (caddr_t)de->pu;
1122         de->init = no_tt;
1123         read_props(de->p);
1124         cal_convert_cmrc(de->p);
1125         if ((start = get_int_prop(de->p, CP_DAYBEGIN)) < 0)
1126                 start = 0;
1127         else if (start > 22)
1128                 start = 22;
1129         if ((stop = get_int_prop(de->p, CP_DAYEND)) <= start)
1130                 stop = start + 1;
1131         else if (stop > 23)
1132                 stop = 23;
1133         set_int_prop(de->p, CP_DAYBEGIN, start);
1134         set_int_prop(de->p, CP_DAYEND, stop);
1135         de->c->calname = cm_strdup(get_char_prop(de->p, CP_DEFAULTCAL));
1136         de->c->DT_catd = catopen(DTCM_CAT, NL_CAT_LOCALE);
1137
1138         /* Open the message catalog for internationalization */
1139         calendar->DT_catd = catopen(DTCM_CAT, NL_CAT_LOCALE);
1140
1141         title = XtNewString(catgets(calendar->DT_catd, 1, 1074, 
1142                                                 "Calendar Appointment"));
1143         XtVaSetValues(de->top_level, 
1144                 XmNtitle, title,
1145                 NULL);
1146         XtFree(title);
1147
1148         /*
1149         **  Okay, now create the form manager and the widgets
1150         */
1151         de->form = XtVaCreateWidget("form",
1152                 xmFormWidgetClass, de->top_level,
1153                 XmNautoUnmanage, True,
1154                 XmNfractionBase, 5,
1155                 NULL);
1156
1157         label_str = XmStringCreateLocalized(catgets(de->c->DT_catd, 1, 846, "Save"));
1158         de->attach_button = XtVaCreateWidget("attach_button",
1159                 xmPushButtonGadgetClass, de->form,
1160                 XmNlabelString, label_str,
1161                 XmNleftAttachment, XmATTACH_POSITION,
1162                 XmNleftPosition, 1,
1163                 XmNrightAttachment, XmATTACH_POSITION,
1164                 XmNrightPosition, 2,
1165                 XmNbottomAttachment, XmATTACH_FORM,
1166                 XmNbottomOffset, 10,
1167                 NULL);
1168         XmStringFree(label_str);
1169         XtAddCallback(de->attach_button, XmNactivateCallback, de_apply_proc, (XtPointer)de);
1170         if (!de->file)
1171                 XtSetSensitive(de->attach_button, False);
1172
1173         label_str = XmStringCreateLocalized(catgets(de->c->DT_catd, 1, 691, "Reset"));
1174         de->reset_button = XtVaCreateWidget("reset_button",
1175                 xmPushButtonGadgetClass, de->form,
1176                 XmNlabelString, label_str,
1177                 XmNleftAttachment, XmATTACH_POSITION,
1178                 XmNleftPosition, 3,
1179                 XmNrightAttachment, XmATTACH_POSITION,
1180                 XmNrightPosition, 4,
1181                 XmNbottomAttachment, XmATTACH_FORM,
1182                 XmNbottomOffset, 10,
1183                 NULL);
1184         XmStringFree(label_str);
1185         XtAddCallback(de->reset_button, XmNactivateCallback, de_reset_proc, de);
1186
1187         create_all_pixmaps(de->pu, de->form);
1188
1189         de->rfpFlags = 0;
1190         de->rfp = (RFP *)ckalloc(sizeof(RFP));
1191         build_rfp(de->rfp, de->c, de->form);
1192         XtVaSetValues(de->rfp->rfp_form_mgr,
1193                 XmNbottomAttachment, XmATTACH_WIDGET,
1194                 XmNbottomWidget, de->attach_button,
1195                 XmNbottomOffset, 25,
1196                 XmNleftAttachment, XmATTACH_FORM,
1197                 XmNleftOffset, 5,
1198                 NULL);
1199         XtVaGetValues(de->rfp->rfp_form_mgr,
1200                 XmNchildren,            &children,
1201                 XmNnumChildren,         &n,
1202                 NULL);
1203         /* We don't want to manage the privacy widgets */
1204         for (i = 0; i < n; i++) {
1205                 if ((children[i] == de->rfp->privacy_label) ||
1206                     (children[i] == de->rfp->privacy_menu))
1207                     continue;
1208                 widgets[j++] = children[i];
1209         }
1210         XtManageChildren(widgets, n - 2);       
1211
1212         /*
1213          * Add a drag source icon inside the dssw, lower right
1214          */
1215         xmstr = XmStringCreateLocalized(
1216                         catgets(de->c->DT_catd, 1, 627, "Drag Appt"));
1217         de->drag_source = XtVaCreateWidget("drag_source",
1218                 dtIconGadgetClass, de->form,
1219                 XmNpixmapPosition, XmPIXMAP_TOP,
1220                 XmNstringPosition, XmSTRING_BOTTOM,
1221                 XmNalignment, XmALIGNMENT_CENTER,
1222                 XmNstring, xmstr,
1223                 XmNbottomAttachment, XmATTACH_WIDGET,
1224                 XmNbottomWidget, de->attach_button,
1225                 XmNbottomOffset, 25,
1226                 XmNleftAttachment, XmATTACH_WIDGET,
1227                 XmNleftWidget, de->rfp->rfp_form_mgr,
1228                 XmNrightAttachment, XmATTACH_FORM,
1229                 XmNtraversalOn, False,
1230                 NULL);
1231         XmStringFree(xmstr);
1232
1233         XtAddEventHandler(XtParent(de->drag_source), Button1MotionMask, False,
1234                 (XtEventHandler)EditApptDragMotionHandler, (XtPointer) de);
1235
1236         XtVaGetValues((Widget)XmGetXmDisplay(XtDisplay(de->form)),
1237                 "enableBtn1Transfer",   &btn1_transfer,
1238                 NULL);
1239
1240         /* btn1_transfer is a tri-state variable - see 1195846 */
1241         if ((Boolean)btn1_transfer != True)
1242                 XtAddEventHandler(XtParent(de->drag_source),
1243                                 Button2MotionMask, False,
1244                                 (XtEventHandler)EditApptDragMotionHandler,
1245                                 (XtPointer) de);
1246
1247
1248         if (de->pu->drag_icon_xbm)
1249                 XtVaSetValues(de->drag_source,
1250                                 XmNpixmap, de->pu->drag_icon_xbm,
1251                                 NULL);
1252
1253         de->dsswFlags = 0;
1254         de->dssw = (DSSW *)ckalloc(sizeof(DSSW));
1255         build_dssw(de->dssw, de->c, de->form, True, True);
1256         XtVaSetValues(de->dssw->dssw_form_mgr,
1257                 XmNtopAttachment, XmATTACH_FORM,
1258                 XmNtopOffset, 10,
1259                 XmNbottomAttachment, XmATTACH_WIDGET,
1260                 XmNbottomWidget, de->rfp->rfp_form_mgr,
1261                 XmNbottomOffset, 15,
1262                 XmNleftAttachment, XmATTACH_FORM,
1263                 XmNleftOffset, 10,
1264                 NULL);
1265         ManageChildren(de->dssw->dssw_form_mgr);
1266
1267         /* set up callback to detect whether the appointment 
1268            definition has been modified */
1269
1270         XtAddCallback(de->dssw->start_text, XmNvalueChangedCallback, de_mark_change, de);
1271         XtAddCallback(de->dssw->start_am, XmNvalueChangedCallback, de_mark_change, de);
1272         XtAddCallback(de->dssw->start_pm, XmNvalueChangedCallback, de_mark_change, de);
1273         XtAddCallback(de->dssw->stop_text, XmNvalueChangedCallback, de_mark_change, de);
1274         XtAddCallback(de->dssw->stop_am, XmNvalueChangedCallback, de_mark_change, de);
1275         XtAddCallback(de->dssw->stop_pm, XmNvalueChangedCallback, de_mark_change, de);
1276         XtAddCallback(de->dssw->what_text, XmNvalueChangedCallback, de_mark_change, de);
1277         XtAddCallback(de->rfp->repeat_menu, XmNselectionCallback, de_mark_change, de);
1278         XtAddCallback(de->rfp->for_menu, XmNselectionCallback, de_mark_change, de);
1279
1280         ManageChildren(de->form);
1281         XtManageChild(de->form);
1282         XtRealizeWidget(de->top_level);
1283
1284         /*
1285         **  Do some monkeying to compensate for Motif's pitiful form managers
1286         XtVaGetValues(de->dssw->start_menu, XmNx, &dssw_x,
1287                 XmNleftOffset, &dssw_loffset,
1288                 NULL);
1289         XtVaGetValues(de->rfp->repeat_menu, XmNx, &rfp_x,
1290                 XmNleftOffset, &rfp_loffset,
1291                 NULL);
1292
1293         if (dssw_x > rfp_x)
1294                 XtVaSetValues(de->rfp->rfp_form_mgr,
1295                         XmNleftOffset, rfp_loffset + (dssw_x - rfp_x),
1296                         NULL);
1297         else if (rfp_x > dssw_x)
1298                 XtVaSetValues(de->dssw->dssw_form_mgr,
1299                         XmNleftOffset, dssw_loffset + (rfp_x - dssw_x),
1300                         NULL);
1301         */
1302
1303 /*      Don't need these any more.      
1304         XtVaGetValues(de->dssw->date_label, XmNwidth, &longest_dssw_label, NULL);
1305
1306         XtVaGetValues(de->dssw->start_label, XmNwidth, &width, NULL);
1307         if (width > longest_dssw_label)
1308                 longest_dssw_label = width;
1309
1310         XtVaGetValues(de->dssw->stop_label, XmNwidth, &width, NULL);
1311         if (width > longest_dssw_label)
1312                 longest_dssw_label = width;
1313
1314         XtVaGetValues(de->dssw->what_label, XmNwidth, &width, NULL);
1315         if (width > longest_dssw_label)
1316                 longest_dssw_label = width;
1317
1318         XtVaGetValues(de->rfp->frequency_label, XmNwidth, &longest_rfp_label, NULL);
1319         XtVaGetValues(de->rfp->for_label, XmNwidth, &width, NULL);
1320         if (width > longest_rfp_label)
1321                 longest_rfp_label = width;
1322
1323         XtVaSetValues(de->dssw->dssw_form_mgr, 
1324                         XmNleftOffset, longest_rfp_label - longest_dssw_label, 
1325                         NULL);
1326 */
1327
1328         de_set_defaults(de);
1329
1330         de_register_drop_site(de, de->form, True);
1331         de_register_drop_site(de, de->dssw->dssw_form_mgr, False);
1332         de_register_drop_site(de, de->rfp->rfp_form_mgr, False); 
1333
1334         XmProcessTraversal(de->dssw->what_text, XmTRAVERSE_CURRENT);
1335         XtVaSetValues(de->form, XmNinitialFocus, de->dssw->what_text, NULL);
1336
1337
1338         status = cmtt_init("AppointmentEditor", de, app, calendar->frame);
1339         if (TT_OK != status) {
1340             char *errfmt;
1341             errfmt = catgets(calendar->DT_catd, 2, 2,
1342                         "Could not connect to ToolTalk:\n%s\n");
1343             DieFromToolTalkError( de, errfmt, status );
1344         }
1345
1346         XtAppMainLoop(app);
1347
1348         return 0;
1349 }