summaryrefslogtreecommitdiffstats
path: root/vcl/README
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/README242
-rw-r--r--vcl/README.GDIMetaFile190
-rw-r--r--vcl/README.lifecycle325
-rw-r--r--vcl/README.scheduler394
-rw-r--r--vcl/README.vars52
5 files changed, 1203 insertions, 0 deletions
diff --git a/vcl/README b/vcl/README
new file mode 100644
index 000000000..e0944688f
--- /dev/null
+++ b/vcl/README
@@ -0,0 +1,242 @@
+Visual Class Library is responsible for the widgets (windowing, buttons, controls, file-pickers etc.), operating system abstraction, including basic rendering (e.g. the output device).
+
+It should not be confused with Borland's Visual Component Library, which is entirely unrelated.
+
+VCL provides a graphical toolkit similar to gtk+, Qt, SWING etc.
+
+source/
+ + the main cross-platform chunk of source
+
+inc/
+ + cross-platform abstraction headers
+
+headless/
+ + a backend renderer that draws to bitmaps
+
+android/
+ + Android backend
+
+osx/
+ + macOS backend
+
+ios/
+ + iOS backend
+
+quartz/
+ + code common to macOS and iOS
+
+win/
+ + Windows backend
+
+qt5/
+ + Qt5 (under construction)
+
+unx/
+ + X11 backend and its sub-platforms
+ gtk3/
+ + GTK3 support
+ kf5/
+ + KF5 support (based on qt5 VCL plugin mentioned above)
+ gtk3_kde5/
+ + GTK3 support with KDE5 file pickers (alternative to native kf5 one)
+ generic/
+ + raw X11 support
+
+ plugadapt/
+ + pluggable framework to select correct unx backend
+
+ dtrans/
+ + "data transfer" - clipboard handling
+ + http://stackoverflow.com/questions/3261379/getting-html-source-or-rich-text-from-the-x-clipboard
+ for tips how to show the current content of the
+ clipboard
+
+
+How the platform abstraction works
+
+ + InitVCL calls 'CreateSalInstance'
+ + this is implemented by the compiled-in platform backend
+ + it stores various bits of global state in the
+ 'SalData' (inc/saldatabasic.hxx) structure but:
+ + the SalInstance vtable is the primary outward facing gateway
+ API for platform backends
+ + It is a factory for:
+ SalFrames, SalVirtualDevices, SalPrinters,
+ Timers, the SolarMutex, Drag&Drop and other
+ objects, as well as the primary event loop wrapper.
+
+Note: references to "SV" in the code mean StarView, which was a
+portable C++ class library for GUIs, with very old roots, that was
+developed by StarDivision. Nowadays it is not used by anything except
+LibreOffice (and OpenOffice).
+
+"svp" stands for "StarView Plugin".
+
+== COM threading ==
+
+The way COM is used in LO generally:
+- vcl puts main thread into Single-threaded Apartment (STA)
+- oslWorkerWrapperFunction() puts every thread spawned via oslCreateThread()
+ into MTA (free-threaded)
+
+== GDIMetafile ==
+
+GDIMetafile is a vector drawing representation that corresponds directly
+to the SVM (StarView Metafile) format; it is extremely important as
+an intermediate format in all sorts of drawing and printing operations.
+
+There is a class MetafileXmlDump in include/vcl/mtfxmldump.hxx that
+can store a GDIMetafile as XML, which makes debugging much easier
+since you can just use "diff" to see changes.
+
+== EMF+ ==
+
+emf+ is vector file format used by MSO and is successor of wmf and
+emf formats. see
+http://msdn.microsoft.com/en-us/library/cc230724.aspx for
+documentation. note that we didn't have this documentation from
+start, so part of the code predates to the time when we had guessed
+some parts and can be enhanced today. there also still many thing not
+complete
+
+emf+ is handled a bit differently compared to original emf/wmf files,
+because GDIMetafile is missing features we need (mostly related to
+transparency, argb colors, etc.)
+
+emf/wmf is translated to GDIMetafile in import filter
+vcl/source/filter/wmf and so special handling ends here
+
+emf+ is encapsulated into GDIMetafile inside comment records and
+parsed/rendered later, when it reaches cppcanvas. It is parsed and
+rendered in cppcanvas/source/mtfrenderer. also note that there are
+emf+-only and emf+-dual files. dual files contains both types of
+records (emf and emf+) for rendering the images. these can used also
+in applications which don't know emf+. in that case we must ignore
+emf records and use emf+ for rendering. for more details see
+documentation
+
+parsing:
+
+ wmf/emf filter --> GDI metafile with emf+ in comments --> cppcanvas metafile renderer
+
+lately the GDIMetafile rendering path changed which also influenced
+emf+ rendering. now many things happen in drawing layer, where
+GDIMetafile is translated into drawing layer primitives. for
+metafiles with emf+ we let the mtfrenderer render them into bitmap
+(with transparency) and use this bitmap in drawinlayer. cleaner
+solution for current state would be to extend the drawing layer for
+missing features and move parsing into drawing layer (might be quite
+a lot of work). intermediary enhancement would be to know better the
+needed size/resolution of the bitmap, before we render emf+ into
+bitmap in drawing layer. Thorsten is working on the same problem with
+svg rendering, so hopefully his approach could be extended for emf+ as
+well. the places in drawing layer where we use canvas mtfrenderer to
+render into bitmaps can be found when you grep for GetUseCanvas. also
+look at vcl/source/gdi/gdimetafile.cxx where you can look for
+UseCanvas again. moving the parsing into drawinglayer might also have
+nice side effect for emf+-dual metafiles. in case the emf+ records
+are broken, it would be easier to use the duplicit emf
+rendering. fortunately we didn't run into such a broken emf+ file
+yet. but there were already few cases where we first though that the
+problem might be because of broken emf+ part. so far it always turned
+out to be another problem.
+
+rendering:
+
+ before
+
+ vcl --> cppcanvas metafile renderer --> vcl
+
+ now
+
+ drawing layer --> vcl --> cppcanvas metafile renderer --> vcl --> drawing layer
+
+another interesting part is actual rendering into canvas bitmap and
+using that bitmap later in code using vcl API.
+
+EMF+ implementation has some extensive logging, best if you do a dbgutil
+build, and then
+
+export SAL_LOG=+INFO.cppcanvas.emf+INFO.vcl.emf
+
+before running LibreOffice; it will give you lots of useful hints.
+
+You can also fallback to EMF (from EMF+) rendering via
+
+export EMF_PLUS_DISABLE=1
+
+
+== Printing/PDF export ==
+
+Printing from Writer works like this:
+
+1) individual pages print by passing an appropriate OutputDevice to XRenderable
+2) in drawinglayer, a VclMetafileProcessor2D is used to record everything on
+ the page (because the OutputDevice has been set up to record a GDIMetaFile)
+3) the pages' GDIMetaFiles are converted to PDF by the vcl::PDFWriter
+ in vcl/source/gdi/pdfwriter*
+
+Creating the ODF thumbnail for the first page works as above except step 3 is:
+
+3) the GDIMetaFile is replayed to create the thumbnail
+
+On-screen display differs in step 1 and 2:
+
+1) the VCL Window gets invalidated somehow and paints itself
+2) in drawinglayer, a VclPixelProcessor2D is used to display the content
+
+
+=== Debugging PDF export ===
+
+Debugging the PDF export becomes much easier when
+compression is disabled (so the PDF file is directly readable) and
+the MARK function puts comments into the PDF file about which method
+generated the following PDF content.
+
+The compression can be disabled even using an env. var:
+
+export VCL_DEBUG_DISABLE_PDFCOMPRESSION=1
+
+To de-compress the contents of a PDF file written by a release build or
+other programs, use the "pdfunzip" tool:
+
+bin/run pdfunzip input.pdf output.pdf
+
+=== SolarMutexGuard ===
+
+The solar mutex is the "big kernel lock" of LibreOffice, a global one. It's a
+recursive mutex, so it's allowed to take the lock on the same thread multiple
+times, and only the last unlock will actually release the mutex.
+
+UNO methods on components can be called from multiple threads, while the
+majority of the codebase is not prepared for multi-threading. One way to get
+around this mismatch is to create a SolarMutexGuard instance at the start of
+each & every UNO method implementation, but only when it is necessary:
+
+- Only acquire the SolarMutex if you actually need it (e.g., not in functions
+ that return static information).
+
+- Only around the code that actually needs it (i.e., never call out with it
+ locked).
+
+This way you ensure that code (not prepared for multithreading) is still
+executed only on a single thread.
+
+In case you expect that your caller takes the solar mutex, then you can use
+the DBG_TESTSOLARMUTEX() macro to assert that in dbgutil builds.
+
+Event listeners are a special (but frequent) case of the "never call out with
+a mutex (SolarMutex or other) locked" fundamental rule:
+
+- UNO methods can be called from multiple threads, so most implementations
+ take the solar mutex as their first action when necessary.
+
+- This can be problematic if later calling out (an event handler is called),
+ where the called function may be an UNO method implementation as well and
+ may be invoked on a different thread.
+
+- So we try to not own the solar mutex, whenever we call out (invoke event
+ listeners).
+
+In short, never hold any mutex unless necessary, especially not when calling
+out.
diff --git a/vcl/README.GDIMetaFile b/vcl/README.GDIMetaFile
new file mode 100644
index 000000000..98be38d08
--- /dev/null
+++ b/vcl/README.GDIMetaFile
@@ -0,0 +1,190 @@
+GDIMetaFile class
+=================
+
+The GDIMetaFile class reads, writes, manipulates and replays metafiles via the VCL module.
+
+A typical use case is to initialize a new GDIMetaFile, open the actual stored metafile and
+read it in via GDIMetaFile::Read( aIStream ). This reads in the metafile into the GDIMetafile
+object - it can read in an old-style VCLMTF metafile (back in the days that Microsoft didn't
+document the metafile format this was used), as well as EMF+ files - and adds them to a list
+(vector) of MetaActions. You can also populate your own GDIMetaFile via AddAction(),
+RemoveAction(), ReplaceAction(), etc.
+
+Once the GDIMetafile object is read to be used, you can "play" the metafile, pause it, wind
+forward or rewind the metafile. The metafile can be moved, scaled, rotated and clipped, as
+well have the colours adjusted or replaced, or even made monochrome.
+
+The GDIMetafile can be used to get an OutputDevice's metafile via the Linker() and Record()
+functions.
+
+
+Using GDIMetafile
+-----------------
+
+First, create a new GDIMetafile, this can be done via the default constructor. It can then
+be constructed manually, or you can use Record() on an OutputDevice to populate the
+GDIMetaFile, or of course you can read it from file with Read(). From here you can then
+elect to manipulate the metafile, or play it back to another GDIMetafile or to an
+OutputDevice via Play(). To store the file, use Write().
+
+CONSTRUCTORS AND DESTRUCTORS
+
+- GDIMetaFile
+- GDIMetaFile( cosnt GDIMetaFile& rMtf ) - copy constructor
+- ~GDIMetaFile
+
+
+OPERATORS
+
+- operator =
+- operator ==
+- operator !=
+
+
+RECORDING AND PLAYBACK FUNCTIONS
+
+- Play(GDIMetaFile&, size_t) - play back metafile into another metafile up
+ to position
+- Play(OutputDevice*, size_t) - play back metafile into OutputDevice up to
+ position
+- Play(OutputDevice*, Point, Size, size_t) - play back metafile into OutputDevice at a
+ particular location on the OutputDevice, up
+ to the position in the metafile
+- Pause - pauses or continues the playback
+- IsPause
+- Stop - stop playback fully
+- WindStart - windback to start of the metafile
+- windPrev - windback one record
+- GetActionSize - get the number of records in the metafile
+
+
+METAFILE RECORD FUNCTIONS
+
+- FirstAction - get the first metafile record
+- NextAction - get the next metafile record from the
+ current position
+- GetAction(size_t) - get the metafile record at location in file
+- GetCurAction - get the current metafile record
+- AddAction(MetaAction*) - appends a metafile record
+- AddAction(MetaAction*, size_t) - adds a metafile record to a particular
+ location in the file
+- RemoveAction - removes record at file location
+- Clear - first stops if recording, then removes all
+ metafile records
+- push_back - pushes back, basically a thin wrapper to the
+ metafile record list
+
+
+READ AND WRITING
+
+- Read
+- Write
+- GetChecksum
+- GetSizeBytes
+
+
+DISPLACEMENT FUNCTIONS
+
+- Move( long nX, long nX)
+- Move( long nX, long nX, long nDPIX, long nDPIY ) - Move method getting specifics how to
+ handle MapMode( MapUnit::MapPixel )
+
+
+TRANSFORMATION FUNCTIONS
+
+- Scale( double fScaleX, double fScaleY )
+- Scale( const Fraction& rScaleX, const Fraction& rScaleY )
+- Mirror
+- Rotate( long nAngle10 )
+- Clip( const Rectangle& )
+
+
+COLOR ADJUSTMENT FUNCTIONS
+
+- Adjust - change luminance, contrast, gamma and RGB via a
+ percentage
+- Convert - colour conversion
+- ReplaceColors
+- GetMonochromeMtf
+
+
+Related classes
+---------------
+
+MetaAction: a base class used by all records. It implements a command-like pattern, and also
+acts as a prototype for other actions.
+
+
+CONSTRUCTORS AND DESTRUCTORS
+
+- MetaAction() - default constructor, sets mnRefCount to 1 and
+ mnType, in this case MetaActionType::NONE
+- MetaAction(sal_uInt16 nType) - virtual constructor, sets mnType to nType, and
+ mnRefCount to 1
+- ~MetaAction
+
+
+COMMAND FUNCTION
+
+- Execute(OutputDevice*) - execute the functionality of the record to the
+ OutputDevice. Part of command pattern.
+
+
+FACTORY FUNCTION
+
+- Clone() - prototype clone function
+
+
+MANIPULATION FUNCTIONS
+
+- Move(long nHorzMove, long nVerMove)
+- Scale(double fScaleX, double fScaleY)
+
+
+READ AND WRITE FUNCTIONS
+
+- Read
+- Write
+- ReadMetaAction - a static function, only used to determine which
+ MetaAction to call on to read the record, which
+ means that this is the function that must be used.
+
+
+INTROSPECTIVE FUNCTIONS
+
+- GetType
+
+
+
+A note about MetaCommentAction:
+-------------------------------
+
+So this class is the most interesting - a comment record is what is used to extended metafiles, to
+make what we call an "Enhanced Metafile". This basically gets the OutputDevice's connect metafile
+and adds the record via this when it runs Execute(). It doesn't actually do anything else, unlike
+other MetaActions which invoke functions from OutputDevice. And if there is no connect metafile in
+OutputDevice, then it just does nothing at all in Execute. Everything else works as normal (Read,
+Write, etc).
+
+
+
+Basic pseudocode
+----------------
+
+The following illustrates an exceptionally basic and incomplete implementation of how to use
+GDIMetafile. An example can be found at vcl/workben/mtfdemo.cxx
+
+
+DemoWin::Paint()
+{
+ // assume that VCL has been initialized and a new application created
+
+ Window* pWin = new WorkWindow();
+ GDIMetaFile* pMtf = new GDIMetaFile();
+
+ SvFileStream aFileStream("example.emf", STEAM_READ);
+
+ ReadWindowMetafile(aFileStream, pMtf);
+ pMtf->Play(pWin);
+
+}
diff --git a/vcl/README.lifecycle b/vcl/README.lifecycle
new file mode 100644
index 000000000..a309b65ef
--- /dev/null
+++ b/vcl/README.lifecycle
@@ -0,0 +1,325 @@
+** Understanding transitional VCL lifecycle **
+
+---------- How it used to look ----------
+
+ All VCL classes were explicitly lifecycle managed; so you would
+do:
+ Dialog aDialog(...); // old - on stack allocation
+ aDialog.Execute(...);
+or:
+ Dialog *pDialog = new Dialog(...); // old - manual heap allocation
+ pDialog->Execute(...);
+ delete pDialog;
+or:
+ std::shared_ptr<Dialog> xDialog(new pDialog()); // old
+ xDialog->Execute(...);
+ // depending who shared the ptr this would be freed sometime
+
+ In several cases this lead to rather unpleasant code, when
+various shared_ptr wrappers were used, the lifecycle was far less than
+obvious. Where controls were wrapped by other ref-counted classes -
+such as UNO interfaces, which were also used by native Window
+pointers, the lifecycle became extremely opaque. In addition VCL had
+significant issues with re-enterancy and event emission - adding
+various means such as DogTags to try to detect destruction of a window
+between calls:
+
+ ImplDelData aDogTag( this ); // 'orrible old code
+ Show( true, ShowFlags::NoActivate );
+ if( !aDogTag.IsDead() ) // did 'this' go invalid yet ?
+ Update();
+
+ Unfortunately use of such protection is/was ad-hoc, and far
+from uniform, despite the prevalence of such potential problems.
+
+ When a lifecycle problem was hit, typically it would take the
+form of accessing memory that had been freed, and contained garbage due
+to lingering pointers to freed objects.
+
+
+---------- Where we are now: ----------
+
+ To fix this situation we now have a VclPtr - which is a smart
+ reference-counting pointer (include/vcl/vclptr.hxx) which is
+ designed to look and behave -very- much like a normal pointer
+ to reduce code-thrash. VclPtr is used to wrap all OutputDevice
+ derived classes thus:
+
+ VclPtr<Dialog> pDialog( new Dialog( ... ), SAL_NO_ACQUIRE );
+ ...
+ pDialog.disposeAndClear();
+
+ However - while the VclPtr reference count controls the
+ lifecycle of the Dialog object, it is necessary to be able to
+ break reference count cycles. These are extremely common in
+ widget hierarchies as each widget holds (smart) pointers to
+ its parents and also its children.
+
+ Thus - all previous 'delete' calls are replaced with 'dispose'
+ method calls:
+
+** What is dispose ?
+
+ Dispose is defined to be a method that releases all references
+ that an object holds - thus allowing their underlying
+ resources to be released. However - in this specific case it
+ also releases all backing graphical resources. In practical
+ terms, all destructor functionality has been moved into
+ 'dispose' methods, in order to provide a minimal initial
+ behavioral change.
+
+ As such a VclPtr can have three states:
+
+ VclPtr<PushButton> pButton;
+ ...
+ assert (pButton == nullptr || !pButton); // null
+ assert (pButton && !pButton->IsDisposed()); // alive
+ assert (pButton && pButton->IsDisposed()); // disposed
+
+** ScopedVclPtr - making disposes easier
+
+ While replacing existing code with new, it can be a bit
+ tiresome to have to manually add 'disposeAndClear()'
+ calls to VclPtr<> instances.
+
+ Luckily it is easy to avoid that with a ScopedVclPtr which
+ does this for you when it goes out of scope.
+
+** One extra gotcha - an initial reference-count of 1
+
+ In the normal world of love and sanity, eg. creating UNO
+ objects, the objects start with a ref-count of zero. Thus
+ the first reference is always taken after construction by
+ the surrounding smart pointer.
+
+ Unfortunately, the existing VCL code is somewhat tortured,
+ and does a lot of reference and de-reference action on the
+ class -during- construction. This forces us to construct with
+ a reference of 1 - and to hand that into the initial smart
+ pointer with a SAL_NO_ACQUIRE.
+
+ To make this easier, we have 'Instance' template wrappers
+ that make this apparently easier, by constructing the
+ pointer for you.
+
+** How does my familiar code change ?
+
+ Lets tweak the exemplary code above to fit the new model:
+
+- Dialog aDialog(... dialog params ... );
+- aDialog.Execute(...);
++ ScopedVclPtrInstance<Dialog> pDialog(... dialog params ... );
++ pDialog->Execute(...); // VclPtr behaves much like a pointer
+
+or:
+- Dialog *pDialog = new Dialog(... dialog params ...);
++ VclPtrInstance<Dialog> pDialog(... dialog params ...);
+ pDialog->Execute(...);
+- delete pDialog;
++ pDialog.disposeAndClear(); // done manually - replaces a delete
+or:
+- std::shared_ptr<Dialog> xDialog(new Dialog(...));
++ ScopedVclPtrInstance<Dialog> xDialog(...);
+ xDialog->Execute(...);
++ // depending how shared_ptr was shared perhaps
++ // someone else gets a VclPtr to xDialog
+or:
+- VirtualDevice aDev;
++ ScopedVclPtrInstance<VirtualDevice> pDev;
+
+ Other things that are changed are these:
+
+- pButton = new PushButton(NULL);
++ pButton = VclPtr<PushButton>::Create(nullptr);
+...
+- vcl::Window *pWindow = new PushButton(NULL);
++ VclPtr<vcl::Window> pWindow;
++ pWindow.reset(VclPtr<PushButton>::Create(nullptr));
+
+** Why are these 'disposeOnce' calls in destructors ?
+
+ This is an interim measure while we are migrating, such that
+ it is possible to delete an object conventionally and ensure
+ that its dispose method gets called. In the 'end' we would
+ instead assert that a Window has been disposed in its
+ destructor, and elide these calls.
+
+ As the object's vtable is altered as we go down the
+ destruction process, and we want to call the correct dispose
+ methods we need this disposeOnce(); call for the interim in
+ every destructor. This is enforced by a clang plugin.
+
+ The plus side of disposeOnce is that the mechanics behind it
+ ensure that a dispose() method is only called a single time,
+ simplifying their implementation.
+
+
+---------- Who owns & disposes what ? ----------
+
+ Window sub-classes tend to create their widgets in one of two
+ways and often both.
+
+ 1. Derive from VclBuilderContainer. The VclBuilder then owns
+ many of the sub-windows, which are fetched by a 'get'
+ method into local variables often in constructors eg.
+
+ VclPtr<PushButton> mpButton; // in the class
+ , get(mpButton, "buttonName") // in the constructor
+ mpButton.clear(); // in dispose.
+
+ We only clear, not disposeAndClear() in our dispose method
+ for this case, since the VclBuilder / Container truly owns
+ this Window, and needs to dispose its hierarchy in the
+ right order - first children then parents.
+
+ 2. Explicitly allocated Windows. These are often created and
+ managed by custom widgets:
+
+ VclPtr<ComplexWidget> mpComplex; // in the class
+ , mpComplex( VclPtr<ComplexWidget>::Create( this ) ) // constructor
+ mpComplex.disposeAndClear(); // in dispose
+
+ ie. an owner has to dispose things they explicitly allocate.
+
+ In order to ensure that the VclBuilderConstructor
+ sub-classes have their Windows disposed at the correct time
+ there is a disposeBuilder(); method - that should be added
+ -only- to the class immediately deriving from
+ VclBuilderContainer's dispose.
+
+---------- What remains to be done ? ----------
+
+ * Cleanup DogTags
+
+ * Expand the VclPtr pattern to many other less
+ than safe VCL types.
+
+ * create factory functions for VclPtr<> types and privatize
+ their constructors.
+
+ * Pass 'const VclPtr<> &' instead of pointers everywhere
+ + add 'explicit' keywords to VclPtr constructors to
+ accelerate compilation etc.
+
+ * Cleanup common existing methods such that they continue to
+ work post-dispose.
+
+ * Dispose functions should be audited to:
+ + not leave dangling pointsr
+ + shrink them - some work should incrementally
+ migrate back to destructors.
+
+ * VclBuilder
+ + ideally should keep a reference to pointers assigned
+ in 'get()' calls - to avoid needing explicit 'clear'
+ code in destructors.
+
+ * VclBuilder 'makeFoo' methods
+ + these should return VclPtr<> types and have their
+ signatures adjusted en-masse.
+ + currently we use a VclPtr<> constructor with
+ SAL_NO_ACQUIRE inside the builder.
+
+---------- FAQ / debugging hints ----------
+
+** Compile with dbgutil
+
+ This is by far the best way to turn on debugging and
+ assertions that help you find problems. In particular
+ there are a few that are really helpful:
+
+ vcl/source/window/window.cxx (Window::dispose)
+ "Window ( N4sfx27sidebar20SidebarDockingWindowE (Properties))
+ ^^^ class name window title ^^^
+ with live children destroyed: N4sfx27sidebar6TabBarE ()
+ N4sfx27sidebar4DeckE () 10FixedImage ()"
+
+ You can de-mangle these names if you can't read them thus:
+
+ $ c++filt -t N4sfx27sidebar20SidebarDockingWindowE
+ sfx2::sidebar::SidebarDockingWindow
+
+ In the above case - it is clear that the children have not been
+ disposed before their parents. As an aside, having a dispose chain
+ separate from destructors allows us to emit real type names for
+ parents here.
+
+ To fix this, we will need to get the dispose ordering right,
+ occasionally in the conversion we re-ordered destruction, or
+ omitted a disposeAndClear() in a ::dispose() method.
+
+ => If you see this, check the order of disposeAndClear() in
+ the sfx2::Sidebar::SidebarDockingWindow::dispose() method
+
+ => also worth git grepping for 'new sfx::sidebar::TabBar' to
+ see where those children were added.
+
+** Check what it used to do
+
+ While a ton of effort has been put into ensuring that the new
+ lifecycle code is the functional equivalent of the old code,
+ the code was created by humans. If you identify an area where
+ something asserts or crashes here are a few helpful heuristics:
+
+ * Read the git log -u -- path/to/file.cxx
+
+ => Is the order of destruction different ?
+
+ in the past many things were destructed (in reverse order of
+ declaration in the class) without explicit code. Some of these
+ may be important to do explicitly at the end of the destructor.
+
+ eg. having a 'Idle' or 'Timer' as a member, may now need an
+ explicit .Stop() and/or protection from running on a
+ disposed Window in its callback.
+
+ => Is it 'clear' not 'disposeAndClear' ?
+
+ sometimes we get this wrong. If the code previously used to
+ use 'delete pFoo;' it should now read pFoo->disposeAndClear();
+ Conversely if it didn't delete it, it should be 'clear()' it
+ is by far the best to leave disposing to the VclBuilder where
+ possible.
+
+ In simple cases, if we allocate the widget with VclPtrInstance
+ or VclPtr<Foo>::Create - then we need to disposeAndClear it too.
+
+** Event / focus / notification ordering
+
+ In the old world, a large amount of work was done in the
+ ~Window destructor that is now done in Window::dispose.
+
+ Since those Windows were in the process of being destroyed
+ themselves, their vtables were adjusted to only invoke Window
+ methods. In the new world, sub-classed methods such as
+ PreNotify, GetFocus, LoseFocus and others are invoked all down
+ the inheritance chain from children to parent, during dispose.
+
+ The easiest way to fix these is to just ensure that these
+ cleanup methods, especially LoseFocus, continue to work even
+ on disposed Window sub-class instances.
+
+** It crashes with some invalid memory...
+
+ Assuming that the invalid memory is a Window sub-class itself,
+ then almost certainly there is some cockup in the
+ reference-counting; eg. if you hit an OutputDevice::release
+ assert on mnRefCount - then almost certainly you have a
+ Window that has already been destroyed. This can easily
+ happen via this sort of pattern:
+
+ Dialog *pDlg = VclPtr<Dialog>(nullptr /* parent */);
+ // by here the pDlg quite probably points to free'd memory...
+
+ It is necessary in these cases to ensure that the *pDlg is
+ a VclPtr<Dialog> instead.
+
+** It crashes with some invalid memory #2...
+
+ Often a ::dispose method will free some pImpl member, but
+ not NULL it; and (cf. above) we can now get various virtual
+ methods called post-dispose; so:
+
+ a) delete pImpl; pImpl = NULL; // in the destructor
+ b) if (pImpl && ...) // in the subsequently called method
+
diff --git a/vcl/README.scheduler b/vcl/README.scheduler
new file mode 100644
index 000000000..52c76dac5
--- /dev/null
+++ b/vcl/README.scheduler
@@ -0,0 +1,394 @@
+= Introduction =
+
+The VCL scheduler handles LOs primary event queue. It is simple by design,
+currently just a single-linked list, processed in list-order by priority
+using round-robin for reoccurring tasks.
+
+The scheduler has the following behaviour:
+
+B.1. Tasks are scheduled just priority based
+B.2. Implicitly cooperative AKA non-preemptive
+B.3. It's not "fair" in any way (a consequence of B.2)
+B.4. Tasks are handled round-robin (per priority)
+B.5. Higher priorities have lower values
+B.6. A small set of priorities instead of an flexible value AKA int
+
+There are some consequences due to this design.
+
+C.1. Higher priority tasks starve lower priority tasks
+ As long as a higher task is available, lower tasks are never run!
+ See Anti-pattern.
+
+C.2. Tasks should be split into sensible blocks
+ If this can't really be done, process pending tasks by calling
+ Application::Reschedule(). Or use a thread.
+
+C.3. This is not an OS scheduler
+ There is no real way to "fix" B.2. and B.3.
+ If you need to do a preemptive task, use a thread!
+ Otherwise make your task suspendable.
+
+
+= Driving the scheduler AKA the system timer =
+
+ 1. There is just one system timer, which drives LO event loop
+ 2. The timer has to run in the main window thread
+ 3. The scheduler is run with the Solar mutex acquired
+ 4. The system timer is a single-shot timer
+ 5. The scheduler system event / message has a low system priority.
+ All system events should have a higher priority.
+
+Every time a task is started, the scheduler timer is adjusted. When the timer
+fires, it posts an event to the system message queue. If the next most
+important task is an Idle (AKA instant, 0ms timeout), the event is pushed to
+the back of the queue, so we don't starve system messages, otherwise to the
+front.
+
+Every time the scheduler is invoked it searches for the next task to process,
+restarts the timer with the timeout for the next event and then invokes the
+task. After invoking the task and if the task is still active, it is pushed
+to the end of the queue and the timeout is eventually adjusted.
+
+
+= Locking =
+
+The locking is quite primitive: all interaction with internal Scheduler
+structures are locked. This includes the ImplSchedulerContext and the
+Task::mpSchedulerData, which is actually a part of the scheduler.
+Before invoking the task, we have to release the lock, so others can
+Start new Tasks.
+
+
+= Lifecycle / thread-safety of Scheduler-based objects =
+
+A scheduler object it thread-safe in the way, that it can be associated to
+any thread and any thread is free to call any functions on it. The owner must
+guarantee that the Invoke() function can be called, while the Scheduler object
+exists / is not disposed.
+
+
+= Anti-pattern: Dependencies via (fine grained) priorities =
+
+"Idle 1" should run before "Idle 2", therefore give "Idle 1" a higher priority
+then "Idle 2". This just works correct for low frequency idles, but otherwise
+always breaks!
+
+If you have some longer work - even if it can be split by into schedulable,
+smaller blocks - you normally don't want to schedule it with a non-default
+priority, as it starves all lower priority tasks. Even if a block was processed
+in "Idle 1", it is scheduled with the same (higher) priority again. Changing
+the "Idle" to a "Timer" also won't work, as this breaks the dependency.
+
+What is needed is task based dependency handling, so if "Task 1" is done, it
+has to start "Task 2" and if "Task 1" is started again, it has to stop
+"Task 2". This currently has to be done by the implementor, but this feature
+can be added to the scheduler reasonably.
+
+
+= Implementation details =
+
+== General: event priority for DoYield ==
+
+There are three types of events, with different priority:
+
+1. LO user events
+2. System events
+3. LO Scheduler event
+
+They should be processed according to the following code:
+
+bool DoYield( bool bWait, bool bAllCurrent )
+{
+ bool bWasEvent = ProcessUserEvents( bAllCurrent );
+ if ( !bAllCurrent && bWasEvent )
+ return true;
+ bWasEvent = ProcessSystemEvents( bAllCurrent, &bWasSchedulerEvent ) || bWasEvent;
+ if ( !bWasSchedulerEvent && IsSchedulerEvent() )
+ {
+ ProcessSchedulerEvent()
+ bWasEvent = true;
+ }
+ if ( !bWasEvent && bWait )
+ {
+ WaitForSystemEvents();
+ bWasEvent = true;
+ }
+ return bWasEvent;
+}
+
+== General: main thread deferral ==
+
+In almost all VCL backends, we run main thread deferrals by disabling the
+SolarMutex using a boolean. In the case of the redirect, this makes
+tryToAcquire and doAcquire return true or 1, while a release is ignored.
+Also the IsCurrentThread() mutex check function will act accordingly, so all
+the DBG_TESTSOLARMUTEX won't fail.
+
+Since we just disable the locks when we start running the deferred code in the
+main thread, we won't let the main thread run into stuff, where it would
+normally wait for the SolarMutex.
+
+Eventually this will move into the SolarMutex. KDE / Qt also does main
+thread redirects using Qt::BlockingQueuedConnection.
+
+== General: processing all current events for DoYield ==
+
+This is easily implemented for all non-priority queue based implementations.
+Windows and macOS both have a timestamp attached to their events / messages,
+so simply get the current time and just process anything < timestamp.
+For the KDE backend this is already the default behaviour - single event
+processing isn't even supported. The headless backend accomplishes this by
+just processing a copy of the list of current events.
+
+Problematic in this regard is the Gtk+ backend. g_main_context_iteration
+dispatches "only those highest priority event sources". There is no real way
+to tell, when these became ready. I've added a workaround idea to the TODO
+list. FWIW: Qt runs just a single timer source in the glib main context,
+basically the same we're doing with the LO scheduler as a system event.
+
+The gen X11 backend has some levels of redirection, but needs quite some work
+to get this fixed.
+
+== General: non-main thread yield ==
+
+Yielding from a non-main thread must not wait in the main thread, as this
+may block the main thread until some events happen.
+
+Currently we wait on an extra conditional, which is cleared by the main event
+loop.
+
+== General: invalidation of elapsed timer event messages ==
+
+Since the system timer to run the scheduler is single-shot, there should never
+be more than one elapsed timer event in system event queue. When stopping or
+restarting the timer, we eventually have to remove the now invalid event from
+the queue.
+
+But for the Windows and macOS backends this may fail as they have delayed
+posting of events, so a consecutive remove after a post will actually yield no
+remove. On Windows we even get unwanted processing of events outside of the
+main event loop, which may call the Scheduler, as timer management is handled
+in critical scheduler code.
+
+To prevent these problems, we don't even try to remove these events, but
+invalidate them by versioning the timer events. Timer events with invalid
+versions are processed but simply don't run the scheduler.
+
+== General: track time of long running tasks ==
+
+There is TaskStopwatch class. It'll track the time and report a timeout either
+when the tasks time slice is finished or some system event did occur.
+
+Eventually it will be merged into the main scheduler, so each invoked task can
+easily track it's runtime and eventually this can be used to "blame" / find
+other long running tasks, so interactivity can be improved.
+
+There were some questions coming up when implementing it:
+
+=== Why does the scheduler not detect that we only have idle tasks pending,
+and skip the instant timeout? ===
+
+You never know how long a task will run. Currently the scheduler simply asks
+each task when it'll be ready to run, until two runnable tasks are found.
+Normally this is very quick, as LO has a lot of one-shot instant tasks / Idles
+and just a very few long term pending Timers.
+
+Especially UNO calls add a lot of Idles to the task list, which just need to
+be processed in order.
+
+=== Why not use things like Linux timer wheels? ===
+
+LO has relatively few timers and a lot one-shot Idles. 99% of time the search
+for the next task is quick, because there are just ~5 long term timers per
+document (cache invalidation, cursor blinking etc.).
+
+This might become a problem, if you have a lot of open documents, so the long
+term timer list increases AKA for highly loaded LOOL instances.
+
+But the Linux timer wheel mainly relies on the facts that the OS timers are
+expected to not expire, as they are use to catch "error" timeouts, which rarely
+happen, so this definitely not matches LO's usage.
+
+=== Not really usable to find misbehaving tasks ===
+
+The TaskStopwatch class is just a little time keeper + detecting of input
+events. This is not about misbehaving Tasks, but long running tasks, which
+have to yield to the Scheduler, so other Tasks and System events can be
+processed.
+
+There is the TODO to merge the functionality into the Scheduler itself, at
+which point we can think about profiling individual Tasks to improve
+interactivity.
+
+== macOS implementation details ==
+
+Generally the Scheduler is handled as expected, except on resize, which is
+handled with different runloop-modes in macOS. In case of a resize, the normal
+runloop is suspended in sendEvent, so we can't call the scheduler via posted
+main loop-events. Instead the scheduler uses the timer again.
+
+Like the Windows backend, all Cocoa / GUI handling also has to be run in
+the main thread. We're emulating Windows out-of-order PeekMessage processing,
+via a YieldWakeupEvent and two conditionals. When in a RUNINMAIN call, all
+the DBG_TESTSOLARMUTEX calls are disabled, as we can't release the SolarMutex,
+but we can prevent running any other SolarMutex based code. Those wakeup
+events must be ignored to prevent busy-locks. For more info read the "General:
+main thread deferral" section.
+
+We can neither rely on macOS dispatch_sync code block execution nor the
+message handling, as both can't be prioritized or filtered and the first
+does also not allow nested execution and is just processed in sequence.
+
+There is also a workaround for a problem for pushing tasks to an empty queue,
+as [NSApp postEvent: ... atStart: NO] doesn't append the event, if the
+message queue is empty.
+
+An additional problem is the filtering of events on Window close. This drops
+posted timer events, when a Window is closed resulting in a busy DoYield loop,
+so we have to re-post the event, after closing a window.
+
+== Windows implementation details ==
+
+Posted or sent event messages often trigger processing of WndProc in
+PeekMessage, GetMessage or DispatchMessage, independently from the message to
+fetch, remove or dispatch ("During this call, the system delivers pending,
+nonqueued messages..."). Additionally messages have an inherited priority
+based on the function used to generate them. Even if WM_TIMER messages should
+have the lowest priority, a manually posted WM_TIMER is processed with the
+priority of a PostMessage message.
+
+So we're giving up on processing all our Scheduler events as a message in the
+system message loop. Instead we just indicate a 0ms timer message by setting
+the m_bDirectTimeout in the timer object. This timer is always processed, if
+the system message wasn't already our timer. As a result we can also skip the
+polling. All this is one more reason to drop the single message processing
+in favour of always processing all pending (system) events.
+
+There is another special case, we have to handle: window updates during move
+and resize of windows. These system actions run in their own nested message
+loop. So we have to completely switch to timers, even for 0ms. But these
+posted events prevent any event processing, while we're busy. The only viable
+solution seems to be to switch to WM_TIMER based timers, as these generate
+messages with the lowest system priority (but they don't allow 0ms timeouts).
+So processing slows down during resize and move, but we gain working painting,
+even when busy.
+
+An additional workaround is implemented for the delayed queuing of posted
+messages, where PeekMessage in WinSalTimer::Stop() won't be able remove the
+just posted timer callback message. See "General: invalidation of elapsed
+timer event messages" for the details.
+
+To run the required GUI code in the main thread without unlocking the
+SolarMutex, we "disable" it. For more infos read the "General: main thread
+deferral" section.
+
+== KDE implementation details ==
+
+This implementation also works as intended. But there is a different Yield
+handling, because Qts QAbstractEventDispatcher::processEvents will always
+process all pending events.
+
+
+= TODOs and ideas =
+
+== Task dependencies AKA children ==
+
+Every task can have a list of children / a child.
+
+ * When a task is stopped, the children are started.
+ * When a task is started, the children are stopped.
+
+This should be easy to implement.
+
+== Per priority time-sorted queues ==
+
+This would result in O(1) scheduler. It was used in the Linux kernel for some
+time (search Ingo Molnar's O(1) scheduler). This can be a scheduling
+optimization, which would prevent walking longer event list. But probably the
+management overhead would be too large, as we have many one-shot events.
+
+To find the next task the scheduler just walks the (constant) list of priority
+queues and schedules the first ready event of any queue.
+
+The downside of this approach: Insert / Start / Reschedule(for "auto" tasks)
+now need O(log(n)) to find the position in the queue of the priority.
+
+== Always process all (higher priority) pending events ==
+
+Currently Application::Reschedule() processes a single event or "all" events,
+with "all" defined as "100 events" in most backends. This already is ignored
+by the KDE backend, as Qt defines its QAbstractEventDispatcher::processEvents
+processing all pending events (there are ways to skip event classes, but no
+easy way to process just a single event).
+
+Since the Scheduler is always handled by the system message queue, there is
+really no more reasoning to stop after 100 events to prevent LO Scheduler
+starvation.
+
+== Drop static inherited or composed Task objects ==
+
+The sequence of destruction of static objects is not defined. So a static Task
+can not be guaranteed to happen before the Scheduler. When dynamic unloading
+is involved, this becomes an even worse problem. This way we could drop the
+mbStatic workaround from the Task class.
+
+== Run the LO application in its own thread ==
+
+This would probably get rid of most of the macOS and Windows implementation
+details / workarounds, but is quite probably a large amount of work.
+
+Instead of LO running in the main process / thread, we run it in a 2nd thread
+and defer al GUI calls to the main thread. This way it'll hopefully not block
+and can process system events.
+
+That's just a theory - it definitely needs more analysis before even attending
+an implementation.
+
+== Re-evaluate the macOS ImplNSAppPostEvent ==
+
+Probably a solution comparable to the Windows backends delayed PostMessage
+workaround using a validation timestamp is better then the current peek,
+remove, re-postEvent, which has to run in the main thread.
+
+Originally I didn't evaluate, if the event is actually lost or just delayed.
+
+== Drop nMaxEvents from Gtk+ based backends ==
+
+gint last_priority = G_MAXINT;
+bool bWasEvent = false;
+do {
+ gint max_priority;
+ g_main_context_acquire( NULL );
+ bool bHasPending = g_main_context_prepare( NULL, &max_priority );
+ g_main_context_release( NULL );
+ if ( bHasPending )
+ {
+ if ( last_priority > max_priority )
+ {
+ bHasPending = g_main_context_iteration( NULL, bWait );
+ bWasEvent = bWasEvent || bHasPending;
+ }
+ else
+ bHasPending = false;
+ }
+}
+while ( bHasPending )
+
+The idea is to use g_main_context_prepare and keep the max_priority as an
+indicator. We cannot prevent running newer lower events, but we can prevent
+running new higher events, which should be sufficient for most stuff.
+
+This also touches user event processing, which currently runs as a high
+priority idle in the event loop.
+
+== Drop nMaxEvents from gen (X11) backend ==
+
+A few layers of indirection make this code hard to follow. The SalXLib::Yield
+and SalX11Display::Yield architecture makes it impossible to process just the
+current events. This really needs a refactoring and rearchitecture step, which
+will also affect the Gtk+ and KDE backend for the user event handling.
+
+== Merge TaskStopwatch functionality into the Scheduler ==
+
+This way it can be easier used to profile Tasks, eventually to improve LO's
+interactivity.
diff --git a/vcl/README.vars b/vcl/README.vars
new file mode 100644
index 000000000..9f5a10a26
--- /dev/null
+++ b/vcl/README.vars
@@ -0,0 +1,52 @@
+Environment variables in VCL:
+
+General
+-------
+SAL_USE_VCLPLUGIN - use a VCL plugin
+SAL_RTL_ENABLED - Enable RTL UI
+SAL_NO_NWF - disable native widgets
+SAL_FORCEDPI - force a specific DPI (gtk3 & qt5/kf5 plugins only)
+SAL_FORCE_HC - force high-contrast mode
+
+SAL_NO_FONT_LOOKUP - disable font search and fallback and always use a hard-coded font name (for some unit tests)
+
+LO_COLLECT_UIINFO - enable the uitesting logging, value is expected to be a relative file name that
+will be used to write the log under instdir/uitest/.
+
+VCL_DOUBLEBUFFERING_AVOID_PAINT - don't paint the buffer, useful to see where we do direct painting
+VCL_DOUBLEBUFFERING_FORCE_ENABLE - enable double buffered painting
+VCL_DOUBLEBUFFERING_ENABLE - enable a safe subset of double buffered painting (currently in Writer, not in any other applications)
+
+VCL_DEBUG_DISABLE_PDFCOMPRESSION - disable compression in the PDF writer
+
+Gtk+
+----
+
+VCL_GTK3_PAINTDEBUG - in debug builds, if set to 1 then holding down shift+0 forces a redraw event, shift+1 repaints everything, and
+shift+2 dumps cairo frames to pngs as /tmp/frame<n>.png
+
+Bitmap
+------
+VCL_NO_THREAD_SCALE - disable threaded bitmap scale
+VCL_NO_THREAD_IMPORT - disable threaded bitmap import
+EMF_PLUS_DISABLE - use EMF rendering and ignore EMF+ specifics
+
+OpenGL
+------
+SAL_FORCEGL - force enable OpenGL
+SAL_GL_NO_SWAP - disable buffer swapping if set (should show nothing)
+SAL_GL_SLEEP_ON_SWAP - sleep for half a second on each swap-buffers.
+SAL_DISABLE_WATCHDOG - don't start the thread that watches for broken GL/Vulkan/OpenCL drivers
+
+Skia
+----
+SAL_DISABLESKIA=1 - force disabled Skia
+SAL_ENABLESKIA=1 - enable Skia, unless blacklisted (and if the VCL backend supports Skia)
+SAL_FORCESKIA=1 - force using Skia, even if blacklisted
+SAL_SKIA=raster|vulkan - select Skia's drawing method, by default Vulkan is used
+SAL_DISABLE_SKIA_CACHE=1 - disable caching of complex images
+
+OpenGL,Skia
+-----------
+SAL_WITHOUT_WIDGET_CACHE - disable LRU caching of native widget textures
+SAL_DISABLE_GLYPH_CACHING - don't render glyphs through OpenGL textures or Skia surfaces