Public C++ API

From Scribus Wiki
Jump to navigation Jump to search
This article is part of the Scripts series.

Public C++ API

Scribus's internal interfaces are in constant flux as code is improved and refactored. They're also sometimes complex, hard to use, and often underdocumented. Often performance takes priority over easy understanding for the new user, and some have a considerable amount of historical cruft.

For all these reasons, we would benefit signifiantly from providing a new public C++ API to Scribus's functionality. This would be a collection of wrappers around the core code that:

  • Hide internal-only structure and complexity
  • Abstracted some functionality into simpler, but perhaps slower, interfaces
  • Handle ownership issues related to exposing the API to refcounting or GC'd languages with:
    • Handles or proxy objects for important internal objects
    • Factory functions rather than direct ctor use
  • Hides direct use of pointers to structures behind indexes into structures, names, proxy objects, or other abstractions.
  • Logically groups functionality, often consolidating a number of objects into one
    • Needs interfaces to:
      • Text system
      • Pages
      • PageItems
      • Document manipulation (sizes, export, page management)
      • Application-wide functions (open doc etc)
      • GUI (menus, toolbars, etc)
    • See current scripter for an outline of some of the needed functionality.
  • Provides important hooks for extending the GUI (exposes menu manager, key toolbars, action manager, etc)
  • Exposes signals that can be connected to when a script/plugin wants to be informed of important events

Design Beginnings

We need to start, I think, by (a) looking at how others have done it (especially KDE apps supporting Kross, see here the Kross Tutorials), and (b) writing down everything we want the plug-in API to enable people to do. The existing scripter will serve as the beginnings of a guide to what's needed.

Once we know what we want to initially expose, we collect functionality into logical groups or entities based on what it applies to or acts on (works on the entire app vs works on the document vs works on a text pageitem, etc). That gives the beginnings of the structure we'd need the API to have as far as users of it are concerned.

Details

Object Lifetimes & Memory Management

One thing I am pretty clear on is how to handle object lifetime issues. A script or plug-in may wish to use objects that existed before it was loaded, or wish to create objects that will exist after it is unloaded. It should be able to do this without explicitly managing object lifetimes or performing manual memory management - especially for interfaces to languages that handle memory differently (using a gc, refcounting, etc). I think the best way to handle this is the use of proxy objects, or handles, in cases where a 1:1 shadowing of an internal object is required.

Deleting a proxy object ( a handle ) should not affect the behaviour or lifetime of the shadowed internal object. Nor should creating one. The handle should be able to tell when the referenced object is invalidated/destroyed and flag its self as invalid, so further operations on it throw an exception. For shadowed QObjects this is easily accomplished by connecting the proxy to a signal such as the automatic destroyed() signal emitted by a QObject as it's deleted, or a custom signal indicating invalidation for more complex cases. In other words, a handle to an object should work much like a weak smart pointer does, but should also provide wrappers for useful API for the internal object. The behaviour described above matches closely to how SIP works, and it's far from perfect, but probably as good as you'll get when faced with an environment where a user might be manipulating the same objects your script/plugin is.

Units

Another thing I think is critically important is that all API calls should use the internal units. Experience with the scripter has shown that supporting user-set units in code is the quick way to scripts that behave inconsistently based on different user settings, bugs, "interesting" floating point rounding issues, and general insanity. All API calls should use `pt'. No exceptions. It should be possible to use the API to query for the units the user wants to see so scripts can convert displayed units, and input data, to the right units, and we should probably eventually expose some widgets and input dialogs that handle units automatically. All API calls should take measurements in pt, however, and return measurements in pt. API users can use the exported units enums to convert from pt to other units, or maybe conversion functions - not sure which is the better interface.

Having multiple units supported in the API leads to bugs where the plugin/script is tested and modified by a user using one set of units, and consequently malfunctions when used by other users. It also leads to a lot of unnecessary unit conversions, possibly causing a loss of precision. Units only need conversion on display to the user or input from the user, and should only be converted then. We can provide tools, as mentioned above, to make it easier for script and plugin writers to take input and display data in the users' preferred units, but should stick to our internal units for anything not directly involving the user.

Possible Benefits

If done well, this public API may let us actually hide much of the current innards entirely from scripts and plug-ins. They should see only the wrapper API headers, and should need to link only to wrapper API symbols. We should be able to reduce the size of the exported symbol table (improving binary size and link times), more easily expose functionality to new languages like QtScript, make it easier to write highly functional plug-ins and scripts, etc. Handily, we could also provide a more _stable_ interface to plug-ins and scripts, so fewer core changes would require incompatible changes in scripts or plug-ins.