RWStyledTextView
Diese Seite gibt es auch auf deutsch!
My first tutorial about RapidWeaver plugin development
deals with the RWStyledTextView invented with RapidWeaver
version 4. Compared with the RWTextView it already had a toolbar at the
bottom that holds common actions for text formatting. It
also hold buttons to add and remove links. It's best-known
from Styled Text and
Blog page types.
Unfortunately the HelloWorld example does not provide
information on how to use this view in your own plugin. The
"comprehensive comments to some of the header files" aren't
that kind of usefull for me either. I was only able to
handle this with big support from some of the pros of the
RapidWeaver forum. Therefor I like to specially thank
Isaiah from Yourhead, John from Loghound and RapidAlbum
Smackie for their support. I hope this tutorial will help
some of you to get in touch with plugin development.
This tutorial is not the last word on the subject. Actually
I'm not sure about some points and even André Pang from
Realmac Software couldn't provide answers to that
questions. Meanwhile he left for working for pixar
unfortunately (for me). So you are invited to share your
knowledge to improve this tutorial.
Current Issue!
Since I've updated to RapidWeaver 4.2.2 I get an error when
compiling my plugins. The error is produced by the
RWLink.h header file.
Therefore I switch back to RW 4.2 for compiling. With this
Version everything works fine!
Project Preparation
My tutorial is based on the HelloWorld example from
Realmacs Plugin-SDK. Out of the box the HelloWorld example
is not that usefull as it contains some issues (At least
the one that was available when I started to work on this
tutorial - RW 4.2). So we will make somme changes to the
example. We need to make some more changes later on, but I
will come back on this when the time is right. But before
we start lets do some more general changes/additions.
To be able to easily test our new plugin, we will add an
executable to the project. Then we can start RapidWeaver
directly from within Xcode and more important, we can debug
out plugin smoothly. To do so we control-click the entry
Executables under Groups & Files and select Add/New Custom Executable... from context
menu.
Enter RapidWeaver into the Executable Name field and choose the RapidWeaver Application bundle for the Executable path field. Finaly press Finish and you will see the Info dialog of the new executable that you can close also.

The next point on our preparation list are the compiler settings. This is also the first recommendation I get from Smackie (RapidAlbum) when I started developing plugins. It's about making the compiler very pedantic. Let the compiler tell you about every tiny problem he is aware of and let him deal with it like they where errors. It does not pay to ignore or accept warnings. Sooner or later you will run into problems and your are not able to figure out where they are comming from. And therefore we mill change the settings to have a really pedantic compiler. To do so we select our plugin under Groups & Files and press the Info button on the toolbar. Now we will see the Project "HelloWorldPlugin" Info dialog where we switch to the Build section. Enter warning into the search field an scroll down to the GCC 3.0 - Warnings section where we will find Treat Warnings as Errors and activate it. In the same section we will find the Other Warning Flags entry where we add -Wall. Now our compiler is ready and will only build our application when there are no errors and warnings.
Lets pay a little attention to the RapidWeaver part of the project file. Against the first issue of the Plugin-SDK for RapidWeaver release 4, the import of the RapidWeaver frameworks was corrected in the sourcecode while the inclusion into the project is still the old one. So you can find the following lines in the code
#import <RMKit/RMKit.h>#import <RWKit/RWKit.h>
but in the project file still includes the old RapidWeaver 3 framework RWPluginUtilities. Obviously this works, but we have to deal with a lot of limits this way. You can't take a look into the header files for example, because the RWPluginUtilities framework is just a dummy to load the new ones. You have some problems to use the new components in Interface Builder as well. So let's start removing this problems. Locate the RapidWeaver application package in the Applications folder and call the context menu using a right click and select Show Package Contents. Now open the folder Contents/Frameworks where you will find the new frameworks RMKit and RWKit. Drag'n'Drop these two frameworks into the Frameworks folder of your HelloWorldPlugin profect file.
Your can remove the old RWPluginUtilities framework now. With this changes with have a solid foundation for our project and now we can have a look at the header files also. We need to do some more changes later on, but for now lets start coding.
To prepare our plugin to be a more advanced HelloWorld example, we change the inheritance. The plugin doesn't inherit from RWAbstractPlugin any longer. Now it is derived from RWTextViewPluginClient and implements the RWStyledTextViewDelegate interface. Then we can add a new Outlet for theRWStyledTextView. The changed header file will look like this:
@interface HelloWorldPlugin : RWTextViewPluginClient <RWStyledTextViewDelegate>{IBOutlet NSView *pluginView; //Interface ViewIBOutlet NSView *optionsView; //Options ViewIBOutlet NSTextField *outputTextField; // Text FieldIBOutlet RWStyledTextView *contentTextView;}- (IBAction)showOptions:(id)sender;@end
Interface Builder
Now we are ready to do some Interface Builder stuff and it's time again to fix some problems. We will provide some information to the Interface Builder so it is aware of the new classes. We put RWStyledTextView.h headerfile from RWKit.framework onto the document window of the project
and so we do for RWAbstractPlugin.h and RWTextViewPluginClient.h. Now we can insert the RWStyledTextView into the PluginView. Do this by dragging a CustomView onto the PluginView. I expect you know how to place and size this view. To make this CustomView a RWStyledTextView we switch to Identity Inspector and change the class name to RWStyledTextView. A corresponding entry should be available in the list.
The next step is to connect the view with the outlet of our plugin. But if we open the Connections Inspector of our File's Owner we cant see the outlet. The reason for this problem can be found in the Identitiy Inspector of the File's Owner. The HelloWorlds class name is TemplatePlugin and that's wrong. As soon as we change the class to HelloWorldPlugin, we can find our outlet in the Connections Inspector. Now we can connect the outlet with our RWStyledTextView.
This was all we have to do in the Interface Builder. Let's head back to Xcode to add some more lines of code. This time we need to add some code to HelloWorldPlugin.m.
Initializing the plugin
We already told our plugin to implement the RWStyledTextViewDelegate interface in the header file. Now we need to implement the necessary code. Fortunately the interface consists of one method only and so we just need to add the following code:
- (NSObject<RWPluginProtocol>*)pluginForStyledTextView:(RWStyledTextView*)styledTextView{return self;}
We also need to make sure that our plugin registers itself as a delegate for the RWStyledTextView. To do so add the code shown in line 8:
- (id)init{self = [super init];if(self == nil) return nil;[NSBundle loadNibNamed:@"HelloWorld" owner:self];[contentTextView setDelegate:self];return self;}
It's time to test our plugin for the first time. If everything's fine and my tutorial was ok so far, you should be able to use the plugin in edit mode. You should be able to edit and format text and insert pictures.
After celebrating our success and playing arround with the plugin, we need to make the plugin able to provide the output for the website.
Exporting the contents of the plugin
The method contentHTML is
responsible to provide the output, so we are responsible
for the method to do so. We need to parse the content of
the NSAttributedString of the
RSStyledTextView and translate
the attributes of the text into HTML code. We also need to
handle embeded pictures and links and make them exported
and referenced correct.
Scared about this big task?
Don't worry. We didn't have to do this ourselves.
Fortunately the guys from Realmac Software provided som
usefull tools for this job. An instance of RHTML will do the work.
Unfortunately this class was the one that kept me from
finishing this tutorial for so long. It is not documented
in the header files and I still didn't undeestand it
completely. And so the following solution is a result of
trail and error and picking some stuff from the source code
of other plugins. If anyone of you is can explain it,
please share your information.
Doch damit genug der Vorrede. Hier kommt der Code.
- (id)contentHTML:(NSDictionary*)params{RMHTML* contentExporter = [self exporterWithParams:params];NSMutableString *contentExported = [NSMutableString string];NSString *path = [NSString stringWithFormat:@"%@/files", [params valueForKey:@"path"]];[contentExported setString:[contentExporter exportAttributedString:[contentTextView attributedString]toPath:pathimagesFolder:pathimagePrefix:@""HTMLTemplate:[NSMutableString stringWithString:@"%content%"]contentTag:@"content"fromPage:[[self document] performSelector:@selector(pageFromUniqueID:) withObject:[self uniqueID]]]];return contentExported;}
First of all we create an instance of RHTML that will do the workf for us later.
The we create an instance of NSMutableString that will hold the HTML
code output.
An NSDictionary holding some
usefull data about the RapidWeaver environment is passed to
the contentHTML method. For
now we pick up the path
parameter that will be used as the target for our exported
files. RapidWeaver automatically provides a path that is
correct for preview and export.
Now it's time to call the exportAttributedString method of our RHTML
instance to do the work. I can't provide information on the
parameters of the method. I coukd guess something from the
naming you can read in the header file, at least from some
of them, but I don't know exactly. The solution provided
here I picked up in the source of the PageTOC plugin once.
So again. If you know some more details about the
parameters, let me know!
One more thing
I love to say this for so long: There is one more thing!
*lol* We need to save and restore the data of our
RWStyledTextView. To do so, we
need to change the initWithCoder and encodeWithCoder methods. The already hold
the code to save and restore the outputTextField and we can use this as an
example on how to do it for our contentTextView.
I just made one additional change. Instead of encodeObject: I use encodeObject: forKey: which will make the
load and restore process some more robust.
//Load Data- (id)initWithCoder:(NSCoder *)aDecoder{if (self = [self init]){[outputTextField setStringValue:[aDecoder decodeObjectForKey:@"headline"]];[contentTextView setAttributedString:[aDecoder decodeObjectForKey:@"content"]];}return self;}//Save Data- (void)encodeWithCoder:(NSCoder *)aCoder{[aCoder encodeObject:[outputTextField stringValue] forKey:@"headline"];[aCoder encodeObject:[contentTextView attributedString] forKey:@"content"];}
If still everything is fine, you should have a working example of using a RWStyledTextView. Down below I provide my project for download.
Extended HelloWorldPlugin Source