1 /***************************************************************************
3 BetterString.mcc - A better String gadget MUI Custom Class
4 Copyright (C) 1997-2000 Allan Odgaard
5 Copyright (C) 2005-2013 by BetterString.mcc Open Source Team
7 This library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the License, or (at your option) any later version.
12 This library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 BetterString class Support Site: http://www.sf.net/projects/bstring-mcc/
21 ***************************************************************************/
25 #include <clib/alib_protos.h>
26 #include <proto/exec.h>
27 #include <proto/intuition.h>
28 #include <proto/muimaster.h>
29 #include <proto/graphics.h>
30 #include <proto/utility.h>
31 #include <proto/locale.h>
35 static IPTR
mNew(struct IClass
*cl
, Object
*obj
, struct opSet
*msg
)
39 if((obj
= (Object
*)DoSuperMethodA(cl
, obj
, (Msg
)msg
)) != NULL
)
41 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
43 if((data
->Contents
= AllocContentString(40)) != NULL
)
47 set(obj
, MUIA_FillArea
, FALSE
);
49 // muimaster V20 is MUI 3.9
50 data
->mui39
= LIB_VERSION_IS_AT_LEAST(MUIMasterBase
, 20, 0);
51 // everything beyond muimaster 20.5500 is considered to be MUI4
52 data
->mui4x
= LIB_VERSION_IS_AT_LEAST(MUIMasterBase
, 20, 5500);
54 data
->locale
= OpenLocale(NULL
);
56 if((tag
= FindTagItem(MUIA_Background
, msg
->ops_AttrList
)) != NULL
)
58 setFlag(data
->Flags
, FLG_OwnBackground
);
59 data
->OwnBackground
= (STRPTR
)tag
->ti_Data
;
62 mSet(cl
, obj
, (struct opSet
*)msg
);
68 CoerceMethod(cl
, obj
, OM_DISPOSE
);
77 static IPTR
mDispose(struct IClass
*cl
, Object
*obj
, Msg msg
)
79 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
83 if(isFlagSet(data
->Flags
, FLG_WindowSleepNotifyAdded
))
85 E(DBF_INPUT
, "MUIA_Window_Sleep notify still active at OM_DISPOSE!!");
88 FreeContentString(data
->Contents
);
89 data
->Contents
= NULL
;
91 FreeContentString(data
->Original
);
92 data
->Original
= NULL
;
94 FreeContentString(data
->Undo
);
97 if(data
->FNCBuffer
!= NULL
)
99 SharedPoolFree(data
->FNCBuffer
);
100 data
->FNCBuffer
= NULL
;
103 if(data
->locale
!= NULL
)
105 CloseLocale(data
->locale
);
110 return DoSuperMethodA(cl
, obj
, msg
);
113 static IPTR
mExport(struct IClass
*cl
, Object
*obj
, struct MUIP_Export
*msg
)
115 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
120 if((id
= (muiNotifyData(obj
)->mnd_ObjectID
)) != 0)
121 DoMethod(msg
->dataspace
, MUIM_Dataspace_Add
, data
->Contents
, strlen(data
->Contents
)+1, id
);
127 static IPTR
mImport(UNUSED
struct IClass
*cl
, Object
*obj
, struct MUIP_Import
*msg
)
133 if((id
= (muiNotifyData(obj
)->mnd_ObjectID
)) != 0)
135 STRPTR contents
= (STRPTR
)DoMethod(msg
->dataspace
, MUIM_Dataspace_Find
, id
);
138 set(obj
, MUIA_String_Contents
, contents
);
145 void AddWindowSleepNotify(struct IClass
*cl
, Object
*obj
)
147 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
151 // we must check for a successful MUIM_Setup, because this function might be called during
152 // OM_NEW and _win(obj) is not yet valid at that time
153 if(isFlagClear(data
->Flags
, FLG_WindowSleepNotifyAdded
) && isFlagSet(data
->Flags
, FLG_Setup
) && _win(obj
) != NULL
)
155 if(data
->SelectOnActive
== TRUE
|| isFlagSet(data
->Flags
, FLG_ForceSelectOn
))
158 // Ugly workaround for an ancient bug in MUI
159 // MUIbase <= 2.11 adds some notifies for certain attributes which in turn
160 // modify MUIA_Window_Sleep and hence trigger our own notify for this
162 // Removing our own notify will not remove it immediately but mark it as
163 // "killed" only by MUI. The removal happens when the notifies are checked
164 // for triggers. The problem arises if executing one notify triggers yet
165 // another notification handling on the same object. In this case the nested
166 // call will do the same removal as the first call is about to do next. This
167 // will cause a double Remove() and double free of memory later in the first
168 // call as here the pointer to the next notify to be handled has already been
169 // obtained and will be used without further checks in the next iteration.
170 // All this only happens if the removed notify directly follows the notify
171 // which causes the removal. Thus we add a dummy notify to produce a "hole"
172 // in the notify list and to let the nested notification check do its
173 // removal work without causing a bad impact on the first check. This "hole"
174 // just consists of another notify which never gets triggered. And even if
175 // it would get triggered it will not cause a nested notify check. Thus the
176 // first check will see this "hole" first before finally skipping the just
178 // NOTE: this is neither a bug in MUIbase nor in BetterString but an ancient
179 // bug in MUI itself as it does not take into account that a set() may cause
180 // nested notifications which in turn may be removed inbetween!
183 #if defined(__amigaos3__) || defined(__amigaos4__)
184 if(LIB_VERSION_IS_AT_LEAST(MUIMasterBase
, 20, 5824))
186 // MUI4 for AmigaOS is safe for V20.5824+
189 else if(LIB_VERSION_IS_AT_LEAST(MUIMasterBase
, 20, 2346) && LIBREV(MUIMasterBase
) < 5000)
191 // MUI3.9 for AmigaOS is safe for V20.2346+
196 // MUI 3.8 and older version of MUI 3.9 or MUI4 are definitely unsafe
197 safeNotifies
= FALSE
;
200 // MorphOS and AROS must be considered unsafe unless someone from the
201 // MorphOS/AROS team confirms that removing notifies in nested OM_SET
203 safeNotifies
= FALSE
;
206 // add the dummy notify only once
207 if(safeNotifies
== FALSE
&& isFlagClear(data
->Flags
, FLG_DummyNotifyAdded
))
209 // add a notify for an attribute which will *NEVER* be modified, thus the
210 // trigger action will never be executed as well
211 DoMethod(_win(obj
), MUIM_Notify
, MUIA_BetterString_Nop
, MUIV_EveryTime
, obj
, 5, MUIM_Set
, MUIA_NoNotify
, TRUE
, MUIA_BetterString_Nop
, MUIV_TriggerValue
);
212 setFlag(data
->Flags
, FLG_DummyNotifyAdded
);
213 D(DBF_INPUT
, "added dummy notify");
216 // If the "select on active" feature is active we must be notified in case our
217 // window is put to sleep to be able to deactivate the feature, because waking
218 // the window up again will let ourself go active again and we will select the
219 // complete content, even if it was not selected before. See YAM ticket #360
221 // We must use a private attribute here, because the public attribute will remove
222 // the notify again as soon as it is triggered.
223 DoMethod(_win(obj
), MUIM_Notify
, MUIA_Window_Sleep
, MUIV_EveryTime
, obj
, 3, MUIM_Set
, MUIA_BetterString_InternalSelectOnActive
, MUIV_NotTriggerValue
);
224 setFlag(data
->Flags
, FLG_WindowSleepNotifyAdded
);
225 D(DBF_INPUT
, "added MUIA_Window_Sleep notify");
232 void RemWindowSleepNotify(struct IClass
*cl
, Object
*obj
)
234 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
238 // we must check for a successful MUIM_Setup, because this function might be called during
239 // OM_NEW and _win(obj) is not yet valid at that time
240 if(isFlagSet(data
->Flags
, FLG_WindowSleepNotifyAdded
) && isFlagSet(data
->Flags
, FLG_Setup
) && _win(obj
) != NULL
)
242 // remove the notify again
243 D(DBF_INPUT
, "remove MUIA_Window_Sleep notify");
244 if(DoMethod(_win(obj
), MUIM_KillNotifyObj
, MUIA_Window_Sleep
, obj
) == 0)
245 E(DBF_INPUT
, "removing MUIA_Window_Sleep notify failed?");
246 clearFlag(data
->Flags
, FLG_WindowSleepNotifyAdded
);
252 static IPTR
mSetup(struct IClass
*cl
, Object
*obj
, struct MUI_RenderInfo
*rinfo
)
254 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
259 InitConfig(obj
, data
);
261 if(DoSuperMethodA(cl
, obj
, (Msg
)rinfo
))
263 // tell MUI we know how to indicate the active state
264 _flags(obj
) |= (1<<7);
266 // remember that we went through MUIM_Setup
267 setFlag(data
->Flags
, FLG_Setup
);
269 data
->ehnode
.ehn_Priority
= 0;
270 data
->ehnode
.ehn_Flags
= MUI_EHF_GUIMODE
;
271 data
->ehnode
.ehn_Object
= obj
;
272 data
->ehnode
.ehn_Class
= cl
;
273 data
->ehnode
.ehn_Events
= IDCMP_MOUSEBUTTONS
| IDCMP_RAWKEY
;
275 // setup the selection pointer
276 if(data
->SelectPointer
== TRUE
)
278 data
->ehnode
.ehn_Events
|= IDCMP_MOUSEMOVE
;
279 SetupSelectPointer(data
);
282 DoMethod(_win(obj
), MUIM_Window_AddEventHandler
, &data
->ehnode
);
284 AddWindowSleepNotify(cl
, obj
);
290 FreeConfig(muiRenderInfo(obj
), data
);
297 static IPTR
mCleanup(struct IClass
*cl
, Object
*obj
, Msg msg
)
299 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
303 // cleanup the selection pointer
304 CleanupSelectPointer(data
);
306 RemWindowSleepNotify(cl
, obj
);
308 DoMethod(_win(obj
), MUIM_Window_RemEventHandler
, &data
->ehnode
);
310 FreeConfig(muiRenderInfo(obj
), data
);
312 // forget that we went through MUIM_Setup
313 clearFlag(data
->Flags
, FLG_Setup
);
316 return DoSuperMethodA(cl
, obj
, msg
);
319 static IPTR
mAskMinMax(struct IClass
*cl
, Object
*obj
, struct MUIP_AskMinMax
*msg
)
321 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
326 DoSuperMethodA(cl
, obj
, (Msg
)msg
);
328 Height
= _font(obj
)->tf_YSize
;
329 msg
->MinMaxInfo
->MinHeight
+= Height
;
330 msg
->MinMaxInfo
->DefHeight
+= Height
;
331 msg
->MinMaxInfo
->MaxHeight
+= Height
;
337 SetFont(&data
->rport
, _font(obj
));
338 width
= data
->Width
* TextLength(&data
->rport
, "n", 1);
340 msg
->MinMaxInfo
->MinWidth
+= width
;
341 msg
->MinMaxInfo
->DefWidth
+= width
;
342 msg
->MinMaxInfo
->MaxWidth
+= width
;
346 msg
->MinMaxInfo
->MinWidth
+= 10;
347 msg
->MinMaxInfo
->DefWidth
+= 100;
348 msg
->MinMaxInfo
->MaxWidth
+= MBQ_MUI_MAXMAX
;
355 static IPTR
mShow(struct IClass
*cl
, Object
*obj
, Msg msg
)
357 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
358 struct BitMap
*friendBMp
= _rp(obj
)->BitMap
;
359 WORD width
, height
, depth
;
363 DoSuperMethodA(cl
, obj
, msg
);
365 width
= _mwidth(obj
);
366 height
= _font(obj
)->tf_YSize
;
367 depth
= ((struct Library
*)GfxBase
)->lib_Version
>= 39 ? GetBitMapAttr(friendBMp
, BMA_DEPTH
) : friendBMp
->Depth
;
369 InitRastPort(&data
->rport
);
370 data
->rport
.BitMap
= MUIG_AllocBitMap(width
+40, height
, depth
, 0, friendBMp
);
371 SetFont(&data
->rport
, _font(obj
));
372 SetDrMd(&data
->rport
, JAM1
);
374 setFlag(data
->Flags
, FLG_Shown
);
380 static IPTR
mHide(struct IClass
*cl
, Object
*obj
, Msg msg
)
382 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
386 clearFlag(data
->Flags
, FLG_Shown
);
388 // hide the selection pointer
389 HideSelectPointer(obj
, data
);
391 MUIG_FreeBitMap(data
->rport
.BitMap
);
394 return DoSuperMethodA(cl
, obj
, msg
);
397 static IPTR
mDraw(struct IClass
*cl
, Object
*obj
, struct MUIP_Draw
*msg
)
401 DoSuperMethodA(cl
, obj
, (Msg
)msg
);
403 if(isFlagSet(msg
->flags
, MADF_DRAWUPDATE
) || isFlagSet(msg
->flags
, MADF_DRAWOBJECT
))
405 PrintString(cl
, obj
);
412 static IPTR
mHandleEvent(struct IClass
*cl
, Object
*obj
, struct MUIP_HandleEvent
*msg
)
414 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
419 if(isFlagSet(data
->Flags
, FLG_Ghosted
) || isFlagClear(data
->Flags
, FLG_Shown
))
425 ULONG display_pos
= data
->DisplayPos
;
427 result
= mHandleInput(cl
, obj
, msg
);
428 if(display_pos
!= data
->DisplayPos
)
429 set(obj
, MUIA_String_DisplayPos
, data
->DisplayPos
);
431 if(!result
&& data
->ForwardObject
!= NULL
)
438 attr
= MUIV_List_Active_Top
;
442 attr
= MUIV_List_Active_Bottom
;
446 attr
= MUIV_List_Active_Up
;
450 attr
= MUIV_List_Active_Down
;
454 attr
= MUIV_List_Active_PageUp
;
457 case MUIKEY_PAGEDOWN
:
458 attr
= MUIV_List_Active_PageDown
;
464 set(data
->ForwardObject
, MUIA_List_Active
, attr
);
465 result
= MUI_EventHandlerRC_Eat
;
474 static IPTR
mGoActive(struct IClass
*cl
, Object
*obj
, UNUSED Msg msg
)
476 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
480 D(DBF_INPUT
, "GoActive: %08lx %08lx", obj
, data
->Flags
);
482 FreeContentString(data
->Original
);
483 if((data
->Original
= AllocContentString(strlen(data
->Contents
)+1)) != NULL
)
484 strlcpy(data
->Original
, data
->Contents
, strlen(data
->Contents
+1));
486 // select everything if this is necessary or requested
487 if((data
->SelectOnActive
== TRUE
&& isFlagClear(data
->Flags
, FLG_ForceSelectOff
)) ||
488 isFlagSet(data
->Flags
, FLG_ForceSelectOn
))
490 // If the active flag is still clear we have been activated by keyboard or by
491 // the application. Otherwise this method is called due to activation by mouse
492 // and we must skip the "select on active" stuff as this has been done already.
493 if(isFlagClear(data
->Flags
, FLG_Active
))
495 DoMethod(obj
, MUIM_BetterString_DoAction
, MUIV_BetterString_DoAction_SelectAll
);
499 // now declare ourself as active
500 setFlag(data
->Flags
, FLG_Active
);
501 setFlag(data
->Flags
, FLG_FreshActive
);
503 if(isFlagClear(data
->Flags
, FLG_OwnBackground
) && data
->mui4x
== FALSE
)
504 set(obj
, MUIA_Background
, data
->ActiveBackground
);
505 else if(data
->mui4x
== TRUE
)
506 set(obj
, MUIA_Background
, MUII_StringActiveBack
);
508 MUI_Redraw(obj
, MADF_DRAWUPDATE
);
514 static IPTR
mGoInactive(struct IClass
*cl
, Object
*obj
, UNUSED Msg msg
)
516 struct InstData
*data
= (struct InstData
*)INST_DATA(cl
, obj
);
520 D(DBF_INPUT
, "GoInActive: %08lx", obj
);
522 // clean an eventually marked block and the
523 // active state flag of the gadget
524 clearFlag(data
->Flags
, FLG_BlockEnabled
);
525 clearFlag(data
->Flags
, FLG_Active
);
526 clearFlag(data
->Flags
, FLG_FreshActive
);
528 if(isFlagSet(data
->Flags
, FLG_OwnBackground
))
530 set(obj
, MUIA_Background
, data
->OwnBackground
);
531 // MUI 3.8 needs an explicit refresh
532 if(data
->mui39
== FALSE
&& data
->mui4x
== FALSE
)
533 MUI_Redraw(obj
, MADF_DRAWUPDATE
);
535 else if(data
->mui4x
== TRUE
)
536 set(obj
, MUIA_Background
, MUII_StringBack
);
538 set(obj
, MUIA_Background
, data
->InactiveBackground
);
544 DISPATCHER(_Dispatcher
)
550 switch(msg
->MethodID
)
553 result
= mNew(cl
, obj
, (struct opSet
*)msg
);
557 result
= mSetup(cl
, obj
, (struct MUI_RenderInfo
*)msg
);
561 result
= mShow(cl
, obj
, msg
);
565 result
= mAskMinMax(cl
, obj
, (struct MUIP_AskMinMax
*)msg
);
569 result
= mDraw(cl
, obj
, (struct MUIP_Draw
*)msg
);
573 result
= mGet(cl
, obj
, (struct opGet
*)msg
);
577 mSet(cl
, obj
, (struct opSet
*)msg
);
578 result
= DoSuperMethodA(cl
, obj
, msg
);
581 case MUIM_HandleEvent
:
582 result
= mHandleEvent(cl
, obj
, (struct MUIP_HandleEvent
*)msg
);
586 result
= mGoActive(cl
, obj
, msg
);
589 case MUIM_GoInactive
:
590 result
= mGoInactive(cl
, obj
, msg
);
594 result
= mHide(cl
, obj
, msg
);
598 result
= mCleanup(cl
, obj
, msg
);
602 result
= mDispose(cl
, obj
, msg
);
606 result
= mExport(cl
, obj
, (struct MUIP_Export
*)msg
);
610 result
= mImport(cl
, obj
, (struct MUIP_Import
*)msg
);
613 case MUIM_BetterString_ClearSelected
:
615 // forward the clear request to our new DoAction method
616 // which in fact will do the very same, but a bit more clever
617 DoMethod(obj
, MUIM_BetterString_DoAction
, MUIV_BetterString_DoAction_Delete
);
622 case MUIM_BetterString_Insert
:
623 result
= mInsert(cl
, obj
, (struct MUIP_BetterString_Insert
*)msg
);
626 case MUIM_BetterString_DoAction
:
627 result
= mDoAction(cl
, obj
, (struct MUIP_BetterString_DoAction
*)msg
);
630 case MUIM_BetterString_FileNameStart
:
631 result
= mFileNameStart((struct MUIP_BetterString_FileNameStart
*)msg
);
635 result
= DoSuperMethodA(cl
, obj
, msg
);