RWStyledTextView

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 View
    IBOutlet NSView *optionsView; //Options View
    IBOutlet NSTextField *outputTextField; // Text Field
    IBOutlet 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:path
                                                        imagesFolder:path
                                                         imagePrefix:@""
                                                        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


Werbung