1 #ifndef lint
2 static char *rcsid = "$Id: ConvCtrl.c,v 1.54 2001/01/10 08:51:28 ishisone Exp $";
3 #endif
4 /*
5 * Copyright (c) 1990 Software Research Associates, Inc.
6 *
7 * Permission to use, copy, modify, and distribute this software and its
8 * documentation for any purpose and without fee is hereby granted, provided
9 * that the above copyright notice appear in all copies and that both that
10 * copyright notice and this permission notice appear in supporting
11 * documentation, and that the name of Software Research Associates not be
12 * used in advertising or publicity pertaining to distribution of the
13 * software without specific, written prior permission. Software Research
14 * Associates makes no representations about the suitability of this software
15 * for any purpose. It is provided "as is" without express or implied
16 * warranty.
17 *
18 * Author: Makoto Ishisone, Software Research Associates, Inc., Japan
19 */
20
21 #include <X11/IntrinsicP.h>
22 #include <X11/StringDefs.h>
23 #include <X11/Xmu/CharSet.h>
24 #include "ConvCtrlP.h"
25 #include "InputConv.h"
26 #include "ConvDisp.h"
27 #include "MyDispatch.h"
28 #include "AsyncErr.h"
29
30 #define DEBUG_VAR debug_ConversionControl
31 #include "DebugPrint.h"
32
33 static XtResource resources[] = {
34 #define offset(field) XtOffset(ConversionControlWidget, ccontrol.field)
35 { XtNinputObject, XtCInputObject, XtRWidget, sizeof(Widget),
36 offset(inputobj), XtRImmediate, (XtPointer)NULL },
37 { XtNinputObjectClass, XtCInputObjectClass,
38 XtRPointer, sizeof(WidgetClass),
39 offset(inputobjclass), XtRImmediate, (XtPointer)NULL },
40 { XtNdisplayObjectClass, XtCDisplayObjectClass,
41 XtRPointer, sizeof(WidgetClass),
42 offset(displayobjclass), XtRImmediate, (XtPointer)NULL },
43 { XtNclientWindow, XtCWindow, XtRWindow, sizeof(Window),
44 offset(clientwindow), XtRImmediate, (XtPointer)None },
45 { XtNfocusWindow, XtCWindow, XtRWindow, sizeof(Window),
46 offset(focuswindow), XtRImmediate, (XtPointer)None },
47 { XtNcursor, XtCCursor, XtRCursor, sizeof(Cursor),
48 offset(cursor), XtRImmediate, (XtPointer)None },
49 { XtNeventSelectMethod, XtCEventSelectMethod,
50 XtREventSelectMethod, sizeof(EventSelectMethod),
51 offset(eventselectmethod), XtRString, (XtPointer)"none" },
52 { XtNtextEncoding, XtCTextEncoding, XtRAtom, sizeof(Atom),
53 offset(textencoding), XtRString, "COMPOUND_TEXT" },
54 { XtNtextCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
55 offset(textcallback), XtRCallback, (XtPointer)NULL },
56 { XtNnewTextCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
57 offset(newtextcallback), XtRCallback, (XtPointer)NULL },
58 { XtNendCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
59 offset(endcallback), XtRCallback, (XtPointer)NULL },
60 { XtNunusedEventCallback, XtCCallback, XtRCallback, sizeof(XtCallbackList),
61 offset(unusedeventcallback), XtRCallback, (XtPointer)NULL },
62 { XtNsendbackKeyPress, XtCSendbackEvent, XtRBoolean, sizeof(Boolean),
63 offset(sendbackKeyPress), XtRString, (XtPointer)"False" },
64 { XtNtitlebarHeight, XtCTitlebarHeight, XtRDimension, sizeof(Dimension),
65 offset(titlebarheight), XtRImmediate, (XtPointer)0 },
66 #undef offset
67 };
68
69 static void EventToInputObject();
70
71 static XtActionsRec actions[] = {
72 {"to-inputobj", EventToInputObject },
73 };
74
75 static char translations[] = "<Key>: to-inputobj()";
76
77 static void ClassInitialize();
78 static void StringToESM();
79 static void ClassPartInitialize();
80 static void Initialize(), Destroy();
81 static void Realize();
82 static void Resize();
83 static Boolean SetValues();
84
85 static void ConversionStartup();
86 static void ConversionFinish();
87 static void ChangeAttributes();
88 static void ChangeFocus();
89 static void TextChange();
90 static void Fix();
91 static void ModeChange();
92 static void SelectionControl();
93 static void AuxControl();
94
95 static void GetClientCoordinates();
96
97 static Widget CreateInputObject();
98
99 static Boolean ClassIsSubClassOf();
100
101 static void CaptureClientDead();
102 static void InterceptClientKeyEvent();
103 static void SelectFocusKeyEvent();
104 static void UnselectFocusKeyEvent();
105 static void ClientKey();
106 static void ClientDead();
107
108 static Boolean SafeGetWindowAttributes();
109 static void CheckAttributes();
110 static void CheckCoordinates();
111 static Boolean clipRectangle();
112
113 static void FixCallback();
114 static void ConversionEndCallback();
115 static void TextChangeCallback();
116 static void ModeChangeCallback();
117 static void SelectionControlCallback();
118 static void AuxControlCallback();
119
120 static void WidgetError(), WidgetWarning();
121
122 static CompositeClassExtensionRec CompositeExtension = {
123 /* next_extension */ NULL,
124 /* record_type */ NULLQUARK,
125 /* version */ XtCompositeExtensionVersion,
126 /* record_size */ sizeof(CompositeClassExtensionRec),
127 /* accept_objects */ True,
128 };
129
130 ConversionControlClassRec conversionControlClassRec = {
131 { /* core fields */
132 /* superclass */ (WidgetClass) &transientShellClassRec,
133 /* class_name */ "ConversionControl",
134 /* widget_size */ sizeof(ConversionControlRec),
135 /* class_initialize */ ClassInitialize,
136 /* class_part_initialize */ ClassPartInitialize,
137 /* class_inited */ FALSE,
138 /* initialize */ Initialize,
139 /* initialize_hook */ NULL,
140 /* realize */ Realize,
141 /* actions */ actions,
142 /* num_actions */ XtNumber(actions),
143 /* resources */ resources,
144 /* num_resources */ XtNumber(resources),
145 /* xrm_class */ NULLQUARK,
146 /* compress_motion */ TRUE,
147 /* compress_exposure */ TRUE,
148 /* compress_enterleave */ TRUE,
149 /* visible_interest */ FALSE,
150 /* destroy */ Destroy,
151 /* resize */ Resize,
152 /* expose */ NULL,
153 /* set_values */ SetValues,
154 /* set_values_hook */ NULL,
155 /* set_values_almost */ XtInheritSetValuesAlmost,
156 /* get_values_hook */ NULL,
157 /* accept_focus */ NULL,
158 /* version */ XtVersion,
159 /* callback_private */ NULL,
160 /* tm_table */ translations,
161 /* query_geometry */ XtInheritQueryGeometry,
162 /* display_accelerator */ XtInheritDisplayAccelerator,
163 /* extension */ NULL
164 },
165 { /* composite fields */
166 /* geometry_manager */ XtInheritGeometryManager,
167 /* change_managed */ XtInheritChangeManaged,
168 /* insert_child */ XtInheritInsertChild,
169 /* delete_child */ XtInheritDeleteChild,
170 /* extension */ (XtPointer)&CompositeExtension,
171 },
172 { /* shell fields */
173 /* extension */ NULL
174 },
175 { /* wm_shell fields */
176 /* extension */ NULL
177 },
178 { /* vendor_shell fields */
179 /* extension */ NULL
180 },
181 { /* transient_shell fields */
182 /* extension */ NULL
183 },
184 { /* conversionControl fields */
185 /* Startup */ ConversionStartup,
186 /* Finish */ ConversionFinish,
187 /* ChangeAttributes */ ChangeAttributes,
188 /* ChangeFocus */ ChangeFocus,
189 /* TextChange */ TextChange,
190 /* Fix */ Fix,
191 /* ModeChange */ ModeChange,
192 /* SelectionControl */ SelectionControl,
193 /* AuxControl */ AuxControl,
194 }
195 };
196
197 WidgetClass conversionControlWidgetClass = (WidgetClass)&conversionControlClassRec;
198
199 /* ARGSUSED */
200 static void
ClassInitialize()201 ClassInitialize()
202 {
203 /* add String -> EventSelectionMethod converter */
204 XtAddConverter(XtRString, XtREventSelectMethod, StringToESM,
205 (XtConvertArgList)NULL, (Cardinal)0);
206 }
207
208 /* ARGSUSED */
209 static void
StringToESM(args,num_args,from,to)210 StringToESM(args, num_args, from, to)
211 XrmValue *args;
212 Cardinal *num_args;
213 XrmValue *from;
214 XrmValue *to;
215 {
216 char *s = (char *)from->addr;
217 static EventSelectMethod esm = ESMethodNone;
218
219 if (!XmuCompareISOLatin1(s, "inputonly")) {
220 esm = ESMethodInputOnly;
221 } else if (!XmuCompareISOLatin1(s, "selectfocus")) {
222 esm = ESMethodSelectFocus;
223 } else if (!XmuCompareISOLatin1(s, "none")) {
224 esm = ESMethodNone;
225 } else {
226 XtStringConversionWarning(s, XtREventSelectMethod);
227 }
228
229 to->size = sizeof(EventSelectMethod);
230 to->addr = (caddr_t)&esm;
231 }
232
233 static void
ClassPartInitialize(cl)234 ClassPartInitialize(cl)
235 WidgetClass cl;
236 {
237 ConversionControlWidgetClass class = (ConversionControlWidgetClass)cl;
238 ConversionControlWidgetClass super = (ConversionControlWidgetClass)class->core_class.superclass;
239
240 #define ccclass conversionControl_class
241 if (class->ccclass.Startup == XtInheritStartup) {
242 class->ccclass.Startup = super->ccclass.Startup;
243 }
244 if (class->ccclass.Finish == XtInheritFinish) {
245 class->ccclass.Finish = super->ccclass.Finish;
246 }
247 if (class->ccclass.ChangeAttributes == XtInheritChangeAttributes) {
248 class->ccclass.ChangeAttributes = super->ccclass.ChangeAttributes;
249 }
250 if (class->ccclass.ChangeFocus == XtInheritChangeFocus) {
251 class->ccclass.ChangeFocus = super->ccclass.ChangeFocus;
252 }
253 if (class->ccclass.TextChange == XtInheritTextChange) {
254 class->ccclass.TextChange = super->ccclass.TextChange;
255 }
256 if (class->ccclass.Fix == XtInheritFix) {
257 class->ccclass.Fix = super->ccclass.Fix;
258 }
259 if (class->ccclass.ModeChange == XtInheritModeChange) {
260 class->ccclass.ModeChange = super->ccclass.ModeChange;
261 }
262 if (class->ccclass.SelectionControl == XtInheritSelectionControl) {
263 class->ccclass.SelectionControl = super->ccclass.SelectionControl;
264 }
265 if (class->ccclass.AuxControl == XtInheritAuxControl) {
266 class->ccclass.AuxControl = super->ccclass.AuxControl;
267 }
268 #undef ccclass
269 }
270
271 /* ARGSUSED */
272 static void
Initialize(req,new,args,num_args)273 Initialize(req, new, args, num_args)
274 Widget req;
275 Widget new;
276 ArgList args;
277 Cardinal *num_args;
278 {
279 ConversionControlWidget ccw = (ConversionControlWidget)new;
280
281 /*
282 * check inputobj/inputobjclass resource
283 */
284
285 if (ccw->ccontrol.inputobj == NULL) {
286 /* if inputobj not specified, inputobjclass must be specified */
287 if (ccw->ccontrol.inputobjclass == NULL) {
288 WidgetError(new, "noResourceError", "inputObjectClass",
289 "either inputObject or inputObjectClass must be specified at creation time");
290 } else if (!ClassIsSubClassOf(ccw->ccontrol.inputobjclass,
291 inputConvObjectClass)) {
292 WidgetError(new, "classError", "inputObjectClass",
293 "inputObjectClass must be subclass of inputConvObjectClass");
294 }
295 (void)CreateInputObject(ccw);
296 ccw->ccontrol.createinputobj = True;
297
298 } else if (!XtIsSubclass(ccw->ccontrol.inputobj, inputConvObjectClass)) {
299 WidgetError(new, "classError", "inputObject",
300 "inputObject must be subclass of inputConvObjectClass");
301 }
302
303 if (ccw->ccontrol.displayobjclass == NULL) {
304 WidgetError(new, "noResourceError", "displayObjectClass",
305 "displayObjectClass must be specified");
306 } else if (!ClassIsSubClassOf(ccw->ccontrol.displayobjclass,
307 convDisplayObjectClass)) {
308 WidgetError(new, "classError", "displayObjectClass",
309 "displayObjectClass must be subclass of convDisplayObjectClass");
310 }
311
312 ccw->ccontrol.active = False;
313 ccw->ccontrol.oldclientwindow = None;
314 ccw->ccontrol.probewindow = None;
315 }
316
317 static void
Destroy(w)318 Destroy(w)
319 Widget w;
320 {
321 ConversionControlWidget ccw = (ConversionControlWidget)w;
322 Display *dpy = XtDisplay(w);
323
324 if (ccw->ccontrol.active == False) return;
325
326 if (ccw->ccontrol.clientwindow != None) {
327 MyRemoveAllEventHandler(dpy, ccw->ccontrol.clientwindow);
328 }
329
330 if (ccw->ccontrol.probewindow != None) {
331 MyRemoveAllEventHandler(dpy, ccw->ccontrol.probewindow);
332 XDestroyWindow(dpy, ccw->ccontrol.probewindow);
333 }
334 }
335
336 static void
Realize(w,maskp,attr)337 Realize(w, maskp, attr)
338 Widget w;
339 XtValueMask *maskp;
340 XSetWindowAttributes *attr;
341 {
342 ConversionControlWidget ccw = (ConversionControlWidget)w;
343
344 if (ccw->ccontrol.cursor != None) {
345 attr->cursor = ccw->ccontrol.cursor;
346 *maskp |= CWCursor;
347 }
348
349 /* call super class's realize function */
350 (*conversionControlWidgetClass->core_class.superclass->core_class.realize)(w, maskp, attr);
351 }
352
353 static void
Resize(w)354 Resize(w)
355 Widget w;
356 {
357 ConversionControlWidget ccw = (ConversionControlWidget)w;
358 Widget child;
359 int i;
360
361 TRACE(("ConversionControl:Resize()\n"));
362
363 /* ignore non-widgets */
364 for (i = 0; i < ccw->composite.num_children; i++) {
365 child = ccw->composite.children[i];
366 if (XtIsWidget(child) && child->core.managed) {
367 XtResizeWidget(child, ccw->core.width, ccw->core.height,
368 child->core.border_width);
369 }
370 }
371 }
372
373 /* ARGSUSED */
374 static Boolean
SetValues(cur,req,wid,args,num_args)375 SetValues(cur, req, wid, args, num_args)
376 Widget cur;
377 Widget req;
378 Widget wid;
379 ArgList args;
380 Cardinal *num_args;
381 {
382 ConversionControlWidget old = (ConversionControlWidget)cur;
383 ConversionControlWidget new = (ConversionControlWidget)wid;
384
385 if (new->ccontrol.active) {
386 if (old->ccontrol.inputobj != new->ccontrol.inputobj) {
387 WidgetWarning(wid, "setValuesError", "inputObject",
388 "inputObject resource can't be changed during conversion");
389 new->ccontrol.inputobj = old->ccontrol.inputobj; /* restore */
390 }
391 if (old->ccontrol.eventselectmethod != new->ccontrol.eventselectmethod) {
392 WidgetWarning(wid, "setValuesError", "eventSelectionMethod",
393 "eventSelectionMethod resource can't be changed during conversion");
394 new->ccontrol.eventselectmethod = old->ccontrol.eventselectmethod; /* restore */
395 }
396 }
397
398 if (new->ccontrol.clientwindow != old->ccontrol.clientwindow ||
399 new->ccontrol.focuswindow != old->ccontrol.focuswindow) {
400 WidgetWarning(wid, "setValuesError", "clientWindow",
401 "clientWindow and focusWindow resources are read-only");
402 new->ccontrol.clientwindow = old->ccontrol.clientwindow; /* restore */
403 new->ccontrol.focuswindow = old->ccontrol.focuswindow; /* restore */
404 }
405
406 if (new->ccontrol.cursor != old->ccontrol.cursor && XtIsRealized(wid)) {
407 XDefineCursor(XtDisplay(wid), XtWindow(wid), new->ccontrol.cursor);
408 }
409
410 return False;
411 }
412
413 /* ARGSUSED */
414 static void
ConversionStartup(w,mask,value)415 ConversionStartup(w, mask, value)
416 Widget w;
417 unsigned long mask;
418 ConversionAttributes *value;
419 {
420 /* do nothing */
421 }
422
423 /* ARGSUSED */
424 static void
ConversionFinish(w)425 ConversionFinish(w)
426 Widget w;
427 {
428 /* do nothing */
429 }
430
431 /* ARGSUSED */
432 static void
ChangeAttributes(w,mask,value)433 ChangeAttributes(w, mask, value)
434 Widget w;
435 unsigned long mask;
436 ConversionAttributes *value;
437 {
438 /* do nothing */
439 }
440
441 /* ARGSUSED */
442 static void
ChangeFocus(w,set)443 ChangeFocus(w, set)
444 Widget w;
445 int set;
446 {
447 /* do nothing */
448 }
449
450 /* ARGSUSED */
451 static void
TextChange(w)452 TextChange(w)
453 Widget w;
454 {
455 /* do nothing */
456 }
457
458 /* ARGSUSED */
459 static void
Fix(w,arg)460 Fix(w, arg)
461 Widget w;
462 CCTextCallbackArg *arg;
463 {
464 ConversionControlWidget ccw = (ConversionControlWidget)w;
465
466 XtCallCallbackList((Widget)ccw, ccw->ccontrol.textcallback,
467 (XtPointer)arg);
468 }
469
470 /* ARGSUSED */
471 static void
ModeChange(w)472 ModeChange(w)
473 Widget w;
474 {
475 /* do nothing */
476 }
477
478 /* ARGSUSED */
479 static void
SelectionControl(w,controlarg)480 SelectionControl(w, controlarg)
481 Widget w;
482 ICSelectionControlArg *controlarg;
483 {
484 /* do nothing */
485 }
486
487 /* ARGSUSED */
488 static void
AuxControl(w,controlarg)489 AuxControl(w, controlarg)
490 Widget w;
491 ICAuxControlArg *controlarg;
492 {
493 /* do nothing */
494 }
495
496 /*
497 * public functions
498 */
499
500 void
CControlStartConversion(w,clientwindow,valuemask,value)501 CControlStartConversion(w, clientwindow, valuemask, value)
502 Widget w;
503 Window clientwindow;
504 unsigned long valuemask;
505 ConversionAttributes *value;
506 {
507 ConversionControlWidget ccw = (ConversionControlWidget)w;
508 ConversionControlWidgetClass class = (ConversionControlWidgetClass)w->core.widget_class;
509
510 TRACE(("CControlStartConversion(clientwindow=%lx)\n", clientwindow));
511 if (ccw->ccontrol.active) {
512 WidgetWarning(w, "busyError", "CControlStartConversion",
513 "is busy. can't start conversion");
514 return;
515 }
516 if (clientwindow == None) {
517 /* ouch */
518 WidgetWarning(w, "dataError", "cControlStartConversion",
519 "clientWindow not specified. can't start conversion.");
520 return;
521 }
522
523 /* check clientWindow's existance */
524 if (!SafeGetWindowAttributes(XtDisplay(w), clientwindow,
525 &(ccw->ccontrol.client_attr))) {
526 WidgetWarning(w, "badWindowError", "clientWindow",
527 "clientWindow does not exist. can't start conversion.");
528 return;
529 }
530
531 ICClearConversion(ccw->ccontrol.inputobj);
532 ccw->ccontrol.notext = ICNumSegments(ccw->ccontrol.inputobj) == 0;
533
534 ccw->ccontrol.active = True;
535 ccw->ccontrol.clientwindow = clientwindow;
536
537 /* check given attributes */
538 CheckAttributes(ccw, &valuemask, value);
539
540 if (valuemask & CAFocusWindow) {
541 ccw->ccontrol.focuswindow = value->focuswindow;
542 } else {
543 ccw->ccontrol.focuswindow = clientwindow;
544 ccw->ccontrol.focus_attr = ccw->ccontrol.client_attr;
545 }
546
547 if (ccw->ccontrol.eventselectmethod == ESMethodInputOnly) {
548 InterceptClientKeyEvent(ccw);
549 } else if (ccw->ccontrol.eventselectmethod == ESMethodSelectFocus) {
550 SelectFocusKeyEvent(ccw);
551 }
552
553 CheckCoordinates(ccw, &valuemask, value, 1);
554
555 GetClientCoordinates(ccw);
556
557 CaptureClientDead(ccw);
558
559 XtAddCallback(ccw->ccontrol.inputobj,
560 XtNfixNotify, FixCallback, (XtPointer)ccw);
561 XtAddCallback(ccw->ccontrol.inputobj,
562 XtNendNotify, ConversionEndCallback, (XtPointer)ccw);
563 XtAddCallback(ccw->ccontrol.inputobj,
564 XtNtextChangeNotify, TextChangeCallback, (XtPointer)ccw);
565 XtAddCallback(ccw->ccontrol.inputobj,
566 XtNmodeChangeNotify, ModeChangeCallback, (XtPointer)ccw);
567 XtAddCallback(ccw->ccontrol.inputobj,
568 XtNselectionControl, SelectionControlCallback,
569 (XtPointer)ccw);
570 XtAddCallback(ccw->ccontrol.inputobj,
571 XtNauxControl, AuxControlCallback,
572 (XtPointer)ccw);
573
574 /* call input style dependent startup */
575 (*class->conversionControl_class.Startup)(w, valuemask, value);
576 }
577
578 void
CControlEndConversion(w)579 CControlEndConversion(w)
580 Widget w;
581 {
582 ConversionControlWidget ccw = (ConversionControlWidget)w;
583 ConversionControlWidgetClass class = (ConversionControlWidgetClass)w->core.widget_class;
584 Display *dpy = XtDisplay(w);
585
586 if (!ccw->ccontrol.active) {
587 WidgetWarning(w, "busyError", "cControlEndConversion",
588 "is not active. can't stop conversion");
589 return;
590 }
591
592 XtRemoveCallback(ccw->ccontrol.inputobj,
593 XtNfixNotify, FixCallback, (XtPointer)ccw);
594 XtRemoveCallback(ccw->ccontrol.inputobj,
595 XtNendNotify, ConversionEndCallback, (XtPointer)ccw);
596 XtRemoveCallback(ccw->ccontrol.inputobj,
597 XtNtextChangeNotify, TextChangeCallback, (XtPointer)ccw);
598 XtRemoveCallback(ccw->ccontrol.inputobj,
599 XtNmodeChangeNotify, ModeChangeCallback, (XtPointer)ccw);
600 XtRemoveCallback(ccw->ccontrol.inputobj,
601 XtNselectionControl, SelectionControlCallback,
602 (XtPointer)ccw);
603 XtRemoveCallback(ccw->ccontrol.inputobj,
604 XtNauxControl, AuxControlCallback,
605 (XtPointer)ccw);
606
607 ICClearConversion(ccw->ccontrol.inputobj);
608
609 MyRemoveEventHandler(dpy, ccw->ccontrol.clientwindow, DestroyNotify,
610 ClientDead, (XtPointer)ccw);
611
612 if (ccw->ccontrol.probewindow != None) {
613 MyRemoveAllEventHandler(dpy, ccw->ccontrol.probewindow);
614 XDestroyWindow(dpy, ccw->ccontrol.probewindow);
615 ccw->ccontrol.probewindow = None;
616 }
617
618 /* unselect focuswindow events */
619 if (ccw->ccontrol.eventselectmethod == ESMethodSelectFocus) {
620 UnselectFocusKeyEvent(ccw);
621 }
622
623 ccw->ccontrol.active = False;
624
625 /* call input style dependent finish */
626 (*class->conversionControl_class.Finish)(w);
627
628 ccw->ccontrol.oldclientwindow = ccw->ccontrol.clientwindow;
629 ccw->ccontrol.clientwindow = None;
630 }
631
632 void
CControlChangeAttributes(w,valuemask,value)633 CControlChangeAttributes(w, valuemask, value)
634 Widget w;
635 unsigned long valuemask;
636 ConversionAttributes *value;
637 {
638 ConversionControlWidget ccw = (ConversionControlWidget)w;
639 ConversionControlWidgetClass class = (ConversionControlWidgetClass)w->core.widget_class;
640
641 if (!ccw->ccontrol.active) {
642 WidgetWarning(w, "busyError", "cControlChangeAttributes",
643 "is not active. can't change attributes");
644 return;
645 }
646
647 CheckAttributes(ccw, &valuemask, value);
648 CheckCoordinates(ccw, &valuemask, value, 0);
649
650 if (valuemask == 0L) return;
651
652 if (valuemask & CAFocusWindow) {
653 if (ccw->ccontrol.eventselectmethod == ESMethodSelectFocus) {
654 UnselectFocusKeyEvent(ccw);
655 }
656 ccw->ccontrol.focuswindow = value->focuswindow;
657 if (ccw->ccontrol.eventselectmethod == ESMethodSelectFocus) {
658 SelectFocusKeyEvent(ccw);
659 }
660 }
661
662 (*class->conversionControl_class.ChangeAttributes)(w, valuemask, value);
663 }
664
665 void
CControlChangeFocus(w,set)666 CControlChangeFocus(w, set)
667 Widget w;
668 int set;
669 {
670 ConversionControlWidget ccw = (ConversionControlWidget)w;
671 ConversionControlWidgetClass class = (ConversionControlWidgetClass)w->core.widget_class;
672
673 if (!ccw->ccontrol.active) {
674 WidgetWarning(w, "busyError", "cControlChangeFocus",
675 "is not active. can't change focus");
676 return;
677 }
678
679 (*class->conversionControl_class.ChangeFocus)(w, set);
680 }
681
682 static Boolean
SafeGetWindowAttributes(dpy,w,attr)683 SafeGetWindowAttributes(dpy, w, attr)
684 Display *dpy;
685 Window w;
686 XWindowAttributes *attr;
687 {
688 XAEHandle h;
689 unsigned long errbits = 0;
690
691 h = XAESetRecordErrors(dpy, &errbits);
692 (void)XGetWindowAttributes(dpy, w, attr);
693 XAEUnset(h);
694
695 return (errbits == 0);
696 }
697
698 static void
CheckAttributes(ccw,valuemaskp,value)699 CheckAttributes(ccw, valuemaskp, value)
700 ConversionControlWidget ccw;
701 unsigned long *valuemaskp;
702 ConversionAttributes *value;
703 {
704 Display *dpy = XtDisplay((Widget)ccw);
705 XAEHandle h;
706 unsigned long ebits = 0;
707
708 #define CHECKATTRS (CAFocusWindow|CAColormap|CACursor|CABackgroundPixmap)
709 if ((*valuemaskp & CHECKATTRS) == 0) return;
710 #undef CHECKATTRS
711
712 #define XERROR(e) (ebits & (1 << (e)))
713
714 h = XAESetRecordErrors(dpy, &ebits);
715
716 if (*valuemaskp & (CAColormap|CACursor|CABackgroundPixmap)) {
717 XSetWindowAttributes attr;
718 Window w;
719
720 w = XCreateSimpleWindow(dpy, ccw->ccontrol.clientwindow,
721 0, 0, 1, 1, 0, 0L, 0L);
722
723 if (*valuemaskp & CACursor) {
724 /* BadCursor */
725 attr.cursor = value->cursor;
726 XChangeWindowAttributes(dpy, w, CWCursor, &attr);
727 }
728 if (*valuemaskp & CAColormap) {
729 /* BadMatch or BadColormap */
730 attr.colormap = value->colormap;
731 XChangeWindowAttributes(dpy, w, CWColormap, &attr);
732 }
733 if (*valuemaskp & CABackgroundPixmap) {
734 /* BadMatch or BadPixmap */
735 attr.background_pixmap = value->background_pixmap;
736 XChangeWindowAttributes(dpy, w, CWBackPixmap, &attr);
737 }
738
739 XDestroyWindow(dpy, w);
740 }
741 if (*valuemaskp & CAFocusWindow) {
742 (void)XGetWindowAttributes(dpy, value->focuswindow,
743 &(ccw->ccontrol.focus_attr));
744 } else {
745 XSync(dpy, False);
746 }
747 XAEUnset(h);
748
749 if ((*valuemaskp & CAFocusWindow) && XERROR(BadWindow)) {
750 WidgetWarning((Widget)ccw, "badWindowError", "focusWindow",
751 "focusWindow does not exist.");
752 *valuemaskp &= ~CAFocusWindow;
753 }
754 if ((*valuemaskp & CAColormap) && XERROR(BadColor)) {
755 WidgetWarning((Widget)ccw, "badColorError", "colormap",
756 "invalid colormap ID.");
757 *valuemaskp &= ~CAColormap;
758 }
759 if ((*valuemaskp & CAColormap) && XERROR(BadMatch)) {
760 WidgetWarning((Widget)ccw, "badMatchError", "colormap",
761 "invalid colormap.");
762 *valuemaskp &= ~CAColormap;
763 }
764 if ((*valuemaskp & CABackgroundPixmap) && XERROR(BadPixmap)) {
765 WidgetWarning((Widget)ccw, "badPixmapError", "backgroundPixmap",
766 "invalid pixmap ID.");
767 *valuemaskp &= ~CABackgroundPixmap;
768 }
769 if ((*valuemaskp & CABackgroundPixmap) && XERROR(BadMatch)) {
770 WidgetWarning((Widget)ccw, "badMatchError", "backgroundPixmap",
771 "invalid pixmap for background.");
772 *valuemaskp &= ~CABackgroundPixmap;
773 }
774 if ((*valuemaskp & CACursor) && XERROR(BadCursor)) {
775 WidgetWarning((Widget)ccw, "badCursorError", "cursor",
776 "invalid cursor ID.");
777 *valuemaskp &= ~CACursor;
778 }
779 #undef XERROR
780 }
781
782 static void
CheckCoordinates(ccw,valuemaskp,value,clip)783 CheckCoordinates(ccw, valuemaskp, value, clip)
784 ConversionControlWidget ccw;
785 unsigned long *valuemaskp;
786 ConversionAttributes *value;
787 int clip;
788 {
789 #define INVALIDRECT(r) (r.width == 0 || r.height == 0)
790 if ((*valuemaskp & CAClientArea) &&
791 (INVALIDRECT(value->clientarea) ||
792 (clip && !clipRectangle(&value->clientarea, &ccw->ccontrol.client_attr)))) {
793 DPRINT(("CheckCoordinates: invalid ClientArea\n"));
794 *valuemaskp &= ~CAClientArea;
795 }
796 if ((*valuemaskp & CAStatusArea) &&
797 (INVALIDRECT(value->statusarea) ||
798 (clip && !clipRectangle(&value->statusarea, &ccw->ccontrol.client_attr)))) {
799 DPRINT(("CheckCoordinates: invalid StatusArea\n"));
800 *valuemaskp &= ~CAStatusArea;
801 }
802 #undef INVALIDRECT
803 }
804
805 static Boolean
clipRectangle(rectp,attrp)806 clipRectangle(rectp, attrp)
807 register XRectangle *rectp;
808 register XWindowAttributes *attrp;
809 {
810 register int z0, z1, e;
811
812 z0 = rectp->x; z1 = z0 + rectp->width; e = attrp->width;
813 if (z0 == z1) z1 = e; /* if (rectp->width == 0)... */
814 if (z0 >= e || z1 <= 0) return False;
815 if (z0 < 0) z0 = 0;
816 if (z1 > e) z1 = e;
817 rectp->x = z0; rectp->width = z1 - z0;
818
819 z0 = rectp->y; z1 = z0 + rectp->height; e = attrp->height;
820 if (z0 == z1) z1 = e; /* if (rectp->height == 0)... */
821 if (z0 >= e || z1 <= 0) return False;
822 if (z0 < 0) z0 = 0;
823 if (z1 > e) z1 = e;
824 rectp->y = z0; rectp->height = z1 - z0;
825
826 return True;
827 }
828
829 static void
GetClientCoordinates(ccw)830 GetClientCoordinates(ccw)
831 ConversionControlWidget ccw;
832 {
833 Display *dpy = XtDisplay(ccw);
834 Window root = RootWindowOfScreen(XtScreen(ccw));
835 Window win = ccw->ccontrol.clientwindow;
836 Window junk;
837 int rootx, rooty;
838
839 if (!XTranslateCoordinates(dpy, win, root, 0, 0, &rootx, &rooty, &junk)) {
840 WidgetWarning((Widget)ccw, "windowError", "differentRoot",
841 "clientWindow and conversion widget have different root. this should not happen!");
842 rootx = rooty = 0;
843 }
844
845 ccw->ccontrol.client_rootx = rootx;
846 ccw->ccontrol.client_rooty = rooty;
847 }
848
849 /* ARGSUSED */
850 static void
EventToInputObject(w,event,args,num_args)851 EventToInputObject(w, event, args, num_args)
852 Widget w;
853 XEvent *event;
854 String *args;
855 Cardinal *num_args;
856 {
857 ConversionControlWidget ccw = (ConversionControlWidget)w;
858 Boolean hascallback = False;
859 int r;
860 #ifdef OBSOLETE_FEATURE
861 Boolean sendback = False;
862 #endif
863
864 if (ccw->ccontrol.inputobj == NULL) return;
865
866 ccw->ccontrol.endnotify = False;
867
868 if (ccw->ccontrol.unusedeventcallback != NULL &&
869 XtHasCallbacks(w, XtNunusedEventCallback) == XtCallbackHasSome) {
870 hascallback = True;
871 }
872
873 #ifdef OBSOLETE_FEATURE
874 /*
875 * a cheap little hack -- sending back unused events
876 *
877 * if user set some callback on XtNunusedEventCallback, we call
878 * the callback functions on 'unused' KeyPress events.
879 *
880 * otherwise, if the resource XtNsendbackKeyPress is true, we
881 * attempt to send 'unused' KeyPress events back to the client.
882 *
883 * we call callbacks or send an event back to the client when
884 * following conditions are satisfied:
885 * + it is a KeyPress event AND
886 * + the number of segments is 0 before the conversion object
887 * processes it AND
888 * + the number of segments remains 0 after processing AND
889 * + none of the modeChangeNotify, selectionControl, endNotify and
890 * fixNofity callbacks are called during processing
891 * it is intentional to exclude textChangeNotify callback from
892 * above condition, because this callback is often called even if
893 * the text doesn't change actually. so we use the condition that
894 * the number of segments remains 0 instead.
895 */
896
897 if ((hascallback ||ccw->ccontrol.sendbackKeyPress) &&
898 event->type == KeyPress &&
899 ICNumSegments(ccw->ccontrol.inputobj) == 0) {
900 sendback = True;
901 }
902
903 ccw->ccontrol.eventused = False;
904
905 if ((r = ICInputEvent(ccw->ccontrol.inputobj, event)) == 1 ||
906 (sendback && !ccw->ccontrol.eventused &&
907 ICNumSegments(ccw->ccontrol.inputobj) == 0)) {
908 #else
909 /*
910 * Above feature is obsolete. Now it is the input object's
911 * responsibility to decide whether the event should be
912 * sent back to the client or not.
913 */
914 if ((r = ICInputEvent(ccw->ccontrol.inputobj, event)) == 1) {
915 #endif
916
917 /* event isn't used */
918 if (hascallback) {
919 TRACE(("call XtNunusedEventCallback\n"));
920 XtCallCallbackList(w, ccw->ccontrol.unusedeventcallback,
921 (XtPointer)event);
922 } else if (ccw->ccontrol.sendbackKeyPress) {
923 Window savewin;
924 Window savesubwin;
925
926 TRACE(("sendback event to window 0x%lx\n", ccw->ccontrol.focuswindow));
927 savewin = event->xkey.window;
928 savesubwin = event->xkey.subwindow;
929 event->xkey.window = ccw->ccontrol.focuswindow;
930 event->xkey.subwindow = None;
931
932 /*
933 * here we use NoEventMask as the eventmask, not
934 * KeyPressMask. that means the event will be sent only
935 * to the client who created the destination window.
936 */
937 XSendEvent(XtDisplay(w), event->xkey.window,
938 False, NoEventMask, event);
939
940 event->xkey.window = savewin; /* restore */
941 event->xkey.subwindow = savesubwin; /* restore */
942 }
943 }
944
945 if (r < 0 || ccw->ccontrol.endnotify) {
946 CControlEndConversion(w);
947 XtCallCallbackList(w, ccw->ccontrol.endcallback, (XtPointer)False);
948 }
949 }
950
951
952 /*
953 * sub-widget creation
954 */
955
956 static Widget
CreateInputObject(ccw)957 CreateInputObject(ccw)
958 ConversionControlWidget ccw;
959 {
960 Widget inputobj;
961 Arg args[1];
962
963 XtSetArg(args[0], XtNdisplayObjectClass, ccw->ccontrol.displayobjclass);
964 inputobj = XtCreateWidget("inputObj", ccw->ccontrol.inputobjclass,
965 (Widget)ccw, args, 1);
966 ccw->ccontrol.inputobj = inputobj;
967
968 return inputobj;
969 }
970
971 static Boolean
ClassIsSubClassOf(class,reference)972 ClassIsSubClassOf(class, reference)
973 WidgetClass class;
974 WidgetClass reference;
975 {
976 while (class != NULL) {
977 if (class == reference) return True;
978 class = class->core_class.superclass;
979 }
980 return False;
981 }
982
983 static void
CaptureClientDead(ccw)984 CaptureClientDead(ccw)
985 ConversionControlWidget ccw;
986 {
987 Display *dpy = XtDisplay(ccw);
988 Window win = ccw->ccontrol.clientwindow;
989
990 MyAddEventHandler(dpy, win, DestroyNotify, StructureNotifyMask,
991 ClientDead, (XtPointer)ccw);
992 }
993
994 static void
InterceptClientKeyEvent(ccw)995 InterceptClientKeyEvent(ccw)
996 ConversionControlWidget ccw;
997 {
998 Display *dpy = XtDisplay(ccw);
999 Window win = ccw->ccontrol.clientwindow;
1000 Window probe;
1001
1002 TRACE(("InterceptClientKeyEvent()\n"));
1003 probe = XCreateWindow(dpy, win, 0, 0, 9999, 9999, 0, 0,
1004 InputOnly, (Visual *)CopyFromParent,
1005 0L, (XSetWindowAttributes *)NULL);
1006 TRACE(("\tprobewindow = %lx\n", probe));
1007
1008 MyAddEventHandler(dpy, probe, KeyPress, KeyPressMask,
1009 ClientKey, (XtPointer)ccw);
1010 MyAddEventHandler(dpy, probe, KeyRelease, KeyReleaseMask,
1011 ClientKey, (XtPointer)ccw);
1012
1013 ccw->ccontrol.probewindow = probe;
1014
1015 XMapWindow(dpy, probe);
1016 }
1017
1018 static void
SelectFocusKeyEvent(ccw)1019 SelectFocusKeyEvent(ccw)
1020 ConversionControlWidget ccw;
1021 {
1022 Display *dpy = XtDisplay(ccw);
1023 Window win = ccw->ccontrol.focuswindow;
1024
1025 MyAddEventHandler(dpy, win, KeyPress, KeyPressMask,
1026 ClientKey, (XtPointer)ccw);
1027 MyAddEventHandler(dpy, win, KeyRelease, KeyReleaseMask,
1028 ClientKey, (XtPointer)ccw);
1029 }
1030
1031 static void
UnselectFocusKeyEvent(ccw)1032 UnselectFocusKeyEvent(ccw)
1033 ConversionControlWidget ccw;
1034 {
1035 Display *dpy = XtDisplay(ccw);
1036 Window win = ccw->ccontrol.focuswindow;
1037
1038 MyRemoveEventHandler(dpy, win, KeyPress, ClientKey, (XtPointer)ccw);
1039 MyRemoveEventHandler(dpy, win, KeyRelease, ClientKey, (XtPointer)ccw);
1040 }
1041
1042 static void
ClientKey(ev,data)1043 ClientKey(ev, data)
1044 XEvent *ev;
1045 XtPointer data;
1046 {
1047 Widget w = (Widget)data;
1048 Cardinal num_params = 0;
1049
1050 EventToInputObject(w, ev, (String *)NULL, &num_params);
1051 }
1052
1053 static void
ClientDead(ev,data)1054 ClientDead(ev, data)
1055 XEvent *ev;
1056 XtPointer data;
1057 {
1058 ConversionControlWidget ccw = (ConversionControlWidget)data;
1059 ConversionControlWidgetClass class = (ConversionControlWidgetClass)ccw->core.widget_class;
1060
1061 if (ev->type != DestroyNotify ||
1062 ev->xdestroywindow.window != ccw->ccontrol.clientwindow) return;
1063
1064 /*
1065 * Client window is destroyed.
1066 */
1067
1068 /* remove all event handlers */
1069 MyRemoveAllEventHandler(XtDisplay(ccw), ccw->ccontrol.clientwindow);
1070 if (ccw->ccontrol.probewindow != None) {
1071 /* no need to destroy probewindow. it's already destroyed. */
1072 MyRemoveAllEventHandler(XtDisplay(ccw), ccw->ccontrol.probewindow);
1073 }
1074 if (ccw->ccontrol.eventselectmethod == ESMethodSelectFocus &&
1075 ccw->ccontrol.focuswindow != ccw->ccontrol.clientwindow) {
1076 MyRemoveAllEventHandler(XtDisplay(ccw), ccw->ccontrol.focuswindow);
1077 }
1078
1079 ccw->ccontrol.oldclientwindow = ccw->ccontrol.clientwindow;
1080 ccw->ccontrol.clientwindow = None;
1081 ccw->ccontrol.focuswindow = None;
1082 ccw->ccontrol.probewindow = None;
1083 ccw->ccontrol.active = False;
1084
1085 XtRemoveCallback(ccw->ccontrol.inputobj,
1086 XtNfixNotify, FixCallback, (XtPointer)ccw);
1087 XtRemoveCallback(ccw->ccontrol.inputobj,
1088 XtNendNotify, ConversionEndCallback, (XtPointer)ccw);
1089 XtRemoveCallback(ccw->ccontrol.inputobj,
1090 XtNtextChangeNotify, TextChangeCallback, (XtPointer)ccw);
1091 XtRemoveCallback(ccw->ccontrol.inputobj,
1092 XtNmodeChangeNotify, ModeChangeCallback, (XtPointer)ccw);
1093 XtRemoveCallback(ccw->ccontrol.inputobj,
1094 XtNselectionControl, SelectionControlCallback,
1095 (XtPointer)ccw);
1096 XtRemoveCallback(ccw->ccontrol.inputobj,
1097 XtNauxControl, AuxControlCallback,
1098 (XtPointer)ccw);
1099
1100 /* call input style dependent finish */
1101 (*class->conversionControl_class.Finish)((Widget)ccw);
1102
1103 /* clear conversion object */
1104 ICClearConversion(ccw->ccontrol.inputobj);
1105
1106 ccw->ccontrol.oldclientwindow = None;
1107 XtCallCallbackList((Widget)ccw, ccw->ccontrol.endcallback, (XtPointer)True);
1108 }
1109
1110 /* ARGSUSED */
1111 static void
FixCallback(w,client_data,call_data)1112 FixCallback(w, client_data, call_data)
1113 Widget w;
1114 XtPointer client_data;
1115 XtPointer call_data;
1116 {
1117 Widget widget = (Widget)client_data;
1118 ConversionControlWidget ccw = (ConversionControlWidget)widget;
1119 ConversionControlWidgetClass class = (ConversionControlWidgetClass)widget->core.widget_class;
1120 Atom encoding = ccw->ccontrol.textencoding;
1121 int format;
1122 int length;
1123 XtPointer text;
1124 CCTextCallbackArg arg;
1125
1126 ccw->ccontrol.eventused = True;
1127
1128 if (ICGetConvertedString(w, &encoding, &format, &length, &text) < 0) {
1129 return;
1130 }
1131 arg.encoding = encoding;
1132 arg.format = format;
1133 arg.length = length;
1134 arg.text = text;
1135
1136 (*class->conversionControl_class.Fix)(widget, &arg);
1137
1138 XtFree(text);
1139 }
1140
1141 /* ARGSUSED */
1142 static void
ConversionEndCallback(w,client_data,call_data)1143 ConversionEndCallback(w, client_data, call_data)
1144 Widget w;
1145 XtPointer client_data;
1146 XtPointer call_data;
1147 {
1148 ConversionControlWidget ccw = (ConversionControlWidget)client_data;
1149
1150 ccw->ccontrol.eventused = True;
1151 ccw->ccontrol.endnotify = True;
1152 }
1153
1154 /* ARGSUSED */
1155 static void
TextChangeCallback(w,client_data,call_data)1156 TextChangeCallback(w, client_data, call_data)
1157 Widget w;
1158 XtPointer client_data;
1159 XtPointer call_data;
1160 {
1161 Widget widget = (Widget)client_data;
1162 ConversionControlWidget ccw = (ConversionControlWidget)widget;
1163 ConversionControlWidgetClass class = (ConversionControlWidgetClass)widget->core.widget_class;
1164 int nsegs = ICNumSegments(ccw->ccontrol.inputobj);
1165
1166 /*
1167 * don't do:
1168 * ccw->ccontrol.eventused = True;
1169 * because this callback is often called even if
1170 * the text didn't change actually.
1171 */
1172
1173 /*
1174 * when num-segments changed from 0 to 1 (or more),
1175 * call new-text calllback
1176 */
1177 if (ccw->ccontrol.notext && nsegs > 0) {
1178 XtCallCallbackList((Widget)ccw, ccw->ccontrol.newtextcallback,
1179 (XtPointer)w);
1180 }
1181 ccw->ccontrol.notext = nsegs == 0;
1182
1183 (*class->conversionControl_class.TextChange)(widget);
1184 }
1185
1186 /* ARGSUSED */
1187 static void
ModeChangeCallback(w,client_data,call_data)1188 ModeChangeCallback(w, client_data, call_data)
1189 Widget w;
1190 XtPointer client_data;
1191 XtPointer call_data;
1192 {
1193 Widget widget = (Widget)client_data;
1194 ConversionControlWidget ccw = (ConversionControlWidget)widget;
1195 ConversionControlWidgetClass class = (ConversionControlWidgetClass)widget->core.widget_class;
1196
1197 ccw->ccontrol.eventused = True;
1198
1199 (*class->conversionControl_class.ModeChange)(widget);
1200 }
1201
1202 /* ARGSUSED */
1203 static void
SelectionControlCallback(w,client_data,call_data)1204 SelectionControlCallback(w, client_data, call_data)
1205 Widget w;
1206 XtPointer client_data;
1207 XtPointer call_data;
1208 {
1209 Widget widget = (Widget)client_data;
1210 ConversionControlWidget ccw = (ConversionControlWidget)widget;
1211 ConversionControlWidgetClass class = (ConversionControlWidgetClass)widget->core.widget_class;
1212 ICSelectionControlArg *arg = (ICSelectionControlArg *)call_data;
1213
1214 ccw->ccontrol.eventused = True;
1215
1216 (*class->conversionControl_class.SelectionControl)(widget, arg);
1217 }
1218
1219 /* ARGSUSED */
1220 static void
AuxControlCallback(w,client_data,call_data)1221 AuxControlCallback(w, client_data, call_data)
1222 Widget w;
1223 XtPointer client_data;
1224 XtPointer call_data;
1225 {
1226 Widget widget = (Widget)client_data;
1227 ConversionControlWidget ccw = (ConversionControlWidget)widget;
1228 ConversionControlWidgetClass class = (ConversionControlWidgetClass)widget->core.widget_class;
1229 ICAuxControlArg *arg = (ICAuxControlArg *)call_data;
1230
1231 ccw->ccontrol.eventused = True;
1232
1233 (*class->conversionControl_class.AuxControl)(widget, arg);
1234 }
1235
1236 static void
WidgetError(w,name,type,msg)1237 WidgetError(w, name, type, msg)
1238 Widget w;
1239 String name;
1240 String type;
1241 String msg;
1242 {
1243 char buf[512];
1244 String params[1];
1245 Cardinal num_params;
1246
1247 params[0] = XtClass(w)->core_class.class_name;
1248 num_params = 1;
1249
1250 (void)sprintf(buf, "%%s: %s", msg);
1251
1252 XtAppErrorMsg(XtWidgetToApplicationContext(w),
1253 name, type, "WidgetError", buf, params, &num_params);
1254 }
1255
1256 static void
WidgetWarning(w,name,type,msg)1257 WidgetWarning(w, name, type, msg)
1258 Widget w;
1259 String name;
1260 String type;
1261 String msg;
1262 {
1263 char buf[512];
1264 String params[1];
1265 Cardinal num_params;
1266
1267 params[0] = XtClass(w)->core_class.class_name;
1268 num_params = 1;
1269
1270 (void)sprintf(buf, "%%s: %s", msg);
1271
1272 XtAppWarningMsg(XtWidgetToApplicationContext(w),
1273 name, type, "WidgetError", buf, params, &num_params);
1274 }
1275