enumarray for better data arrays – EasyHack

In LibreOffice C++ code, there are many cases where developers have string literals that should be used in the code. If these are messages in the graphical user interface (GUI), they should be added to the translatable messages. But, there are many cases where the string literals has nothing to do with other languages, and they should be used as they are. In this case, enumarray is helpful. Although they are not limited to string literals, and can be used for any data.

Using Symbolic Constants

In old C code, using #define was the preferred way one could give a name to a string literal or other kinds of data. For example, consider this code:

const char[] FRAME_PROPNAME_ASCII_DISPATCHRECORDERSUPPLIER = "DispatchRecorderSupplier";
const char[] FRAME_PROPNAME_ASCII_ISHIDDEN = "IsHidden";
inline constexpr OUString FRAME_PROPNAME_ASCII_LAYOUTMANAGER = "LayoutManager";
const char[] FRAME_PROPNAME_ASCII_TITLE = "Title"_ustr;
const char[] FRAME_PROPNAME_ASCII_INDICATORINTERCEPTION = "IndicatorInterception";
const char[] FRAME_PROPNAME_ASCII_URL = "URL";

And also, the relevant states:

#define FRAME_PROPHANDLE_DISPATCHRECORDERSUPPLIER 0
#define FRAME_PROPHANDLE_ISHIDDEN 1
#define FRAME_PROPHANDLE_LAYOUTMANAGER 2
#define FRAME_PROPHANDLE_TITLE 3
#define FRAME_PROPHANDLE_INDICATORINTERCEPTION 4
#define FRAME_PROPHANDLE_URL 5

Although this C code still works in C++, it is not the desired approach in modern C++.

Using enumarrays

In modern C++ code, you can use enumarry, which is defined in o3tl in LibreOffice. The above code becomes:

enum class FramePropNameASCII
{
    DispatcherRecorderSupplier,
    IsHidden,
    LayoutManager,
    Title,
    IndicatorInterception,
    URL,
    LAST=URL
};

And also, the string literal definitions:

constexpr o3tl::enumarray<FramePropNameASCII, OUString> FramePropName = {
    u"DispatchRecorderSupplier"_ustr,
    u"IsHidden"_ustr,
    u"LayoutManager"_ustr,
    u"Title"_ustr,
    u"IndicatorInterception"_ustr,
    u"URL"_ustr
};

Why an enumarray?

The names are much more readable, as they do not have to be ALL_CAPPS, as per convention for symbolic constants in C. Their usage is also quite easy. For example, one can use [] to access the relevant string literal:

- xPropSet->getPropertyValue( FRAME_PROPNAME_ASCII_LAYOUTMANAGER ) >>= xLayoutManager;
+ xPropSet->getPropertyValue( FramePropName[FramePropNameASCII::LayoutManager] ) >>= xLayoutManager;

Final Notes

In LibreOffice, enumarrays are not limited to string literals, and they can be used with other data. This task is filed as tdf#169155, and if you like, you may try finding some instances in the code and modernize it using enumarrays.

To learn more about LibreOffice development, you can refer to TDF Wiki. You may follow this blog to read about EasyHacks, tutorials and announcements related to LibreOffice development.

enum class instead of unscoped enum – EasyHack

Since C++11 when enum class (also named scoped enum) is introduced, it is preferred to plain enum which is inherited from C programming languages. The task here is to convert the old enum instances to enum class.

Rationale

enum class has many benefits when compared to plain enum, as it provides better type safety among other things. Implicit conversion to integers, lack of ability to define the underlying data type and compatibility issues were some of the problems with plain enum that enum class solved in C++11. Although since then enum has improved and one can specify underlying type in the scoped enumerations.

Plain enums pollute namespace, and you have to pick names that are too long, and have to carry the context inside their names. For example: INETMSG_RFC822_BEGIN inside enum _ImplINetRFC822MessageHeaderState. With an enum class, it is simply written as HeaderState::BEGIN. When placed inside a file/class/namespace that makes it relevant, it is much easier to use: it is more readable, and causes no issues for other identifiers with possible similar names.,

See this change:

You can read more about that in:

Finding Instances

You may find some of the instances with:

$ git grep -w enum *.cxx *.hxx|grep -v "enum class"

When you count it with wc -l, it shows something more than 2k instances.

Examples Commits

You can see some of the previous conversions here, which is around 1k changes:

$ git log --oneline -i -E --grep="convert enum|scoped enum"

This is a good, but lengthy example of such a conversion:

Implementation

First of all, please choose good names for the new enum class and values. For example, you may convert APPLICATION_WINDOW_TITLE into Application::WindowTitle. Therefore, do not use the old names as they were.

Converting enum to enum class is not always straightforward. You should try to understand the code using the enum, and then try to replace it with enum class. You may need to add extra state/values for situations where 0 or -1 or some default value was used. There are cases where a numerical value is used for different conflicting purposes, and then you have to do some sort of conflict resolution to separate those cases.

You may end up modifying more and more files, and a few static_casts where they are absolutely necessary because you are interpreting some integer value read from input. These are the places where you should check the values yourself in the code. You have to make sure that the numerical value is appropriate before casting it to the enum class.

If you want to do bitwise operations, you should use o3tl::typed_flags, for example:

enum class FileViewFlags
{
    None = 0x00,
    MultiSelection = 0x02,
    ShowType = 0x04,
    ShowNone = 0x20,
};

template<> struct o3tl::typed_flags : o3tl::is_typed_flags<FileViewFlags, 0x26> {}

Then, you may use it like this:

    if (nFlags & FileViewFlags::MULTISELECTION)
        mxTreeView->set_selection_mode(SelectionMode::Multiple);

Please note that 0x26 is the mask, and is calculated by applying OR over all possible values. All the values must be non-negative.

Final Notes

This is a simple development task for LibreOffice also known as EasyHack, which is filed in Bugzilla as tdf#168771. These small tasks are defined to help newcomers to LibreOffice development community to improve their skills with LibreOffice coding.

You may find other instances related to C++ here:

Using C++ STL functions instead of loops – EasyHack

C++ Standard library, which resides in std:: namespace provides common classes and functions which can be used by developers. Among them, Standard Template Library (STL) provides classes and functions to better manage data through data structures named containers. Here I discuss how to use STL functions for better processing of data, and avoid loops.

Checking Conditions

To iterate over a container to see if some specific condition is valid for all, any, or none of the elements in that container, C/C++ developers traditionally used loops.

On the other hand, since C++11, there are functions that can handle such cases: all_of, any_of and none_of. These functions process STL containers, and can replace loops. If you want to know if a function returns true for all, any, or none of the items of the container, then you can simply use these functions. This is the EasyHack dedicated to such a change:

Here is an example patch which uses any_of instead of a loop:

-    bool bFound = false;
     // convert ASCII apostrophe to the typographic one
     const OUString aText( rOrig.indexOf( '\'' ) > -1 ? rOrig.replace('\'', u'’') : rOrig );
-    size_t nCnt = aVec.size();
-    for (size_t i = 0;  !bFound && i < nCnt;  ++i)
-    {
-        if (aVec[i] == aText)
-            bFound = true;
-    }
+    const bool bFound = std::any_of(aVec.begin(), aVec.end(),
+        [&aText](const OUString& n){ return n == aText; });

As you can see, the new code is more concise, and avoids using loops.

Conditional Copying, Removing and Finding

If you want to copy, remove or simply find a value in a container which conforms to a specific functions, you may use copy_if, remove_if or find_if.

Again, this is an example patch:

-  for ( size_t i = 0; i < SAL_N_ELEMENTS( arrOEMCP ); ++i )
-        if ( arrOEMCP[i] == codepage )
-            return true;
-
-    return false;
+    return std::find(std::begin(arrOEMCP), std::end(arrOEMCP), codepage) != std::end(arrOEMCP);

Final Words

Refactoring code is a good way to improve knowledge on LibreOffice development. The above EasyHacks are among EasyHacks that everyone can try.

More information about EasyHacks, and how to start working on them can be found on TDF Wiki:

Unit conversion in LibreOffice code – EasyHack

LibreOffice handles different input and output formats, and also displays text and graphics alongside inside the GUI on computer displays. This requires LibreOffice to understand various different measurement units, and convert values from one to another.

Unit selection
Unit selection

The unit conversion can be done by writing extra code, where one should know the units, and calculate factor to convert them to each other.

For example, consider that we want to convert width from points into 1/100 mm, which is used in page setup.

We know that:

1 point = 1/72 inch
1 inch = 25.4 mm = 25400 microns
factor = 25400/(72*10) ≈ 35.27777778

Then, it is possible to write the conversion as:

static int PtTo10Mu( int nPoints )
{
return static_cast<int>((static_cast<double>(nPoints)*35.27777778)+0.5);
}

A separate function that casts integer nPoints to double, then multiplies it by the factor which has 8 decimal points, and then rounds the result by adding 0.5 and then truncates it and stores it in an integer. This approach is not always desirable. It is error-prone, and lacks enough accuracy. For big values, it can calculates values off by one.

Another approach is to use o3tl (OpenOffice.org template library) convert function. It is as simple as writing:

int nResult = o3tl::convert(nPoint, o3tl::Length::pt, o3tl::Length::mm100)

As you can see, it is much cleaner, and gives the output, properly rounded as an integer!

You need a double? No problem! You can use appropriate template to achieve that:

double fResult = o3tl::convert<double>(nPoint, o3tl::Length::pt, o3tl::Length::mm100)

These are the supported units, defined in the header include/o3tl/unit_conversion.hxx:

mm100 – 1/100th mm = 1 micron

mm10 – 1/10 mm

mm – millimeter

cm – centimeter

m – meter

km – kilometer

emu – English Metric Unit (1/360000 cm)

twip – Twentieth of a point (1/20 pt)

pt – Point (1/72 in)

pc – Pica (1/6 in)

in1000 – 1/1000 in

in100 – 1/100 in

in10 – 1/10 in

in – inch

ft – foot

mi – mile

master – PPT Master Unit (1/576 in)

px – Pixel unit (15 twip, 96 ppi)

ch – Char unit (210 twip, 14 px)

line – Line unit (312 twip)

Handling Overflows

If you are doing a conversion, it is possible that the result overflows. With o3tl::convert() you can handle it this way:

sal_Int64 width = o3tl::convert(nPoint, o3tl::Length::pt, o3tl::Length::mm100, overflow, 0);
if (overflow)
{
...
}

Code Pointers

To to find instances to change, one can try finding some magic numbers listed here. For example, consider measuring a line based on twips:

line – Line unit (312 twip)

If you search for 312, you may find some examples:

$ git grep -w 312 *.cxx

Final Words

The task described here is filed as tdf#168226:

EasyHacks are well-defined small tasks that are designed to help newcomers begin LibreOffice programming. If you like it, you can start working on it!

Using o3tl::convert() not only simplifies the unit conversion, but it also improves the accuracy. There is a possibility to handle overflow, which is not easily possible if you do the conversion manually. Therefore, unit conversion using this function is usually the best option in LibreOffice.

If you need help starting LibreOffice coding, make sure that you see this tutorial first:

Getting Started (Video Tutorial)

Splash screen with VCL weld – difficultyInteresting EasyHack

As a LibreOffice user, you have certainly seen the LibreOffice splash screen. It is displayed when you open LibreOffice, it has a progress bar, and when loading the application is finished it goes away. Here we discuss a suggested improvement for this splash screen.

Current Implementation Approach

Currently, the splash screen is implemented by creating a custom widget with a custom painting mechanism that draws the splash image and also the progress bar and moves the progress indicator.

This has some drawbacks:

1. The splash screen does not always scale to the same size as the main LibreOffice Window.

2. The style of the progress bar is somehow different from other UI elements, looks mostly like gen interface.

3. It needs and uses a custom paint code.

4. It does not conform to the dark/light theme.

5. It is not easily localize-able. In fact, the only text is from the displayed image, in English. When you build from sources, the image file is instdir/program/intro.png.

LibreOffice splash screen bitmap
LibreOffice splash screen bitmap

6. It is a separate binary (oosplash). You may run it with:

$ ./instdir/program/oosplash
LibreOffice dev splash screen
LibreOffice dev splash screen

VCL Weld Mechanism

I have previously written about VCL weld mechanism, which is based on creating user interface files (.ui) and loading them inside the application.

The weld mechanism greatly reduces the complexity of creating user interfaces, and also improves other aspects of the user interface, including the consistency.

Code Pointers

Most of the code for the current implementation resides in:
desktop/source/splash/splash.cxx.

The SplashScreenWindow class has an custom paint method, SplashScreenWindow::Paint(), which draws the bitmap, and also the progress. A new UI file is needed for this purpose, which should use GtkProgressBar, which will be considered a weld::ProgressBar. VCL then uses appropriate progress bar widget in different graphical plugins of VCL.

You may look into some dialogs like tip of the day to get some insight:

It would be interesting to avoid a separate binary, but it is fine to keep things as is, and just change to use .ui file.

Final Words

The above issue is tdf#166128. If you would like to work on fixing it, you can just follow the Bugzilla link to see more information.

You may also use ideas from a minimal weld application here:

VCL weld: create LibreOffice GUI from design files