Monday, May 31, 2010

Dockable IDE

Mattias did an good job in the move towards a dockable IDE :-)

There exist a few problems with the form creation, that prevent proper operation of drag-dock. At best the IDE should not try to dock or otherwise place dockable windows.

A general problem is the update of the Caption of docked forms - their Caption is invisible! As a simple solution every change of the Caption of a docked control should be sent to its HostDockSite, so that it can update the DockCaption. An according UpdateDockCaption method should be added to TWinControl, which does nothing by default; TCustomForm instead updates its Caption, so that all (floathost...) docksites will always show the proper caption.

Monday, April 26, 2010


Some flicker and other nasty things can be caused by excessive or inappropriate coordinate re-calculations while docking. This is partially caused by the flawed Delphi implementation, and is made even worse in further LCL hacks. Since the TDockManager interface has been improved already, much code can be removed from the LCL.

First we should agree that managed and unmanaged docksites require different handling of docking coordinates and other details. When a DockManager is installed in the target site, it will manage the DockRect (visual feedback) and finally will adjust the placement of the dropped control, including dock headers etc., eventually overriding user changes in event handlers. Without an DockManager, some calculations should be performed and reflected in the DockRect, and no final adjustment should be performed - this would be fooling the user. Also the calculation of BoundsForNewParent is questionable, at least for managed sites. Premature changes of the BoundsRect are a bad idea at all.

I'll outline the required changes to the TControl and TWinControl classes in futher comments to this entry, based on Lazarus revision 24970.

Docking Forms

Since most widgetsets do not (yet) allow to dock windows in the LCL, a commonly usable workaround must be found. Affected are all controls that eat up mouse events themselves, or which do not create events for the non-client area.

A stable and widgetset-independent solution requires a dedicated "dock grip" component, integrated into the forms or controls. That component can react on mouse moves with the left button down, by starting the dragging of the associated control; this can be the control's Parent (form, toolbar), the page of a notebook, or the control in a dock-zone.

My first approach used a red pin icon in the client area of a form, but this turned out to be very sensitive to changes in the layout (Z-order...) at runtime. It also eats up some space in an otherwise tabular layout, where it should occupy a cell or entire row or column of its own. E.g. a (editor) notebook has no room left for an additional control inside its tabs or editor page.

The next (current) approach uses dockheaders, as already implemented in a docktree or dockbook, to start dragging of the associated client control or notebook page. For that purpose all floating controls (forms...) have to be wrapped into a FloatHostSite, whose DockManager already implements all the functionality of the dockheader. When the dockheader is styled like the grip of a toolbar, this approach fits nicely into any GUI. The DockManager also allows to dock further controls into the same site, eliminating the need for additional ConJoinDockHost sites etc.

Another approach could use dedicated dock-grip controls, as bars with the appearance of commonly used grips (in toolbars...). These tiny bars could be added to a side of any form, just like elastic sites are added, leaving the form layout intact except for a minor reduction of the client width or height. But these controls should be hidden whenever the form is docked, since then the dockheader of the docksite would play the role of the dock-grip. Furthermore such controls would not allow to dock forms together, so that I won't consider this approach right now.

There remain still at least two major issues with wrapping dockable controls into a floating site, the automatic creation of the appropriate FloatHost sites, and the handling of these sites in the store/reload logic of a layout. I already tested the used of the FloatingDockSiteClass property, and found it working in the LCL even with forms, that otherwise float for themselves; perhaps somebody anticipated my idea already?

A minor issue with such floating sites is the visibility of the docked control, when it was just created in invisible state, to reduce flicker. The solution is simple, since it won't do any harm to make the docked client visible in TControl.DoFloatMsg.

Another issue is the exact FloatHost class, that also is used in the Floating property (GetFloating). The meaning or implementation of [Get]Floating had to be changed somehow, when multiple clients can be docked together in the same FloatHost, or in a different FloatHost class. Eventually a dedicated DefaultFloatHostClass can be added to the LCL, that is used with every dockable control (DragKind=dkDock) that has no dedicated FloatingDockSiteClass(=nil).

The currently last issue may deserve more changes to the LCL, related to storing and reloading layouts. Currently persistent layouts are implemented in the DockMaster, which owns all temporary docksites like floating sites, dockbooks and elastic panels. That's why the use of a dedicated DefaultFloatHostClass should be implemented, that is owned by the DockMaster, instead of by Application. At least it should be possible to change the Owner of the floating sites when these are created, but this only would defer the problem, not solve it.

The current implementation of the EasyDockTree and DockMaster try to reduce the dependencies on other (non-LCL) classes, but this is feasable only to some degree. The DockManager with notebook docking capabilities already requires an according notebook component. The DockMaster introduces further classes for floating dock sites and elastic panels, which support the layout streaming, and introduces a layout storage format. All these classes are not required in applications that do not use docking at all, or don't use persistent layouts. But when used, the LCL has to know about some of them.

Minimal impact on the LCL could consist of added global variables, or Application properties, describing the DefaultFloatHostClass and the DockMaster instance (of type TComponent).

A more comfortable implementation would add a TDockMaster or TDesktopLayout base class to the already defined TDockManager class, with methods to stream (and manage?) desktop layouts. The default implementation would do nothing, and an application can install an instance of any derived class. More details will be found out in the practical integration of an DockMaster into the IDE.

Please let me know what you think about my ideas, and what complications I may have missed.

Monday, February 8, 2010

More Flexible Docking

The Delphi docking model happens to implement (by accident?) a feature, which I'd call top-level docking. When no DropOn control is found by the stupid DragManager/TControl methods, the dropped control is docked into the root zone of the docktree. This can happen on e.g. an drop onto an zone header, for which only the DockManager can determine the associated control, or on an drop outside the target DockSite, but within its InfluenceRect.

I've already tried to implement docking relative to dockzones, instead of relative to already docked controls, but didn't follow this approach further. The idea was to take into account the distance of the drop position, relative to the center of a dockzone. From this approach only the notebook-docking has survived, forced on an drop near the center of an already docked control. A similar override could be made for positions close to the boundary of a docked control, but I couldn't find a simple implementation with obvious DockRect feedback.

Now the idea comes up again, with drop positions near the boundaries of an entire DockSite. The existing code only allows for extending the root (or any other) zone only in its established horizontal/vertical orientation (isogonal), and this behaviour now should be extended to orthogonal docking at least for the root zone.

Let's take the case of DropOnControl=Nil to force an drop into the root zone. This condition must be checked at least twice, once in GetDockEdge and later in InsertControl. In both methods a search for the closest already docked control must be skipped, when the drop position is out of (or close to) the boundaries of the DockSite. In InsertControl another check may be required, to distinguish an drop into an empty DockSite from an drop into the root zone of an already populated DockSite. Everything were much easier to implement if the dragged control would not try to determine the DropOnControl and DockRect itself, without any knowledge about the DockManager's dockzones :-(