[Tampon for Juce]


#1

Crumbs! This is going to be a bit of a blog… I’ve got a lot of stuff planned in this area. Feel free to skip the ‘introduction’ bit, I just thought it might be prudent to explain what the heck I’m up to.

NOTE: The latest version of Tampon should hopefully always be obtainable by clicking this link: Download Tampon source.

[size=200] Introduction to Project Tampon [/size]

I’m trying to develop a framework to make it easier to build ‘full’ applications. By that, I mean more serious apps than the basic tools you make when cutting your teeth with Juce.

The jucer is provided as an example of the implementation of more advanced features and design tricks - but it’s not exactly up to the level of intuitivity set by the rest of the juce documentation; it’s an example of more advanced application features, but it doesn’t quite lend itself to being a ‘guide’ on how to do complex things.

And so, I’m trying to develop an extended toolkit for Juce - extra classes on top of the already mountainous supply it already has - specifically geared towards doing useful groundwork for you when making a ‘big’ application. If you consider the difference between ‘MyColourPicker’ [*fictionalexample] and The Jucer, you’ll surely appreciate that there is a lot more to the design/construction of a complex application than a simple tool.

The main thing I’m working towards is a more full-featured set of classes for a ‘document/view’ model. Bases that are not only easy to use, but informative in their role within an Application [If assembly is a bag of just the tiny lego studs, the OS API is one of those ‘1x1’ unit bricks, Juce is the whole set of standard blocks in all colours, and what I’m aiming for is one of those big flat pieces you can put all the bits on, and it comes with a nice illustrated booklet explaining how to make a car or a wendy house].

Part of this is the whole ‘archive’ business I’m working on, but there are lots of other things that need addressing. I should point out that this isn’t really just selfless contribution - I need all this stuff! I don’t want to be crawling through sewers trying to hack together the application I’m building - I want it to be built on a solid framework that I can depend upon, and one that I know will be expandable in the future without exposing hideous cracks from hacketty hacks. Because it’s so important to me, I imagine the various bits could be useful to others, so I’ll present them here as I go.

INTRODUCTION ENDS HERE!

So, what I’ve made this time is a set of helper classes/macros to make it very easy to have your own ‘type handlers’, for when your program uses lots of objects of various subclasses of a particular base type.

[size=150]Chapter One: Type Handlers[/size]

Now follows an explanation of the purpose of this chapter…

As an example, consider the Jucer; it has lots of userdata made up of different types of component. Each type of component has different properties, each has a different appearance, etc…

Think about how that’s all done; the user can interact with the components all in the same way (drag/size/view properties, etc…), and the user can ADD components from a single menu. But, they’re all different - adding a Button obviously creates a different object to adding a Label. Clicking on a Label shows different properties to clicking on a ComboBox. That might not seem like such a big deal, but of course the components themselves don’t contain the information on what those properties are, or how to display them. The document holds an arrangement of Components - but as far as its concerned they’re all just Components.

When it comes to saving/loading the layout, the complexity is highlighted; it’ll need to store stuff about these components - not just easy stuff like where, how big, etc… no, more obvious stuff like - er, what component it is! When loading the document back - a bunch of XML comes in, and it has to create the correct Component type (and apply appropriate properties) for each piece of data…

There are obviously a scrillion ways of doing that; the most obvious one (certainly to a novice) would be to have a cascade of if/else blocks, checking the name of the tag, creating the correct object, applying properties, etc… It’s not ‘wrong’ but it does get unwieldy - plus that sort of approach leaves you destined to having such things scattered far and wide around your program, and it becomes difficult to maintain or update.

The way the Jucer does it is to have a bunch of ‘type handlers’; a base ComponentTypeHandler describes stuff common to all components, and subclasses define specific stuff for specific components. It’s not just a bunch of handlers though - there’s a system for determining automatically which handler is suitable for the job in hand [getting the handler for a given component, or getting the right handler for a potential component by a tag name].

If this is all new to you, and sounds interesting (or like the solution to a problem you’ve faced), then take a look at the jucer code. It’s pretty complex! There’s a lot of stuff to take in if you want to replicate that sort of system for your own purposes.

What this all means to me (doubling as another example!)

I have a need for exactly this sort of thing. I’m working on a Quiz game to run in the classroom as a teaching aid. Amongst the data structures is a ‘Question’ base class, but each type of question is different (some are just text, some have pictures, etc…) - and so each type of question also requires a different kind of editor, and a different kind of ‘game view’. If I were to have a handler for each type, it could have a ‘createEditor()’ function, and a ‘createGameView()’ function - e.g. when clicking on a question in the editor, it finds the appropriate handler is found and used to provide the correct editor component.

[size=150]What it involves[/size]

What I’ve written is a base class and a set of macros. It’s used in a manner similar to the juce Singleton; you design a class, and you have to use the declare/implement macros in the appropriate places. The main difference here is that you also derive your class from TypeHandlerBase. What you’re making is a base for handlers of a specific type [so, for example, Component, or Question].

The system makes use of RTTI (run time type indexing). People snarl about using some of this stuff, about how it’s not ‘proper’ object oriented design. I can appreciate what they’re saying, but - heck, you NEED to have RTTI enabled for Juce, so it’s not going to hurt to use it in the odd place, is it? Most importantly though, if you don’t know much about it - don’t worry - the one place you use it with this helper is entirely obvious.

So, to do all this stuff, this is what you do:

Let’s say we have a base type - the old tutorial staple ‘Shape’.

  • Subclass TypeHandlerBase, making sure your constructor takes values to pass on to its constructor. We’ll call it ShapeTypeHandler.
  • add members to do handling stuff. For example, you’ll likely want a ‘createShape()’ pure virtual function. [remember, each handler subclass defines handling for a particular Shape subclass]
  • add the macro ‘tampon_DeclareTypeHandlerBase (ShapeTypeHandler, Shape);’ to the class declaration body.
    e.g.
class ShapeTypeHandler  : public TypeHandlerBase
{
public:
   ShapeTypeHandler (const String& typeTagName, const std::type_info& typeClass)
   :  TypeHandlerBase (typeTagName, typeClass)
   {
   }

   virtual Shape* createShape () = 0;

   tampon_DeclareTypeHandlerBase (ShapeTypeHandler, Shape);
};
  • In the implementation file, put the macro ‘tampon_ImplementTypeHandlerBase (ShapeTypeHandler, Shape);’.

That’s the base done! Now, you need to make your subclasses for each type you need a handler for.

  • Derive your handlers from your base. E.g. TriangleHandler and SquareHandler should derive from ShapeTypeHandler.

The constructors don’t require any parameters, but you can of course demand whatever you like. The important thing is that the base gets its parameters, and they are fixed per type subclass (so you don’t need to expose them here like you did with the base)…

  • The first parameter is the ‘tag name’ - a String used to identify the type. This can be used in a user document - to store the type of the object so it can be recreated later.
  • The second parameter is the alien bit - it uses the C++ RTTI operator ‘typeid’, but you don’t have to understand it. Literally all you need to do is put typeid (ShapeSubclass). That’s it. The name of the class your handler is going to be handling.

So… TriangleHandler’s constructor could be…

TriangleHandler::TriangleHandler ()
: ShapeTypeHandler (T("Triangle"), typeid (Triangle))
{
}
  • make one of these classes for each type you need a handler for, and of course define the bodies of any virtual functions you’ve made.

Once you’ve done that… that’s it! They’re ready to use! The macros take care of everything else, giving you functions to track down the handler you need. To use them, you need to do the following:

- At the start of your program, register all the handlers via a static function in your base:

ShapeTypeHandler::registerHandler (new TriangleHandler);
ShapeTypeHandler::registerHandler (new SquareHandler);
  • At the end of your program, destroy them all, via a single call to this…
ShapeTypeHandler::destroyHandlers ();

Now, whenever you need to get a handler, you can do stuff like this…

ShapeTypeHandler* hander = ShapeTypeHandler::getHandlerForTypeTagName (xml.getTagName());

// or

ShapeTypeHandler* handler = ShapeTypeHandler::getHandlerFor(selectedShape);

You’ll be presented with the appropriate handler, with access to all the functions in the base class [with all virtual function behaviour from the corresponding subclass]. Easy!

No, seriously, DEAD EASY.

The code for the macro/base stuff is in the next post (my web space is perpetually screwed).

Now, I know this post has been epic in length, but I imagine this project is going to be going on for a while, so I wanted to set the scene. I guess it’s a bit like a series of highly specialised seminars in comparison to the previous tutorial… I’m sorry that I’ve not had the time to work on that in so long, I have my own code to write! But I’m going to share it as if it were a tutorial anyway.

So, until the next chapter… :slight_smile:

[*] You’re probably wondering about the name of the thread… I was bored of a generic acronym based name for the project. The thought process was: it’s supposed to be a toolkit to make creating applications easy… a kindof application creator… applicator… And applicator of course leads on to Tampon, and so the name has stuck. There.
[size=75](the fact that it sits so uncomfortably in the mind next to the word Juce just adds to the appeal)[/size]


#2

[EDIT] The code is now available via the Tampon source.

Download Tampon source

This archive should always contain the latest version of Tampon, until it reaches a point where it needs proper versioning [right now it’s just growing from birth so I’ll keep it simple].

Just unpack it somewhere, and include tampon.h to get access to all the fluffy goodness it contains (well, it will eventually!).

Current features:

  • custom dynamic type handler system

On the horizon:

  • Document/View model for complex applications

#3

Very neat! When I wrote the jucer I kind of evolved all the type management stuff rather than sitting down and designing it like you’ve done here. I should probably go back and retrofit something like this to it!


#4

a similar approach can be used in jucer to let the developer add it’s own components and specialize behaviours, atrributes and stuff based on the different types (and without dirtying the codebase)… very neat !


#5

@jules: yes, that highlights the fact that you’re a better and more efficient programmer than me! :slight_smile: I could never have ‘evolved’ something like that, it would have gone off in the wrong direction and become lost and dangerous, like a panic stricken bear in a big city.

I’m really pleased with how simple it is to use - something like this would have made my life easier so many times in the past; it achieves something that can be used in many places (and that I’ve wanted to be able to do on so many occasions) but that is normally such a massive headscratcher to implement. It has loads of applications; one of which is going to be in the ‘document’ part of Project Tampon; there will be a base DocumentTypeHandler, from which you can derive handlers for your own document types, and it will have functions like ‘createDocument(const File&)’, createDocumentView(Document*)’, ‘getDocumentForView(DocumentView*)’, etc… This will all be understood by the DocumentManager system, so there can be additional helpers to make it all seamless [createNewDocument(T(“QuizDoc”))], and keep lots of behaviour ready-made, requiring only that you design a Document, View and Handler; the manager will handle all aspects of instantiation and connection for you, and you only need to register the DocumentTypeHandler handler for it to do so. [and supporting multiple document types will be a simple case of registering additional handlers]

As far as integrating such a system into the Jucer goes… yes! That’d be excellent. It took me a while to figure out which files were doing what - when I discovered the ObjectTypes files, I just thought “damn, Jules knows what he’s doing…”, but was sad because I had no bloody idea :lol: I sussed how it worked, but wasn’t happy about how ‘exclusive’ it was - I concluded that it was that way because it was efficient [trusting Jules’ mysterious elevated coding prowess], but I wanted to play that game too!

Whenever it comes to having static stuff in conventional arrays, like in ObjectTypes::, it feels like its playing outside of the comfortable and familiar realms of Juce (certainly for me), and it’s difficult to know how to add to it (or reimplement the concept elsewhere). Hopefully this new system will make it easier - a LOT easier. It could probably use a few extras, but it works very well and I feel it matches juce’s intuitivity [except for the typeid requirement… I tried to conceal as much of the RTTI stuff as I possibly could so noone feels like they’re doing anything dirty or scary. I think the one time you have to use it is about as straightforward as it could possibly get].

To put it into the jucer should be very easy too! It’s the same model, so it just needs ComponentTypeHandler to be a TypeHandlerBase, and a few tweaks here and there (the base does need a few more functions for getting information on the handlers though). Then the documentation can say something along the lines of “if you want to add support for additional component types, simply subclass ComponentTypeHandler, implementing these functions [xxx…xxx], and call ComponentTypeHandler::registerHander(YourComponentHandler) in the initialiser.”.

Does it get any easier than that?! :smiley: thanks for your support, and for tolerating my incessant gargantuan postery


#6

Good stuff!

Yes, that part of the jucer is certainly pretty confusing, even for me whenever I go back in there to add things…


#7

Just updated it - now it has some extra functions for accessing the handlers directly (e.g. to get a list of known types).

Also, I’ve fully commented the code, and tidied it up to make the start of the Tampon project ‘official’; it can be used just like juce (#include “tampon.h”). [although until it’s big enough to bother making it into a statically linked library, you’ll need to add the CPP files to your project. Or make a library of it yourself.]

*incidentally, i tried making the handlermanager deletedatshutdown, but it didn’t work because that base uses a CriticalSection, which isn’t ready at the point it gets instantiated. Still, calling destroyHandlers() is hardly a chore!


#8

Another update! Not particularly exciting or new, but an added interesting feature…

I’ve moved the ZipOutputStream stuff into Tampon. I’ve also added ExtendedZipFile, which is an enhanced version of ZipFile.

ZipOutputStream has the ability to do the following:

  • Write files, copy existing ZipFile entries or just embed plain data to a new Zip file.
  • The Zip file can have a standard comment embedded
  • Each entry can have a comment and - more interestingly - XML data associated with it. These are stored in the Zip file’s central directory, and can be retrieved without needing to read the entry itself. This means you can add custom information describing each file.

These extra features necessitate the ExtendedZipFile class; the files created are entirely standard, and can be read with the normal ZipFile, but of course you won’t be able to read any of the comments or XML.

[NOTE: I’ve specifically called it ‘miniXml’ because the header records must not exceed 64k in size. Obviously that’s quite a reasonable amount of space, but it’s probably best to limit how much info you put in them]

I’ve also made Tampon into a static library project. There’s a project in there for VC++express2008, but also a premake.lua script that should hopefully generate a working project [only windows at the moment though; if you know how, feel free to add conditionals to it to support other targets, i can’t test anything like that].

Anyway, more to come…


#9

A new addition: Id allocation

At the moment, there are two versions. One (IdAllocator) is just an allocator you can use to provide you with unique IDs (steal and release as needed/freed). The other (IdManager) is templated to a particular type, and remembers which objects are assigned to which IDs, so they can be retrieved directly from the manager (regardless of where they’re actually stored). It requires subclassing, to define the virtual ‘applyIdToObject()’ function, so that it can be used with existing types (with their own method of setting their internal ID value).

I’ll probably change it a bit though to have a base ‘IdManagable’ or something. Some macros would make it easier to add built in IdManagement to a new class (using a static IdManager member) but I’m wary about using too many macros in this library! If anyone has any strong opinions about those sorts of macros (like the ones in singleton/typehandler), good or bad, let me know.


#10

Time for a status report before i go away for christmas (and I won’t have my computer to work properly on it! :’( )

The document/view model is the main part and purpose of tampon, so it’s obviously taking a while, but I’m making good progress and thought I’d share some more info…

There are basically two main classes to be subclassed- Document and View. The document holds all the data representing the current state of a user project, and the view is the UI to manipulate it.

A ‘DocumentTypeHandler’ class is used to describe a Document, and its relationship with any views it may have. For example, it defines how to create instances of the document and its views, how to save/load the document to various formats, etc…

There is also a class called ApplicationWindowContent, which holds the main ‘background’ of the application’s window. The default setup for one of these contains a MultiDocumentPanel, but it’s possible to subclass it to provide additional ‘global’ features (e.g. toolbar, sidebar, etc…) - there will be some ready made examples like this eventually too. The ‘content’ component is automatically wrapped in an actual window, and these can be changed (e.g. the standard juce resizable DocumentWindow, fullscreen mode, some custom window style, etc…) without having to change the program at all - there are handlers for these [you could even have a menu option to change it]

All this stuff is wrapped up in some handy general-purpose application classes. These take care of all the logic of handling documents and their views, menus, etc…, and providing common interface stuff; the sort of stuff that is pretty much standard and essential, yet normally still needs to be written. With this system, it’s all done for you.

To illustrate how much it can do for you, there’s some additional helpers - BasicDocument and BasicDocumentView. If you were only doing a simple tool-type application, you need only subclass these. And I literally mean only those - define the document contents, and an editor for it, and then a single macro LAUNCH_BASIC_TAMPON() uses some ready made handlers to link them together and wrap them in an application for you [no need for ApplicationStartup.cpp etc…]. That provides you with multi-document support, file load/save/close/new etc…, clipboard functions etc… For example, I just knocked together a ‘TextDocument’ holding a single String, and a TextDocumentEditor’ with just a TextEditor component. LAUNCH_BASIC_TAMPON turned it into a multi-panel application, with a standard menu to handle files and clipboard.

Because the basic model uses the same parts as the more advanced one, a goal is to make it easily upgradeable. If you decide you want to add more complex features (additional views, etc…) you can change away from BasicDocument to a normal Document, and learn how to write your own document handler class for it, and use a different macro to build it all.

One purpose for all this is to provide a kind of structured learning process - you can start off building a basic application (without having to know quite everything that’s going on), then you can move on to the next level and learn something more advanced - again without having to see code that you have to just accept - and so on, until you’re familiar with all the classes and can do much more complex stuff; adding format handlers to export to different formats, floating views for the current active document (think of the ‘layers’ window in photoshop) etc… I will be writing a tutorial to go alongside it too.

I imagine it still possibly doesn’t sound quite as interesting or useful as it should prove to be, but I believe it will make it even easier to design and build applications - and also easier to learn what’s needed.

There are quite a lot of other features that I’ve not gone into yet, but i’m sure you’ll find them to be very useful and interesting :slight_smile:

Anyway, merry christmas to all, and I’ll be back to rant endlessly some more before long! :slight_smile:


#11