Sunday, August 15, 2010

Playing with the UI

Again, before doing anything on the UI side, I highly recommend adding this to your .gdbinit : http://pastebin.com/RETfw2up
This GDB macro prints all size & position of a window and all its children.
Making a UI component without a SRC file is quite tricky - there's just too many things to be careful about. How does a src file actually look like?
It's basically a lot of text with the IDs of the UI components in it, and also the positions/sizes and hierarchy in it. You can open any .src file to get a feel of it, or look the one made here for the AnimationTabPage and the StyleSheetEffectPane: http://pastebin.com/TK5cTYFQ
Once this is correctly written, making your UI components should only contain of giving the actual code these IDs.
So:
- Take a notepad (actual papir + pen)
- Sketch how you'd like the UI to look
- Invent the coordinates in which ever way so they are rounded to integers
- Put that sizes, positions and hierarchy into the src file
- Make sure you give the root component the size of your sketch, cause all the other ones will be stretched according to that one, and the root one will be dependent on the container size
- Add the constants used in the src file, to a hrc file
- Include the hrc file in your cxx file
- *Very important*: don't forget to add your .src file to the makefile. No error will be detected (everything's in the hrc!) , but the UI will not work. At all.
- Put all the UI control variables as a private members of your class, e.g. PushButton a, ListBox b, etc.
- in the ctor of your UI class, initialize all of those variables, only with a parent and a ResId. Be careful which ResId you use - if the hrc/src are in the SD code, use SdResId. If they are in SVX code, you have to use SvxResId or nothing will work, and no errors will be detected.
- Add a delete for each of these variables in the destructor

Troubleshooting:
If you make everything nicely and nothing is displayed, try this:
- override a Paint() method from the Window class, and put a breakpoint there, or at the end of ctor (anywhere inside the UI class might work as well)
- call the printWindow script on the UI component (this)
- If all the sizes/positions are (0,0), it is very likely that your ResId isn't found and the control are just given default (zero) sizes & positions. Check where your ResIds are and use the appropriate resource manager. Check for double instances of the IDs.

Some components have to be displayed by explicitly calling Show() on them.
Dialogs have to be Execute() -ed so they would show. One handy thing about dialogs is that they are not deleted once a user closes them, so you can still gather the data from the dialog.

Making the UI is definitely the most painful point of the OOo...except for the build system, of course.
Good luck!

Making a SfxTabPage

So before you even try fiddling with UI components, try adding this to your gdbinit: http://pastebin.com/RETfw2up

Yeah, it's not the prettiest, but it works. This GDB macro prints all size & position of a window and all its children.
Making a UI component without a SRC file is quite tricky - there's just too many things to be careful about.
Now here's a doc of the SfxTabPage: http://docs.go-oo.org/sfx2/html/classSfxTabPage.html
What's actually used in the project is a SvxTabPage - the difference is in one letter only :) but the usage is different as well. Not only to mention that the SvxTabPage lies in a different project!! I first started extending the SfxTabPage and that caused a lot of problems until I extended the right class - the SVX one.
Some functions are core for this class:
- DeactivatePage: although you won't see it on the first page of the documentation (it's inherited from higher up), it's very important. It defines what happens when you try clicking a diffenre tab. To be able to switch to other tabs, you have to set it to return LEAVE_PAGE. It is also handy to fill in the SfxItemSet with whatever data you have associated with that tab page
- FillItemSet: populate the SfxItemSet (in this case, the stylesheet) with the data collected from the UI
- Reset(): called when pressing the reset button, but also when loading the tab page for the first time. You can use this to set the buttons on your tab page to reflect the current (existing) values in the ItemSet.

These are very important for the functionality. The rest is just make-up for the tabpage to look pretty and show the appropriate UI.

STLPropertySet and Effects in Impress

The STLPropertySet class is currently only used for two things :
1) CustomAnimationPane results
2) SfxAnimationItem created for this project :)
As I explain what it does, you will find that it could be used in other places around Impress code and that it shouldn't be forgotten deep down in the UI code
As a part of this project the code for the STLPropertySet was moved from the CustomAnimationPane files, to an independent file. Later on we realized how applicable it really is (and also had problems compiling ;) ) so it was moved to sd/source/core and the header now resides in sd/inc.
STLPropertySet is basically a map of items describing an effect. The key of the item is a so-called handle, which is just an int constant representing what that map entry describes. The Value in the map can be whatever, so the uno::Any is used as a type for the value. When you want to put an item into the STLPropertySet, you will have to use uno::makeAny on the value, and one of the integers defined in the STLPropertySet.hxx as a key.
There are ~30-40 handles, but a few properties are very important to represent the effect
- Preset ID
- Effect duration
Using these two fields, you can create an effect and put it into the MainSequence.

MainSequence is the thing you see in the CustomAnimationPane on the right hand side - a list of all the effects for that particular slide. Every slide (SdPage) has its own MainSequence. When adding/removing elements from the MainSequence, you have to use a mutex, and rebuild it afterwards. The rebuilding makes sure that the effects are triggered in a proper order.

Presets (CustomAnimationPreset) can be divided in several groups (or internally called "Classes") - and you can intuitively understand this if you look at the 'Add Effect' dialog - Entrance, Exit, Emphasis, Motion Paths, Misc...
Presets are not actually effects, but they are something we would intuitively call an animation. They can be used to make an effect, but for a proper effect you have some more options to set.
The smallest group of options to make an effect are
- Preset ID
- Effect duration.
Using these, you can create an XAnimationNode, and initialize the actual effect.
To make a real effect, you have to have an instance of the Main Sequence at hand.
This was a tricky part for this project - as mentioned above, the MainSequence is specific to a page. When applying an effect to all members of a Stylesheet, we have no idea how many objects there are, on which pages they are, etc. That's why we had to keep the information as a PresetId + duration

What gives you exactly that information, is the CustomAnimationCreateDialog. The Custom Animation tab in the Edit Style dialog is nothing but a cleverly remade CustomAnimationCreateDialog.
Preset ID and the duration can be safely stored in an STLPropertySet, and then wrapped into an SfxAnimationItem, and that can be put into a SfxItemPool, and that's exactly what a stylesheet is...can you see the puzzle pieces connecting? :)

Making a SfxPoolItem child

...would actually be very simple if you could have an example to grab onto - the problem is that some Sfx*Item-s are way to complicated and you can't really understand how simple they really are - you just need to keep whatever you want to use as a private type, and expose a couple of methods required by the base class. The only two 'required' methods are Clone() and operator==, although an item would be quite useless without it's Which, and GetValue()

Only very much later I understood that there are no limitations in using this item in whichever way you want, so my plan is to hack it further to contain information for removing effects from a stylesheet and the main sequence.

The way the SfxAnimationItem is currently made is, it encloses one STLPropertySet pointer, and has some simple operators. Although it seemed a bit pointless to make a test for something as simple, it turned out to have a huge benefit later and discovered some bugs that would have been very difficult to catch. The test also exposed the need to have an operator== for the STLPropertySet, or one wouldn't be able to determine if two animations are the same.

The SfxAnimationItem was at one point changed to contain a std::list, when we started implementing the UI to support a list of effects. However, that UI (and the support for it) turned out to be too complicated so I reverted back in time (thanks to Git) and went back to the original SfxAnimationItem. That, however, made me understand how much the SfxPoolItem actually allows flexibility

Properties, ItemSets, Item Pools, etc VS Animation effects

In the start, there was two completely different items:
1) Something representing an entry in a stylesheet (think font color, font size, bold, bullet type...)
2) An animation effect
These are two completely different items, and had to be somehow connected.
Stylesheets are basically a wrapper around so-called ItemPools - which is a bunch of SfxPoolItem-s.
SfxPoolItem is a base class whose instance can be contained in an item pool.
http://docs.go-oo.org/svtools/html/classSfxPoolItem.html
The names of the extended class speak for themselves - bool item, flag item, int item...each instance of a Sfx*Item represents exactly one piece of data. E.g. a bool item can hold a true/false, an int item - an integer value, etc.
How are these tied to 'what' that flag/int is about? Each SfxPoolItem has to have a "Which" - which is a unique ID which ties it to what information is that item used for.

In the case of this project, we needed to make a special item that would somehow describe the whole animation. The whichId (or just simply 'Which'), is defined here: sd/inc/app.hrc just as a constant:
#define SID_ANIMATION_ITEM (SID_SD_START+446)

Luckily, there was already a simple enough data structure which stored all the effect data. The name is STLPropertySet, and it was used only by the CustomAnimationPane (the effect chooser on the right side) to collect the data, once someone would choose an effect. That structure had to be taken out of that class, out of the UI library, and wrapped into an SfxAnimationItem.

The project's over...officially

The result is proudly here: http://www.youtube.com/watch?v=VcAbfUNpuIA
The patch is, a bit less proudly, here: http://pastebin.com/TK5cTYFQ
The result of this project would highly benefit the people who focus on the content of a presentation itself...and don't care about animating each slide differently, but still dislike the default "I just appear" effect that's default in Impress.

Making this was a lot of pain, although it doesn't look like it. There are, of course, things that don't perfectly work. They are cleverly hidden in the demo :) However, the exact points of fixing and improving have been pointed out in the code, but the time was running short.

I have spent the last three months with a 'I will first code, then blog" attitude, so I will now rewrite my experiences, and pain points, and how it all fits together in the end :)

Hope you have fun reading this, that's all that hacking's about, no?