1 /***************************************************************************
3 TextEditor.mcc - Textediting MUI Custom Class
4 Copyright (C) 1997-2000 Allan Odgaard
5 Copyright (C) 2005-2014 TextEditor.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 TextEditor class Support Site: http://www.sf.net/projects/texteditor-mcc
21 ***************************************************************************/
27 #include <exec/memory.h>
28 #include <dos/dosextens.h>
29 #include <dos/dostags.h>
31 #include <rexx/storage.h>
32 #include <workbench/workbench.h>
33 #include <clib/alib_protos.h>
34 #include <proto/rexxsyslib.h>
35 #include <proto/muimaster.h>
36 #include <proto/intuition.h>
37 #include <proto/graphics.h>
38 #include <proto/locale.h>
39 #include <proto/exec.h>
40 #include <proto/dos.h>
46 #if !defined(__amigaos4__) || (INCLUDE_VERSION < 50)
55 HOOKPROTONH(SelectCode
, void, void *lvobj
, long **parms
)
57 struct InstData
*data
= (struct InstData
*)*parms
;
63 DoMethod(lvobj
, MUIM_List_GetEntry
, MUIV_List_GetEntry_Active
, &entry
);
67 int length
= strlen(entry
);
73 block
.startline
= data
->actualline
;
74 block
.startx
= data
->CPos_X
;
75 block
.stopline
= data
->actualline
;
76 block
.stopx
= data
->CPos_X
+length
;
77 AddToUndoBuffer(data
, ET_PASTEBLOCK
, &block
);
78 oldpos
= data
->CPos_X
;
79 data
->CPos_X
+= length
;
80 PasteChars(data
, oldpos
, data
->actualline
, length
, entry
, NULL
);
82 set(data
->SuggestWindow
, MUIA_Window_Open
, FALSE
);
83 DoMethod(lvobj
, MUIM_List_Clear
);
84 set(data
->object
, MUIA_TextEditor_AreaMarked
, FALSE
);
88 MakeStaticHook(SelectHook
, SelectCode
);
92 static BOOL
SafePutMsg(CONST_STRPTR portName
, struct Message
*msg
)
99 D(DBF_SPELL
, "put message 0x%08lx to port '%s'", msg
, portName
);
101 // forbid task switching before we look up the port
104 if((port
= FindPort(portName
)) != NULL
)
106 // send off the message while the Forbid() is still active
111 W(DBF_SPELL
, "cannot find port '%s'", portName
);
113 // permit task switching again
122 static BOOL
SendRexx(CONST_STRPTR word
, CONST_STRPTR command
)
124 struct MsgPort
*clipport
;
129 #if defined(__amigaos4__)
130 clipport
= AllocSysObjectTags(ASOT_PORT
, TAG_DONE
);
132 clipport
= CreateMsgPort();
136 struct RexxMsg
*rxmsg
;
138 if((rxmsg
= CreateRexxMsg(clipport
, NULL
, NULL
)) != NULL
)
142 snprintf(buffer
, sizeof(buffer
), command
, word
);
143 SHOWSTRING(DBF_SPELL
, buffer
);
145 rxmsg
->rm_Action
= RXCOMM
;
147 if((rxmsg
->rm_Args
[0] = (IPTR
)CreateArgstring(buffer
, strlen(buffer
))) != 0)
149 if((rxmsg
->rm_Args
[0] = CreateArgstring(buffer
, strlen(buffer
))) != 0)
152 if(SafePutMsg("REXX", (struct Message
*)rxmsg
) == TRUE
)
154 if(Wait((1 << clipport
->mp_SigBit
) | SIGBREAKF_CTRL_C
) != SIGBREAKF_CTRL_C
)
158 D(DBF_SPELL
, "ARexx result1 %ld", rxmsg
->rm_Result1
);
159 if(rxmsg
->rm_Result1
== 0)
162 DeleteArgstring((APTR
)rxmsg
->rm_Result2
);
167 DeleteArgstring((APTR
)rxmsg
->rm_Args
[0]);
170 DeleteRexxMsg(rxmsg
);
173 #if defined(__amigaos4__)
174 FreeSysObject(ASOT_PORT
, clipport
);
176 DeleteMsgPort(clipport
);
186 #if !defined(__amigaos4__) && !defined(__MORPHOS__) && !defined(__AROS__)
187 #undef WorkbenchControl
188 /// WorkbenchControl()
189 BOOL
WorkbenchControl(STRPTR name
, ...)
196 va_start(args
, name
);
197 ret
= WorkbenchControlA(name
, (struct TagItem
*)args
);
207 /// CloneSearchPath()
208 /***********************************************************************
209 This returns a duplicated search path (preferable the workbench
210 searchpath) usable for NP_Path of SystemTagList().
211 ************************************************************************/
212 static BPTR
CloneSearchPath(void)
218 if(WorkbenchBase
!= NULL
&& WorkbenchBase
->lib_Version
>= 44)
219 WorkbenchControl(NULL
, WBCTRLA_DuplicateSearchPath
, &path
, TAG_DONE
);
221 // We don't like this evil code in OS4 compile, as we should have
222 // a recent enough Workbench available
223 #if !defined(__amigaos4__)
226 struct Process
*pr
= (struct Process
*)FindTask(NULL
);
228 if(pr
->pr_Task
.tc_Node
.ln_Type
== NT_PROCESS
)
230 struct CommandLineInterface
*cli
= BADDR(pr
->pr_CLI
);
235 BPTR dir
= cli
->cli_CommandDir
;
240 struct FileLock
*lock
= BADDR(dir
);
241 struct PathNode
*node
;
244 dir2
= DupLock((BPTR
)lock
->fl_Key
);
248 /* Use AllocVec(), because this memory is freed by FreeVec()
249 * by the system later */
250 if((node
= AllocVec(sizeof(struct PathNode
), MEMF_ANY
)) == NULL
)
256 node
->pn_Lock
= dir2
;
271 /***********************************************************************
272 Free the memory returned by CloneSearchPath
273 ************************************************************************/
274 static void FreeSearchPath(BPTR path
)
280 #if !defined(__MORPHOS__)
281 if(WorkbenchBase
!= NULL
)
283 WorkbenchControl(NULL
, WBCTRLA_FreeSearchPath
, path
, TAG_DONE
);
288 #if !defined(__amigaos4__)
289 /* This is compatible with WorkbenchControl(NULL, WBCTRLA_FreeSearchPath, ...)
293 struct PathNode
*node
= BADDR(path
);
295 path
= node
->pn_Next
;
296 UnLock(node
->pn_Lock
);
308 static BOOL
SendCLI(CONST_STRPTR word
, CONST_STRPTR command
)
316 snprintf(buffer
, sizeof(buffer
), command
, word
);
318 // path maybe 0, which is allowed
319 path
= CloneSearchPath();
321 if(SystemTags(buffer
, NP_Path
, path
, TAG_DONE
) == -1)
323 W(DBF_SPELL
, "command '%s' failed, error code %ld", buffer
, IoErr());
324 FreeSearchPath(path
);
338 Object
*SuggestWindow(struct InstData
*data
)
343 window
= WindowObject
,
344 MUIA_Window_Borderless
, TRUE
,
345 MUIA_Window_CloseGadget
, FALSE
,
346 MUIA_Window_DepthGadget
, FALSE
,
347 MUIA_Window_DragBar
, FALSE
,
348 MUIA_Window_SizeGadget
, FALSE
,
349 WindowContents
, VGroup
,
354 MUIA_Group_PageMode
, TRUE
,
355 Child
, ListviewObject
,
356 MUIA_Listview_Input
, FALSE
,
357 MUIA_Listview_List
, FloattextObject
,
358 MUIA_Floattext_Text
, "Word is spelled correctly.",
359 MUIA_Frame
, MUIV_Frame_ReadList
,
360 MUIA_Background
, MUII_ListBack
,
363 Child
, lvobj
= ListviewObject
,
364 MUIA_Listview_List
, ListObject
,
365 MUIA_Frame
, MUIV_Frame_InputList
,
366 MUIA_Background
, MUII_ListBack
,
367 MUIA_List_ConstructHook
, MUIV_List_ConstructHook_String
,
368 MUIA_List_DestructHook
, MUIV_List_DestructHook_String
,
369 MUIA_List_Pool
, data
->mypool
,
375 DoMethod(lvobj
, MUIM_Notify
, MUIA_Listview_DoubleClick
, TRUE
,
376 MUIV_Notify_Self
, 3, MUIM_CallHook
, &SelectHook
, data
);
378 DoMethod(window
, MUIM_Notify
, MUIA_Window_CloseRequest
, TRUE
,
379 MUIV_Notify_Self
, 3, MUIM_Set
, MUIA_Window_Open
, FALSE
);
381 DoMethod(window
, MUIM_Notify
, MUIA_Window_Open
, MUIV_EveryTime
,
382 data
->object
, 3, MUIM_Set
, MUIA_TextEditor_PopWindow_Open
, MUIV_TriggerValue
);
384 data
->SuggestListview
= lvobj
;
392 static BOOL
LookupWord(struct InstData
*data
, CONST_STRPTR word
)
398 SHOWSTRING(DBF_SPELL
, data
->LookupCmd
);
399 SHOWSTRING(DBF_SPELL
, word
);
400 SHOWVALUE(DBF_SPELL
, data
->LookupSpawn
);
402 if(data
->LookupCmd
[0] != '\0')
404 if(data
->LookupSpawn
== FALSE
)
405 res
= SendCLI(word
, data
->LookupCmd
);
407 res
= SendRexx(word
, data
->LookupCmd
);
414 if(GetVar("Found", &buf
[0], sizeof(buf
), GVF_GLOBAL_ONLY
) != -1)
421 D(DBF_SPELL
, "cannot read ENV variable 'Found', error code %ld", IoErr());
423 // don't treat a missing "Found" variable as a failure, at least this
424 // is what previous releases did.
429 D(DBF_SPELL
, "lookup of word '%s' failed", word
);
433 W(DBF_SPELL
, "empty lookupcmd found");
441 void SuggestWord(struct InstData
*data
)
447 struct line_node
*line
= data
->actualline
;
451 if(IsAlpha(data
->mylocale
, line
->line
.Contents
[data
->CPos_X
]))
455 data
->blockinfo
.enabled
= FALSE
;
456 MarkText(data
, data
->blockinfo
.startx
, data
->blockinfo
.startline
, data
->blockinfo
.stopx
, data
->blockinfo
.stopline
);
460 SetCursor(data, data->CPos_X, line, FALSE);
464 while(data
->CPos_X
!= 0 && (IsAlpha(data
->mylocale
, line
->line
.Contents
[data
->CPos_X
-1]) || line
->line
.Contents
[data
->CPos_X
-1] == '-' || line
->line
.Contents
[data
->CPos_X
-1] == '\''))
466 GoPreviousWord(data
);
469 line
= data
->actualline
;
470 data
->blockinfo
.startx
= data
->CPos_X
;
471 data
->blockinfo
.startline
= line
;
473 line_nr
= LineToVisual(data
, line
) - 1;
474 OffsetToLines(data
, data
->CPos_X
, line
, &pos
);
475 left
= xget(_win(data
->object
), MUIA_Window_LeftEdge
);
476 top
= xget(_win(data
->object
), MUIA_Window_TopEdge
);
477 left
+= _mleft(data
->object
) + FlowSpace(data
, line
->line
.Flow
, line
->line
.Contents
+(data
->CPos_X
-pos
.x
)) + TextLength(&data
->tmprp
, line
->line
.Contents
+(data
->CPos_X
-pos
.x
), pos
.x
);
478 top
+= data
->ypos
+ (data
->fontheight
* (line_nr
+ pos
.lines
));
480 while(data
->CPos_X
< line
->line
.Length
&& (IsAlpha(data
->mylocale
, line
->line
.Contents
[data
->CPos_X
]) || line
->line
.Contents
[data
->CPos_X
] == '-' || line
->line
.Contents
[data
->CPos_X
] == '\''))
484 data
->blockinfo
.stopx
= data
->CPos_X
;
485 data
->blockinfo
.stopline
= line
;
487 data
->blockinfo
.enabled
= TRUE
;
488 MarkText(data
, data
->blockinfo
.startx
, data
->blockinfo
.startline
, data
->blockinfo
.stopx
, data
->blockinfo
.stopline
);
490 SetAttrs(data
->SuggestWindow
, MUIA_Window_Open
, FALSE
,
491 MUIA_Window_LeftEdge
, left
,
492 MUIA_Window_TopEdge
, top
,
495 if(data
->blockinfo
.stopx
-data
->blockinfo
.startx
< 256)
499 strlcpy(word
, line
->line
.Contents
+data
->blockinfo
.startx
, data
->blockinfo
.stopx
-data
->blockinfo
.startx
+1);
501 set(_win(data
->object
), MUIA_Window_Sleep
, TRUE
);
503 if(isFlagSet(data
->flags
, FLG_CheckWords
) && LookupWord(data
, word
) == TRUE
)
505 Object
*group
= (Object
*)xget(data
->SuggestWindow
, MUIA_Window_RootObject
);
507 set(group
, MUIA_Group_ActivePage
, MUIV_Group_ActivePage_First
);
509 SetAttrs(data
->SuggestWindow
, MUIA_Window_Activate
, TRUE
,
510 MUIA_Window_DefaultObject
, NULL
,
511 MUIA_Window_Open
, TRUE
,
518 if(data
->SuggestSpawn
== FALSE
)
519 res
= SendCLI(word
, data
->SuggestCmd
);
521 res
= SendRexx(word
, data
->SuggestCmd
);
527 if((fh
= Open("T:Matches", MODE_OLDFILE
)) != 0)
532 DoMethod(data
->SuggestListview
, MUIM_List_Clear
);
533 while(FGets(fh
, entry
, 128) != NULL
)
535 entry
[strlen(entry
)-1] = '\0';
536 DoMethod(data
->SuggestListview
, MUIM_List_InsertSingle
, entry
, MUIV_List_Insert_Sorted
);
540 group
= (Object
*)xget(data
->SuggestWindow
, MUIA_Window_RootObject
);
541 set(group
, MUIA_Group_ActivePage
, MUIV_Group_ActivePage_Last
);
542 SetAttrs(data
->SuggestWindow
, MUIA_Window_Activate
, TRUE
,
543 MUIA_Window_DefaultObject
, data
->SuggestListview
,
544 MUIA_Window_Open
, TRUE
,
549 set(_win(data
->object
), MUIA_Window_Sleep
, FALSE
);
554 D(DBF_ALWAYS
, "character '%lc' is non-alpha", line
->line
.Contents
[data
->CPos_X
]);
563 void SpellCheckWord(struct InstData
*data
)
567 if(data
->TypeAndSpell
== TRUE
&& data
->CPos_X
!= 0 && IsAlpha(data
->mylocale
, data
->actualline
->line
.Contents
[data
->CPos_X
-1]))
570 LONG end
= data
->CPos_X
;
571 struct line_node
*line
= data
->actualline
;
575 GoPreviousWord(data
);
577 while(data
->CPos_X
!= 0 && data
->actualline
== line
&& (data
->actualline
->line
.Contents
[data
->CPos_X
-1] == '-' || data
->actualline
->line
.Contents
[data
->CPos_X
-1] == '\''));
579 start
= data
->CPos_X
;
582 if(start
-end
< 256 && data
->actualline
== line
)
586 strlcpy(word
, &data
->actualline
->line
.Contents
[start
], end
-start
+1);
588 if(LookupWord(data
, word
) == FALSE
)
593 data
->actualline
= line
;
602 void ParseKeywords(struct InstData
*data
, const char *keywords
)
611 size_t keywordslen
= strlen(keywords
);
613 if((copy
= AllocVecPooled(data
->mypool
, (keywordslen
+1)*sizeof(char))) != NULL
)
618 strlcpy(copy
, keywords
, keywordslen
+1);
620 // count the number of words, we have one at least
624 if((p
= strchr(p
, ',')) != NULL
)
632 if((data
->Keywords
= AllocVecPooled(data
->mypool
, (wordCnt
+1)*sizeof(char *))) != NULL
)
643 if((e
= strpbrk(word
, ",")) != NULL
)
646 maxlen
= strlen(word
)+1;
647 if((data
->Keywords
[i
] = AllocVecPooled(data
->mypool
, maxlen
)) != NULL
)
648 strlcpy(data
->Keywords
[i
], word
, maxlen
);
656 while(word
!= NULL
&& i
< wordCnt
);
659 data
->Keywords
[i
] = NULL
;
662 FreeVecPooled(data
->mypool
, copy
);
671 void FreeKeywords(struct InstData
*data
)
675 if(data
->Keywords
!= NULL
)
679 while(data
->Keywords
[i
] != NULL
)
681 FreeVecPooled(data
->mypool
, data
->Keywords
[i
]);
685 FreeVecPooled(data
->mypool
, data
->Keywords
);
686 data
->Keywords
= NULL
;
693 /// CheckSingleWordAgainstKeywords
694 void CheckSingleWordAgainstKeywords(struct InstData
*data
, const char *word
)
698 D(DBF_SPELL
, "check word '%s' against keywords", word
);
703 while(data
->Keywords
[i
] != NULL
)
705 if(data
->Keywords
[i
][0] == '.')
707 // check a file name extension at the end of the word
708 char *p
= strchr(word
, '.');
710 if((p
= strchr(word
, '.')) != NULL
&& stricmp(p
, data
->Keywords
[i
]) == 0)
712 D(DBF_SPELL
, "matched keyword '%s'", data
->Keywords
[i
]);
713 set(data
->object
, MUIA_TextEditor_MatchedKeyword
, data
->Keywords
[i
]);
719 // check for a complete word
720 if(stricmp(word
, data
->Keywords
[i
]) == 0)
722 D(DBF_SPELL
, "matched keyword '%s'", data
->Keywords
[i
]);
723 set(data
->object
, MUIA_TextEditor_MatchedKeyword
, data
->Keywords
[i
]);
737 void KeywordCheck(struct InstData
*data
)
741 if(data
->Keywords
!= NULL
)
743 ULONG start
= data
->CPos_X
;
744 char *contents
= data
->actualline
->line
.Contents
;
747 // extract the last entered word
748 while(start
> 0 && contents
[start
-1] != ' ')
751 strlcpy(word
, &contents
[start
], MIN(sizeof(word
), data
->CPos_X
-start
+1));
752 CheckSingleWordAgainstKeywords(data
, word
);