Homer SDK
How to develop Homer add-ons
The thing is that ther not much to say. If you have read the section dedicated to the Homer library, you know anything you need to know by now. However, there are a couple of things you will definetely be interested in. All you need to develop your own add-ons is contained in the directory Homer/SDK. This directory contains:
Moreover, some code is made available in HomerCommand.h and HomerCommand.cpp to ease the development of file processing commands.
Based on the information you can find in the Homer library, you can figure out what is to be done. You first have to subclass the HomerFilter or HomerCommand class, and define your own methods as required. The body of your header is like:
#include "HomerFilter.h"
class HOMER_SYMBOL MyHomerFilter: public HomerFilter
{
public:
virtual BView* FilterView();
virtual status_t GetConfig(BMessage* , BView* );
virtual status_t SetConfig(BMessage* , BView* );
virtual bool Accepts(entry_ref , BMessage* , BLooper* );
};
or
#include "HomerCommand.h"
class HOMER_SYMBOL MyHomerCommand: public HomerCommand
{
public:
virtual BView* CommandView();
virtual status_t GetConfig(BMessage* , BView* );
virtual status_t SetConfig(BMessage* , BView* );
virtual status_t Process(entry_ref , BMessage* , BLooper* );
};
You have to define at least the Accepts or Process method. If your add-on has any parameter, then you need to define all 4 methods, because you need to write the GUI associated with your add-on.
The FilterView() or CommandView() method returns an instance of the view which allows the user to set the value of the parameters. GetConfig() and SetConfig() are used to retrieve or set the value of the parameters in the view which is passed as argument 2 (and which has been created through FilterView() or CommandView()).
Homer add-ons SDK provides you with the means to test and debug your add-ons without the need to run Homer or HomerEditor. In order to do so, just define DEBUG_FILTER or DEBUG_COMMAND in your makefile. The add-ons executable can be launched. A small window containing the add-on's parameters view. By drag'n drop-ing files into this window, you run the filter or command with the dropped entry as a parameter.
One problem is that you may need to support drag'n drop to automatically set the value of your parameters. For instance:
When compiling you add-on in debugging mode, you need to differenciate these 2 drag'n drop (which runs the filter or command, or set the parameter values). I suggest that you determine whether the command key (BeOS terminology) is pressed:
void MyAddOnView::MessageReceived(BMessage* msg)
{
if (msg->HasRef("refs"))
{
#if defined(DEBUG_COMMAND)
if (modifiers() & B_CONTROL_KEY)
{
Window()->MessageReceived(msg);
return ;
}
#endif /* DEBUG_COMMAND */
// uses entry to set parameters
...
return ;
}
switch(msg->what)
{
...
}
}
I know it's ugly, but it is only for debugging purposes... You have to run your add-on from a Terminal because the result of the filter/command is printed on stdout. Filters will print 0 (false) or 1 (true), and commands any integer value (the command return status), the most important being 0 (B_OK).
Temporary files and line-by-line processing in Homer commands
The HomerCommand class also provides the means to process a file as a whole, or line by line. For this, the classes HomerCommand::FileProcessor and HomerCommand::LineProcessor are defined. These classes embed both the code and parameters necessary to the processing. By default, there is no parameter but the entry reference about to be processed.
HomerCommand::FileProcessor::FileProcessor(const entry_ref& entry);
Initializes the file processor's entry.
HomerCommand::FileProcessor::~FileProcessor();
Does nothing.
status_t HomerCommand::FileProcessor::Process(BFile* input_file, BFile* output_file);
Performs the processing by reading the input file and writing to the output file. By default, this method does nothing and returns B_OK. You can define your own file processors by deriving the FileProcessor class and redefining this method. Note that you can write your own code to process a file in your add-on. This code is just meant to make your life easier.
Does nothing by default and return B_OK.
This class is meant to be used with the static method HomerCommand::ProcessFile.
static status_t HomerCommand::ProcessFile(FileProcessor& file_processor);
As you notice, it takes a file processor as an argument. It creates a temporary file (referred to as output_file in FileProcessor::Process) and calls the processor. If the return status is B_OK, the temporary file replaces the original.
HomerCommand::LineProcessor::LineProcessor(const entry_ref& entry)
Initializes the line processor's entry.
virtual HomerCommand::LineProcessor::~LineProcessor()
Does nothing.
virtual status_t HomerCommand::LineProcessor::Process(char* line, int line_number)
Performs the processing of line whose number is line_number. By default, this method does nothing and returns B_OK. You can define your own line processors by deriving the LineProcessor class and redefining this method. Note that you can write your own code to process a file line by line in your add-on. This code is just meant to make your life easier.
This class is meant to be used with the static method HomerCommand::ProcessLines.
static status_t HomerCommand::ProcessLines(BFile* input_file, LineProcessor& line_processor);
As you notice, it takes an input file and a line processor as arguments. It read input_file line-by-line and calls the line processor for each line. Note that the output file, if needed, has to be stored as a field (a parameter) in your line processor. In case the line processor returns a value different from B_OK, the processing will be stopped right away. Moreover, if you feed in the processor with a binary file, a buffer overflow is very likely to happen, causing the processing to abort.
Sample search command
This sample command will illustrate how to use regular expressions, how to send messages and how to write and use a FileProcessor and LineProcessor. The only thing missing in this example is the GUI, which is not a big deal anyway.
Basically, you define a line processor. In that example, the processor will just surround the matched strings with "**" and print it out on stdout. Note that you have to check for the case where the matched string is of size 0 in order to avoid an infinite loop (X)
struct MySearchProcessor: public HomerCommand::LineProcessor
{
RegularExpression* re;
BLooper* msgWindow;
MySearchProcessor(const entry_ref& , RegularExpression* , BLooper* );
virtual status_t Process(char* , int );
};
status_t
MySearchCommand::MySearchCommand::Process(char* line, int line_number)
{
RegexString restr(line);
RegexMatch m;
while ((m = restr.Search(re, cas)) != NoMatch)
{
char chr;
char* line2 = restr.GetString();
if (!m.IsNull())
{
chr = line2[m.start];
line2[m.start] = '\0';
printf("%s", line2);
line2[m.start] = chr;
chr = line2[m.end];
line2[m.end] = '\0';
printf("**%s**", line2);
line2[m.end] = chr;
restr.Skip(m);
}
else
{
// (X) the match is null, avoid an infinite loop!
if (*restr.GetString() == '\0')
break ;
// print out the character...
chr = line2[1];
line2[1] = '\0';
printf("%s", line2);
*msg << MessageListItem::plain_item(line2);
line2[1] = chr;
// and skip it!
restr.Skip();
}
}
return B_OK;
}
The MySearchCommand.cpp contains the code for both MySearchCommandView and MySearchCommand
//---------------------------------------------------------
// Exported symbols
//---------------------------------------------------------
char HOMER_SYMBOL addonName[] = "my search";
char HOMER_SYMBOL addonVersion[] = "1.0";
char HOMER_SYMBOL addonAuthor[] = "Acme add-on developer";
char HOMER_SYMBOL addonEmail[] = "xyz@foo.bar";
HomerCommand* instantiate_command()
{ return new MySearchCommand(); }
BView* MySearchCommand::CommandView()
{
BRect fr(0, 0, 250, 50);
MySearchCommandView* v;
v = new MySearchCommandView(fr, "MySearchCommandView");
v->SetViewColor(220, 220, 220, 0);
return v;
}status_t MySearchCommand::GetConfig(BMessage* conf, BView* v)
{
MySearchCommandView* view = dynamic_cast<MySearchCommandView* >(v);
// MySearchCommandView::GetConfig(BMessage* ) must be defined
return view->GetConfig(conf);
}status_t MySearchCommand::SetConfig(BMessage* conf, BView* v)
{
MySearchCommandView* view = dynamic_cast<MySearchCommandView* >(v);
// MySearchCommandView::SetConfig(BMessage* ) must be defined
return view->SetConfig(conf);
}status_t MySearchCommand::Process(entry_ref ref, BMessage* param, BLooper* msg_target)
{
// retrieve parameter values in "param", in particular "regular_expression"
...
// set the input file
BFile file_in;
st = file_in.SetTo(&ref, B_READ_ONLY);
if (st != B_OK)
return st;
RegularExpression re(regular_expression, EXTENDED_POSIX_SYNTAX);
if (re.InitCheck() != B_OK)
return re.InitCheck();
return HomerCommand::ProcessLines(&file_in, MySearchProcessor(ref, &re, msg_target));
}
As you may have noticed, almost 50% of your job in developing an add-on will be to write a GUI.
This page was last updated on 12/12/99.