The Eddie Plugin Developer Kit contains documentation, API definitions and sample code that
allows you to write plugins for Eddie. The plugin API that Eddie provides is fairly rich and
allows a great level of control and customization over the application.
The plugin API allows you to, among other things:
The plugin API has a versioning mechanism that allows
backward and forward compatibility of plugins and Eddie.
The plugin API is fully defined in a single header file, Plugin.h
. This header
file is included in the Plugin Developer Kit you have.
If you are missing a call that would allow you to implement some plugin functionality or if you have any other feedback, contact me at pavel@be.com.
This document describes plugin API as defined in Eddie 1.2.1. This or later versions of Eddie are available at ftp://ftp.be.com/pub/contrib/editors/Eddie1.2.1_ppc.zip and ftp://ftp.be.com/pub/contrib/editors/Eddie1.2.1_x86.zip.
Currently the Plugin Developer Kit includes three sample plugins.
To build the plugins:
Go into the Eddie Worksheet
cd .../PluginDeveloperKit
to get into the PluginDeveloperKit directory
type make
copy the resulting plugins into the Eddie plugins directory
re-launch Eddie
This is an actual usable plugin, a C/C++ commenter. The code sample illustrates how to scan and
modify document text, add a button, publish editor primitives, run a popup menu. The source for
this plugin is in the file Commenter.cpp
.
This plugin is a torso of what could be Emacs-like Electric-C editing mode - a plugin that
formats your source code as you type. It doesn't even come close to doing anything useful, however
it illustrates how to intercept key down events, plus it shows how to add a preference panel to
the Application Settings. The source for this plugin is in the file KeyPressHandling.cpp
This sample plugin is intended to be a source for cutting and pasting code into your plugins.
It illustrates how to handle a mouse click, how to swap button icons, display a clipboard panel,
handle a drag&drop of a bitmap by pasting the bits as a hex dump into the document.
The source for this plugin is in the file CutNPasteSource.cpp
.
Let's look at how to build a plugin, starting from the end, which is:
Eddie plugins are shared libraries. To build
an Eddie plugin you need to include the -G linker option in the makefile. The resulting type of the
plugin needs to be "application/x-be-executable"
on PPC and
"application/x-vnd.Be-peexecutable"
on Intel. For example, a typical makefile link
sequence for Intel will look like:
LIBRARIES = \ $(BELIBRARIES)/glue-noinit.a \ $(BELIBRARIES)/init_term_dyn.o \ $(BELIBRARIES)/start_dyn.o \ $(BELIBRARIES)/libroot.so.LIB \ $(BELIBRARIES)/libbe.so.LIB \ # somePlugin: $(pluginObjectFiles) $(LD) -o $@ $(pluginObjectFiles) -G $(LIBRARIES) settype -t "application/x-vnd.Be-peexecutable" $@ chmod +x $@ mimeset -f $@where pluginObjectFiles are .o files compiled from your plugin source.
Every plugin has to support two calls in order to get recognized by Eddie.
PluginInterface::PluginResult PluginMain(PluginInterface::PluginCallSelector selector, PluginInterface *pluginInterface); const char *PluginHelp(void);
These calls have to have the exact signature as shown here and as declared in Plugin.h
.
If the plugin doesn't have either of these two calls, it fails to load. Note that the plugin
does not need a main
function.
PluginMain is the main entry point of a plugin. It gets called periodically during the lifetime
of a plugin. The selector parameter indicates the reason
PluginMain was called. The pluginInterface parameter
is the structure used to communicate between a plugin and
Eddie. Here are some of the instances when PluginMain gets always called:
The selector parameter always contains the reason for which the plugin was called.
PluginMain returns a PluginInterface::PluginResult value, which may be one of:
kIgnored | A plugin selector call was ignored by the plugin. |
kAccepted | A plugin selector call was handled fine by the plugin. |
kExhausted | A plugin selector call was handled fully by the plugin. The subject of the selector call (such as a message) does not need any further handling from Eddie or any other plugin. |
kFailed | There was an error during a plugin selector call. The plugin can no longer operate. If a plugin returns this result, it will no longer be called. |
PluginHelp returns a short description of the plugin. This description is for instance used when showing a tool tip for a plugin button. It should include short instructions on using the plugin, such as modifier keys used to access the different features, etc.
The PluginMain gets called repeatedly, the selector parameter describes the reason a plugin got called. Most of the calls to the plugin are implicit, the plugin receives them whether it is interrested in them or not. A few calls need to be explicitly requested by the plugin.
kGetPluginVersion | First call a plugin receives, determines if a plugin can be used with Eddie and vice versa.
A plugin is required to respond to this call in order to get further selector calls.
example |
kInit | Called when Eddie starts up. A plugin may initialize it's state, register itself as
a handler for specific settings, keyboard primitives, etc.
example |
kUnload | Called when plugins are unloaded. Plugins should release any resources they acquired during their lifetime, such as dynamically allocated buffers. |
kQuit | Called when Eddie quits. |
kWindowOpened | Called when a new document window is opened - this is the time to install buttons if your plugin
has any.
example |
kWindowClosed | Called when document window is being closed. |
kWindowActivated | Called when document window brought to front. |
kWindowDeactivated | Called when document window sent to the background. |
kDocumentOpened | Called when a new document opened. In multiple document windows called for every document. The DocumentUniqueID call is used to determine which document it is being called on. |
kDocumentClosed | Called when document closed. |
kDocumentActivated | Called when a document was activated. in single document windows this is pretty much like kWindowActivated, if a window has multiple documents this gets called when one of the documents is made the current focus. The DocumentUniqueID call is used to determine which document it is being called on. |
kDocumentTextChanged | Called when document text in window changed - when text was inserted and/or deleted. Calls
ChangeStart,
RemovedLength and
InsertedLength provide the details about the
document change.
Plugin must explicitly request this selector call.
example1, example2 |
kDocumentStateChanged | Called when document file renamed, saved, made read-only, writable, etc. |
kArgvPassed | Called when shell command handled by this plugin is invoked, currently used for
handling plugin settings and UserStartup entries.
Eddie saves settings as argv-style commands in the settings file. When reading a
settings file, Eddie passes any settings that the plugin registered to handle
in argv-like format, accessible with the CurrentArgv
call.
example |
kButtonClick | Called when plugin button clicked.
example |
kButtonPress | Called when plugin button pressed and held. This call can be used for opeining a popup menu, etc. |
kPrimitiveInvoked | Called after a keyboard shortcut associated with a primitive function handled by this
plugin was invoked.
example1 |
kButtonDraw | Called after a plugin button gets redrawn. Plugin must explicitly request this selector call. |
kPulse | Called during a pulse event of the text view. Plugin must explicitly request this selector call. |
kDraw | Called right after a draw call to the text view. Plugin must explicitly request this selector call. |
kMessage | Called from the beginning of the MessageReceived call of the current text view. Plugin can for instance use this selector call to handle messages dropped onto a document. Plugin must explicitly request this selector call. Return kExhausted from plugin if the message is completely consumed in the call. |
kMouseDown | Called when a text view got a MouseDown event. The Point call can be used to determine where. Plugin must explicitly request this selector call. |
kKeyDown | Called from the beginning of the KeyDown call of the current text view. The plugin can use KeyBytes to figure out which key was pressed. Plugin must explicitly request this selector call. |
kGlobalSettingsSaved | Called when settings for the whole application are being saved, plugin
may save it's own settings using the WriteSettings call.
example |
kDocumentSettingsSaved | Called when settings are being saved for the whole document, plugin may save it's own settings as attributes in the documents node. |
kGlobalPrefsShowing | Called when the Application Settings
window is being shown, plugin may add it's own settings panel.
example |
kDocumentPrefsShowing | Called when the document settings window is being shown. This selector not implemented yet. |
The PluginInterface parameter of PluginMain is the main communication structure between a plugin and Eddie. PluginInterface is a proxy class that contains data structures, virtual and non-virtual functions allowing the plugin to interact with Eddie. When calling plugins, Eddie sets up the PluginInterface structure to contain information about the current document, etc. Different selector calls will get specific data passed in the PluginInterface structure. For instance when the kDocumentTextChanged selector is called, the offset, size of removed and size of inserted text is available in the PluginInterface. Most of the data in PluginInterface is accessible through simple getter calls.
We will start with calls that a plugin can use to obtain more details about a selector call in progress.
void ReturnSupportedPluginVersion(uint32 version = kEddieToPluginInterfaceVersion); void ReturnRequiredPluginVersion(uint32 version = kPluginToEddieInterfaceVersion);
During kGetPluginVersion use these to obtain the plugin API version your plugin supports and
the call set version it requires from Eddie.
example
BWindow *Window() const;
Returns the window in which a plugin is being called. Valid during every selector call except for kInit, kQuit, kGlobalSettingsSaved and kGlobalPrefsShowing
uint32 DocumentUniqueID() const;
Returns a unique ID of the active document. Future versions of Eddie will support multiple documents in a single window. Unlike window pointers that may get reused when a window gets closed and a new one opened, document IDs are unique for each document. If you are storing some data in your plugin that needs to be matched up with a document (for instance a table of structures supporting a syntax highliting plugin), a document ID is what you use. The document unique ID is valid during these selector calls: kDocumentOpened, kDocumentClosed, kDocumentTextChanged, kDocumentStateChanged, kDocumentActivated, kWindowActivated, kMouseDown, kKeyDown, kPulse, kDraw, kMessage, kButtonDraw, kButtonPress, kButtonClick
uint32 ChangeStart() const; uint32 RemovedLength() const; uint32 InsertedLength() const;
Used during kDocumentTextChanged selector call.
Returns the offset in the text at which a
change occurred, the length of text removed and the length of text inserted. Not guaranteed to return
any valid value outside the kDocumentTextChanged selector call.
example1,
example2
uint32 ClickSelector() const;
Used during the kButtonClick or
kButtonPress selector calls, if you have a button that
is split vertically or horizontally to determine which part of the button was pressed.
example
BRect ButtonRedrawRect() const; bool Inverted() const;
In a kButtonDraw selector call these can be used to determine the rectangle of the plugin button being drawn and if it is being drawn inverted.
const char *KeyBytes() const; uint32 NumKeyBytes() const;
In a kKeyDown selector call these can be used to determine which key was pressed.
BPoint Point() const;
During a kMouseDown selector call the point of the mouse click can be determined using this call.
BMessage *CurrentMessage() const;
During a kMessage selector call the current message can be examined and handled.
BRect CurrentDrawRect() const;
During a kDraw selector call after the document text is redrawn, the rectangle of the draw rect can be obtained
BRect PrefsViewRect() const;
During a kGlobalPrefsShowing selector
call the rectangle of the preference panel can be obtained. The panel can then be allocated
as a view and installed using the AddAppPrefsPanel
and subclass of PrefsPanel proxy.
example
virtual void RequireSelectors(uint32 selectorMask);
Used during button initialization to request non-default selector calls. Non-default selectors are called fairly often and in a system with a large number of plugins installed and a number of windows open could take a non negligible amount of time to execute, possibly slowing down the response time of the editor. Therefore only the plugins that actually need these selector calls ask for them using the RequireSelector calls and get the corresponding selector calls as a result. This is the list of the non-default selector calls that your plugin must specifically request, along with the mask values used to request the selector.
kButtonDraw | kRequestButtonDrawMask |
kPulse | kRequestPulseMask |
kDraw | kRequestDrawMask |
kMessage | kRequestMessageMask |
kMessage | kRequestMessageMask |
kMouseDown | kRequestMouseDownMask |
kKeyDown | kRequestKeyDownMask |
kDocumentTextChanged | kRequestDocumentChangedMask |
virtual void SetupPluginButton(const unsigned char *buttonBits, const unsigned char *pressedButtonBits); virtual void SetupPluginButton(const unsigned char *buttonBits, const unsigned char *pressedButtonBits, uint32 splitMode); virtual void ChangeButtonBitmaps(const unsigned char *buttonBits, const unsigned char *pressedButtonBits, const BWindow * thisWindowOnly = 0); virtual void ChangeButtonBitmaps(const unsigned char *buttonBits, const unsigned char *pressedButtonBits, uint32 splitMode, const BWindow *thisWindowOnly = 0); virtual void AnimateButtonClick(int32 selector = 0);
SetupPluginButton can be used to install buttons in a new window, should be therefore called
during the kWindowOpened selector. If you are using
split buttons, use the second version which allows you to specify the splitting mode. ChangeButtonBitmaps
can be used to change a button bitmap to indicate a plugin state change. The Magic Prototyper uses this
call to change the button when a prototyper clipboard gets loaded. The thisWindowOnly is currently unused.
AnimateButtonClick can be used to give a positive feedback when invoking a plugin function using
a keyboard shortcut.
example1,
example2,
example3
virtual void GetSelection(int32 *start, int32 *end) const; virtual void Select(int32 start, int32 end);
Lets you get/set the selection in the current document.
example1,
example2,
example3
virtual void Insert(const char *text, int32 length, const char *undoName = 0); virtual void Clear(const char *undoName = 0);
Insert lets you insert text into the current selection in the current document. Clear lets
you delete the current selection in the current document. You may specify an undo name under
which the edit will appear in the undo menu.
example1,
example2,
example3
virtual void CurrentUndoInsert(const char *text, int32 length); virtual void CurrentUndoClear(); virtual void StartUndo(int32 restoreSelStartTo, int32 restoreSelEndTo, const char *undoName = 0); virtual void SealUndo();
CurrentUndoInsert and CurrentUndoClear are the same as Insert
and Clear, except they
allow you to group several edits into a single composite undo. StartUndo opens the undo and gives
it a name. SealUndo closes the undo, any subsequent edit will start a new undo record.
restoreSelStartTo
and restoreSelEndTo
is the position of the
selection after the user invokes undo. In most cases you should pass the values of
the selection right before the first edit of the composite undo.
example
virtual char CharAt(int32 offset) const; virtual void GetText(char *buffer, int32 offset, int32 length) const;
CharAt returns the character at the specified offset in the current document. GetText makes
a copy of a specified ammount of characters form offset into a buffer you supply.
example1,
example2,
example3
virtual int32 CountLines() const; virtual int32 CurrentLine() const; virtual void GoToLine(int32 lineNum);
Return the number of lines, the number of the line the selection starts on. GoToLine selects a specified line. Lines are numbered starting at zero.
virtual int32 StartOfLine(int32 position) const; virtual int32 EndOfLine(int32 position) const;
Given an offset in text, these calls return the offset of the start and end of the line at the offset, respectively.
virtual void ScrollToOffset(int32 inOffset); virtual void ScrollToSelection();
Scroll to a specified offset or to the current selection.
example
virtual rgb_color TextColor() const; virtual rgb_color BackgroundColor() const;
Returns the text and background colors in the current document.
virtual void SetColor(rgb_color color); virtual void StartSettingColorOnRange(); virtual void SetOneColorOnRange(rgb_color color, int32 start, int32 end); virtual void EndSettingColorOnRange();
Color setting calls used by syntax coloring plugins, etc. SetColor sets the specified color on the current selection. StartSettingColorOnRange, SetOneColorOnRange and EndSettingColorOnRange are used for setting color on multiple parts of a document at once for maximum performance. The updates for each color change are postponed till the EndSettingColorOnRange call.
virtual bool SyntaxColoring() const;
Returns true if SyntaxColoring is on for a document.
virtual void GetMouse(BPoint *where, uint32 *buttons, bool checkMessageQueue = true) const;
Returns the mouse position and button states.
virtual int32 PointToOffset(BPoint) const;
Converts a point to an offset in text.
virtual BRect TextViewBounds() const; virtual BRect WindowBounds() const;
Returns bounds of a document text and a document window.
virtual BRect GetButtonRect() const;
Returns the window relative location of the plugin button. May be used for instance for positioning a popup menu over the button during a button press.
virtual const char *PluginName() const;
Returns the name of this plugin.
virtual const BFile *DocumentNode() const; virtual const entry_ref *DocumentRef() const;
DocumentNode returns the BFile used to save the document or NULL if document wasn't saved yet. DocumentRef returns the entry_ref under which the document is being saved, NULL if document wasn't saved yet.
virtual bool DocumentDirty() const; virtual bool ReadOnly() const; virtual bool IsShellWindow() const; virtual bool IsWorksheet() const;
Getter calls for obtaining different parts of the document state. DocumentDirty returns true if document needs saving. ReadOnly is true if you cannot save the document (you can still edit it but you will be required to do a SaveAs). IsShellWindow and IsWorksheet return true if a document is a shell and a worksheet. When one of these values changes, the kDocumentStateChanged selector gets called.
virtual DocumentLanguageType LanguageType() const;
Returns the type of the language associated with a document. Currently supported are:
kUnknown | unknown document types |
kShell | currently any file without a suffix |
kCC | .c files |
kCH | .h files |
kCCPlus | C++ files (.cc, .cpp, .cp, etc.) |
kCHPlus | C++ header files (.h++, .hpp, etc.) |
kJava | Java files |
kHTML | HTML files |
kPerl | Perl files |
kYacc | yacc or Bison files |
kLex | lex, flex files |
kAsm | assembly files |
kPascal | Pascal files |
kModulaDef | Modula 2 definition files |
kModulaOberonImpl | Modula 2 or Oberon implementation files |
kFortran | Fortran files |
kRez | MPW res files |
kCLikeAsm | .S assembly files with C-like syntax |
kMakefile | makefile, Makefile, make*, etc. |
When the type of document changes, the kDocumentStateChanged selector gets called. More document language types are likely to be added in future versions (send me your favorite ones with file suffix conventions).
virtual void RegisterHandledShellCommand(const char *); const char ***CurrentArgv() const; virtual void WriteSettings(const char *format, ...);
RegisterHandledShellCommand called
during plugin initialization. Currently used for handling
settings that are stored in Eddie's settings file in a text format. When Eddie sees the setting
the plugin registered, it calls it with the
kArgvPassed selector to read the setting. During the
kArgvPassed selector call you can use the CurrentArgv
call to access the argv-like formatted line with the setting.
WriteSettings is used to save a given plugin settings into the settings file during the
kGlobalSettingsSaved selector. You just use it
the same way as if you were using printf to print the settings.
example1,
example2
virtual void AddAppPrefsPanel(const char *name, PrefsPanel *prefsPanelProxy);
Called during the kGlobalPrefsShowing
selector, used to install a panel into the Application Settings dialog. name
is the name under which your panel will show up in the dialog. prefsPanelProxy
is an instance of a subclass of PrefsPanel
, a proxy class that controls applying, reverting
and setting the preference values to defaults.
example
virtual void RegisterHandledPrimitiveCommand(const char *command, const char *description); const char *CurrentPrimitive() const;
RegisterHandledPrimitiveCommand called during plugin initialization. The plugin can register
editor primitives that the user can bind to keyboard shortcuts using SetKey. The primitives
will also show up when the Primitives command is invoked from the shell. During the
kPrimitiveInvoked
selector call you can use the CurrentPrimitive to figure out which keyboard primitive was
invoked. This is necessary if your plugin publishes more than one primitive.
example
virtual void ShowClipboardPanel(const char *text);
Open a clipboard panel under the plugin button, displaying the passed text. The panel is closed when the mouse button is released. This is used for instance by the Magic Prototyper to show the contents of the Prototyper clipboard.
A versioning mechanism is included, making sure that a given plugin and a given version of Eddie are capable of running with each other. As more API gets added to Eddie over time, older plugins may continue to run with newer versions of Eddie without recompilation. This way a plugin can be sure that the copy of Eddie it is running in supports all the callbacks that it needs and the Eddie application only calls the selector calls that the plugin understands.
The kGetPluginVersion is the first selector call a plugin will receive. It needs set the pluginInterface structure with the two values, the plugin supported version and the plugin required version. The PluginInterface provides two calls to set these two values as well as default values that are defined by the current version of Plugin.h that you are building your plugin against. Your plugin has to return kAccepted as a result value:
switch (selector) { ... case PluginInterface::kGetPluginVersion: pluginInterface->ReturnSupportedPluginVersion(); pluginInterface->ReturnRequiredPluginVersion(); return PluginInterface::kAccepted; ...
The kInit selector is the next selector your plugin needs to deal with. During this selector a plugin can allocate any data structures it needs for it's operation, request non default selector calls if it needs them, register editor primitives for keyboard shortcuts, register settings handling. If your plugin fails to initialize, return kFailed from PluginMain and it will never get called again.
switch (selector) { ... case PluginInterface::kInit: myPluginState = new MyPluginState(); pluginInterface->RequireSelectors(PluginInterface::kRequestKeyDownMask | PluginInterface::kRequestPulseMask); ... return PluginInterface::kAccepted;
Your plugin can optionally install buttons into the button bar of a window. This is done in the kWindowOpened selector, which gets called whenever a new document gets opened. The plugin uses the SetupPluginButton call, passing in bits for the button bitmap.
switch (selector) { ... case PluginInterface::kWindowOpened: pluginInterface->SetupPluginButton(kButtonBits, kPressedButtonBits); return PluginInterface::kAccepted; ...
The bitmap for the plugin must be an array of bytes representing 8 bit color values 16 x 16 bytes in size. A great way to obtain a button bitmap is to draw it with the bitmap editor in the FileTypes app, as if it was a file icon and then use the Dump icons to emit the bitmap array code. The 32x32 portion of the dump is discarded. Make sure the bitmap colors are consistent with those of the other buttons. A template of a button is provided in CutNPasteSource.cpp for convenience.
A button can be split horizontally, vertically or in both directions. For instance the Function popup uses the following code to install a horizontally split button:
... pluginInterface->SetupPluginButton(kFPButtonBits, kPressedFPButtonBits, kHorizontalySplitButton); ...
When a plugin button is pressed the plugin receives the kButtonClick or kButtonPress selector call, depending on how long the mouse is held down. If you have a split button, you may determine which portion of the button is being clicked by using the ClickSelector call:
switch (selector) { ... case PluginInterface::kButtonClick: if (pluginInterface->ClickSelector() == kTopButtonPart) GoUp(); else GoDown(); ... return PluginInterface::kAccepted;
You may change the button bitmap any time by calling the ChangeButtonBitmaps:
if (CopyMachineWarmedUp()) pluginInterface->ChangeButtonBitmaps(kCopyButton, kCopyPressedButton);
You may read text from the current document using the GetText and CharAt
calls.
The Insert call can be used to insert text at the current selection, in our example
at the beginning of the current document.
pluginInterface->Select(0, 0); pluginInterface->Insert("some text", 9, "Insert Some Text");
The third parameter "Insert Some Text"
will be used as the name of the Undo
for this edit. (In a real plugin you should use a shorted undo name than that).
Clear is used to delete the current selection. To delete the entire text of your current document, you could call:
pluginInterface->Select(0, pluginInterface->TextLength()); pluginInterface->Clear("Delete Everything");
The parameter "Delete Everything"
will be used as the name of the Undo
for this edit.
You may set up your plugin to get notified whenever text changes by using the kDocumentTextChanged selector. During this selector call ChangeStart, InsertedLength and RemovedLength returns the offset in the text at which a change occurred, the length of text removed and the length of text inserted respectively. If for instance the user pressed a single key, say 'A', the InsertedLength will return 1, the ChangeStart will return the offset at which the letter 'a' gets inserted. RemovedLength will be zero, unless the selection was non-empty at the time of the keypress, in which case it got erased by the keypress and the length of the erased text is returned. If a Delete key is pressed, RemovedLength will return the number of deleted characters, InsertedLength will return zero. During a paste RemovedLength will again be the size of the selection that gets erased by the paste, InsertedLength will be the size of the new inserted text.
Let's show an example where a plugin monitors the text that is being inserted, detecting a case where a specific word gets inserted into the text.
const char *kMagicWord = "pasteThis"; int32 magicWordLength = strlen(kMagicWord); ... switch (selector) { ... case PluginInterface::kInit: ... pluginInterface->RequireSelectors( PluginInterface::kRequestDocumentChangedMask); return PluginInterface::kAccepted; ... case PluginInterface::kDocumentTextChanged: ... if (pluginInterface->InsertedLength() == kMagicWordLength && pluginInterface->TextLength() - pluginInterface->ChangeStart() > magicWordLength) { char buffer[20]; pluginInterface->GetText(buffer, pluginInterface->ChangeStart(), magicWordLength); if (strcmp(buffer, kMagicWord) == 0) AMagicWordWasJustPasted(); }
The example above obviously does not handle a case where the magic word was typed, character by character because the plugin will get a notification about a single character insertion after every character typed. We can modify our example to handle this case. The modified example will detect every time insertion of a magic word is completed:
case PluginInterface::kInit: ... pluginInterface->RequireSelectors( PluginInterface::kRequestDocumentChangedMask); return PluginInterface::kAccepted; ... case PluginInterface::kDocumentTextChanged: ... if (pluginInterface->InsertedLength() > 0) { int32 wordEnd = pluginInterface->ChangeStart() + pluginInterface->InsertedLength(); if (wordEnd >= magicWordLength) { int32 index = magicWordLength - 1; for (;index >= 0; index--) if (pluginInterface->CharAt(wordEnd--) != kMagicWord[index]) break; if (index == -1) AMagicWordWasJustInserted(); } }
The Insert and Clear calls support a simple Undo scheme where every edit is undone individually with the Undo command. You may want to group a series of editing commands into a single undo record that can be undone as a whole. To do this, you can use the composite Undo commands, StartUndo, CurrentUndoInsert, CurrentUndoClear and SealUndo.
pluginInterface->StartUndo(selStart, selEnd, "SwapWords"); char word1[256]; int32 firstWordLength = firstWordEnd - firstWordStart; pluginInterface->GetText(word1, firstWordStart, firstWordLength); char word2[256]; int32 secondWordLength = secondWordEnd - secondWordStart; pluginInterface->GetText(word2, secondWordStart, secondWordLength); pluginInterface->Select(secondWordStart, secondWordEnd); pluginInterface->CurrentUndoClear(); pluginInterface->Select(firstWordStart, firstWordEnd); pluginInterface->CurrentUndoClear(); pluginInterface->CurrentUndoInsert(word2, secondWordLength); int32 newFirstWordOffset = secondWordStart + secondWordLength - firstWordLength; pluginInterface->Select(newFirstWordOffset, newFirstWordOffset); pluginInterface->CurrentUndoInsert(word1, firstWordLength); pluginInterface->SealUndo(); pluginInterface->Select(newFirstWordOffset, newFirstWordOffset + firstWordLength); pluginInterface->ScrollToSelection();
You are obviously aware that the example above assumes the offset of the first word is less than that of the second word and that the words may be no longer than the size of the two static buffers. The second assumption would probably be no good in a real plugin.
You need to realize that plugin can have two selector calls invoked simultaneously. Consider for instance the following code fragment:
switch (selector) { ... case PluginInterface::kInit: ... pluginInterface->RequireSelectors( PluginInterface::kRequestDocumentChangedMask); return PluginInterface::kAccepted; ... case PluginInterface::kButtonClick: ... pluginInterface->Select(0, 0); pluginInterface->Insert("text from a plugin", 19); return PluginInterface::kAccepted; ... case PluginInterface::kDocumentTextChanged: ... char buffer[256]; pluginInterface->GetText(buffer, pluginInterface->ChangeStart(), someLength); return PluginInterface::kAccepted;
During call to Insert in the kButtonClick selector the plugin will be called again, this time with the kDocumentTextChanged selector. After all the text insertion is treated as any other text edit. You need to take the usual precautions when dealing with reentrancy like this, for instance you need to be careful about accessing any global state your plugin has.
Furthermore, calling text modifying calls, such as Insert and Clear from within the kDocumentTextChanged selector will have undefined results. You can call SetColor though, because the call does not insert or delete any text.
Eddie allows plugins to use the same settings saving mechanism the editor uses
itself. The application settings are stored in a single file in the
~/config/settings/Eddie/settings
file in a text form, formatted one setting per
line. You can actually edit the settings file with a text editor if you are so
inclined. The order of the individual setting items is arbitrary. The settings
file can contain comments (even though they will get stripped the next time
the settings are saved). The white space formatting is also arbitrary, tabs and
spaces can be used freely. A setting must however be on a single line. You can even
copy settings lines and paste them into the UserStartup file. They will be recognized
just fine, the only problem would be that if you tweaked any of the settings say using
the Application Settings dialog, the changes would get saved back into the settings
file, however since the UserStartup gets read after the settings file, the changes
would be overridden by the corresponding settings in the UserStartup that you pasted
there. This could be confusing so it's not as usefull as it might have seemed at first.
What is however usefull about this is the fact that setting and UserStartup items
can be handled using the same mechanism and same code, saving you effort.
To keep things simple, settings that can be changed using the Application Settings
preference panels, the checkboxes in the Find panel, etc. are saved in the
~/config/settings/Eddie/settings
file and are not usually edited by
the user in the text form. The ones that would be edited in text form would be found
in the UserStartup file. Here is a little example:
switch (selector) { ... case PluginInterface::kInit: ... pluginInterface->RegisterHandledShellCommand ("UltraPluginPluggingSpeed"); pluginInterface->RegisterHandledShellCommand ("UltraPluginPluggingColor"); return PluginInterface::kAccepted; ... case PluginInterface::kArgvPassed: { const char **argv = *pluginInterface->CurrentArgv(); if (strcmp(*argv, "UltraPluginPluggingSpeed") == 0) pluggingSpeed = atoi(*++argv); else if (strcmp(*argv, "UltraPluginPluggingColor") == 0) pluggingColor = MyConvertRGBColor(++argv); } return PluginInterface::kAccepted; ...
Your plugin will get called with the kArgvPassed selector
whenever Eddie detects a line with one of the two UltraPluginPluggingSpeed and
UltraPluginPluggingColor keywords in the settings file or in UserStartup. It will
format the line for your plugin into argv form and will let you get it using the CurrentArgv
call and parse it. In future versions of Eddie a plugin will be able to intercept
shell commands typed into a shell window and executed, all with the same registering
and processing mechanism.
If the setting belongs to the ~/config/settings/Eddie/settings
, you need to
make sure it get's saved at the right time. Doing so is pretty easy:
case PluginInterface::kGlobalSettingsSaved: ... if (pluggingSpeed != kDefaultPluggingSpeed) pluginInterface->WriteSettings("%s %d\n", "UltraPluginPluggingSpeed", pluggingSpeed); pluginInterface->WriteSettings("%s %d %d %d\n", "UltraPluginPluggingColor", (int)pluggingColor.red, (int)pluggingColor.blue, (int)pluggingColor.blue); ... return PluginInterface::kAccepted; ...Just use the printf-like WriteSettings call to write the setting into the Eddie settings file. Note that you don't have any control over the order in which your settings get written with respect to those of other plugins. Note that by convention Eddie does not write out settings that have values identical to the defaults. This keeps the settings file smaller. Also note that for reasons mentioned above you don't wan't to write out values that are to be read from UserStartup. These should be tweaked by the user by editing the UserStartup file.
A plugin can add it's page to the Application Settings dialog. To do this, the
plugin creates the prefence panel duiring the
kGlobalPrefsShowing
selector call, wraps it into a subclass of PrefsPanel
and hands it off to Eddie
using the AddAppPrefsPanel call.
PrefsPanel
is a proxy class with nine virtuals
that control the behavior of applying, reverting and setting the preference
panel to default values.
virtual bool ShowRevert() const; virtual bool ShowDefault() const; virtual bool ShowApply() const;
These three calls are used by the settings dialog to determine whether or not to show the "Revert", "Default" and "Apply" buttons respectively when showing the plugins preference panel. They return true which is the desired behavior for most preference panels.
virtual bool CanRevert() const; virtual bool CanDefault() const; virtual bool CanApply() const;
These three calls are used by the settings dialog to determine if the data
entered into the prefence panel's controls differs from the settings at
the time the dialog was opened, differs from the default values and differs
from the current settings respectively. The call is used for instance
to enable and disable the three buttons based on the data values. Your plugin
will generally override these calls in the subclass of PrefsPanel
to suit
the specific setup of your panel.
virtual void Revert(); virtual void Defaults(); virtual void Apply();
Your PrefsPanel
subclass has to override these three calls bacause they are
pure virtuals. Revert
applies the revert values - the values of settings at
the time the Settings dialog was opened and sets the preference panel controls
to these values. Defaults
applies default settings to and sets them to
the preference panel controls. Apply
applies the values from the controls.
The preference panel your plugin installs is a BView
. Use the call
PrefsViewRect to determine the size
needed. Place all the settings controls
onto the preference panel view using AddChild
. When your panel is layed out,
allocate an instance of your subclass of PrefsPanel
, passing the preference
panel view into the constructor. Add the instance of the PrefsPanel
subclass
to the dialog by calling the AddAppPrefsPanel
call.
case PluginInterface::kGlobalPrefsShowing: BRect frame = pluginInterface->PrefsViewRect(); frame.InsetBy(10, 10); frame.top += 3; BView *preferencePanel = new MyPanelBackgroundView(frame, ... // ... add controls to preferencePanel PrefsPanel *panel = new MyPrefsPanel(preferencePanel); panel->SetTarget(this); pluginInterface->AddAppPrefsPanel("Plugging settings", panel); return PluginInterface::kAccepted;
For a more complete example check out the KeyPressHandling.cpp
sample.
Plugin commands can be bound to keyboard shortcuts using SetKey
just like
editor primitives in the editor itself. To do this, a plugin publishes one or
more editor primitives. The user can then attach any keyboard shortcut to the
primitive using the SetKey
command in UserStartup. When the shortcut is pressed,
the plugin will get called:
switch (selector) { ... case PluginInterface::kInit: pluginInterface->RegisterHandledPrimitiveCommand( "PPPluginInsertPi", "Inserts the constant pi into the current selection"); pluginInterface->RegisterHandledPrimitiveCommand( "PPPluginInsertPhoneNumber", "Inserts your phone number into the current selection"); ... return PluginInterface::kAccepted; ... case PluginInterface::kPrimitiveInvoked: const char *primitive = pluginInterface->CurrentPrimitive(); if (strcmp(primitive, "PPPluginInsertPi") == 0) pluginInterface->Insert("3.1415", 6, "Insert pi"); else if (strcmp(primitive, "PPPluginInsertPhoneNumber") == 0) pluginInterface->Insert("(650) 462-4100", 14, "Insert Phone#"); pluginInterface->AnimateButtonClick(); result = PluginInterface::kAccepted; break;
The invoked primitive can be determined by calling the
CurrentPrimitive call.
If your plugin has a button and the invoked primitive matches the function
called during the button click, call the AnimateButtonClick to give positive
feedback to the user, as if the button was clicked.
Now the user can go and add SetKey lines to the UserStartup:
SetKey Alt-Control-P PPPluginInsertPi SetKey Alt-Shift-P PPPluginInsertPhoneNumber
This version of the Eddie Plugin Developer Kit is shareware. You may use it, copy it and redistribute it freely. You may use the included sample code or parts of it in your own Eddie plugins freely. It most certainly contains bugs, some of the features are not fully functional. Use at your own risk, you have been warned.
PAVEL CISLER PROVIDES THIS SOFTWARE AND DOCUMENTATION "AS IS", WITH NO WARRANTY OF ANY KIND EITHER EXPRESS, IMPLIED OR STATUTORY, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
IN NO EVENT IS PAVEL CISLER LIABLE FOR ANY INDIRECT DAMAGES OR OTHER RELIEF ARISING OUT OF YOUR USE OR INABILITY TO USE THE PROGRAM INCLUDING, BY WAY OF ILLUSTRATION AND NOT LIMITATION, LOST PROFITS, LOST BUSINESS OR LOST OPPORTUNITY, OR ANY SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF SUCH USE OR INABILITY TO USE THE PROGRAM, EVEN IF PAVEL CISLER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES, OR FOR ANY CLAIM BY ANY OTHER PART.
Be Incorporated is no way responsible for Eddie or the Eddie Plugin Developer Kit, Eddie is not a product of Be Incorporated.