Development/WidgetLayout

From The Document Foundation Wiki
< Development
Revision as of 2012-12-17T09:30:49 by Caolan (Talk | contribs)

Jump to: navigation, search

Widget Layout

Widget layout is now in master. Disregarding any, hopefully minor, bugs it should now be possible to replace existing fixed size dialogs with replacements created with glade.

Rationale

  • Pre-layout dialogs and contained widgets had fixed size. So in general labels, etc. had to be of sufficient fixed width to contain the length of the longest translation resulting in acres of white space in most languages and fundamental inability to resize dialogs without tedious per-dialog explicit handling.
  • Units of measurement in the original .src format are in MAP_APPFONT, an arbitrary measurement of an idealized glyph size defined as 1/8 of a fairly arbitrary 8 character Latin script string which makes no real sense for languages using a non-latin script, or much sense for languages that do use a Latin script
  • There was no visual editor for dialogs. So changing or modifying dialogs required incredibly painful procedure of editing of a textural description and compiling it before results could be seen.
  • No mockups without supporting code possible
  • Hiding a widget required lunatic manual re-positioning in code of remaining widgets by explicitly relocating their positions in code in terms of the dimensions of the hidden widgets.
  • Because it's impossible to know the length of the longest translation before it's translated, every new dialog goes through a arduous cycle of being manually increased in size, and the component widgets manually re-positioned (in arbitrary MAP_APPFONT units which bear only a glancing relationship to the dimensions of the actual text being rendered) as translations come on stream which someone happens to see on screen overflow their allocated area and are clipped to fit or sprawl out of the allocated area to draw all over some other widget.

New Dialog Infrastructure

  • The new dialog infrastructure reuses the gtk buildable xml file format, typically suffixed .ui. The ui editor glade can be used to create and edit dialogs visually to supply a visual editor and allow reusable mockups without supporting code.
  • A standalone ui-previewer utility can render generic .ui files with vcl widgets
  • Dialogs requisition the minimum size their contents require to be shown optimally.
  • Hiding a container automatically hides its containees, and dynamically re-layouts the dialog, as does changing the text of e.g. a label, and similar events.
  • vcl has been extended with a number of new Container widgets which implement layout and a vclbuilder class which can generate a vcl widget tree from a .ui description which can be used instead of the classic .src format.
  • Some bridging utilities (uiex) are in place to handle .ui files in our translation system, these can go if we move to gettext in the future

Screenshots

Before After
Wordcount-before.png Wordcount-after.png
Inserttable-before.png Inserttable-after.png
Linenumbering-before.png Linenumbering-after.png
Outlinenumbering-before.png Outlinenumbering-after.png

Available Container Widgets

GTK3 Equivalent VCL Equivalent
GtkHBox VclHBox
GtkVBox VclVBox
GtkHButtonBox VclHButtonBox
GtkVButtonBox VclVButtonBox
GtkGrid VclGrid
GtkFrame VclFrame
GtkAlignment VclAlignment
GtkExpander VclExpander
GtkNotebook TabControl

Available Basic Widgets

GTK3 Equivalent VCL Equivalent
GtkLabel FixedText
GtkImage FixedImage
GtkSeparator FixedLine
GtkEntry Edit
GtkDrawingArea Window
GtkDialog Dialog
GtkButton PushButton
GtkRadioButton RadioButton
GtkCheckButton CheckBox
GtkSpinButton NumericField
GtkComboBox ListBox
GtkComboTextBox ListBox
GtkTreeView ListBox
GtkComboBoxText ComboBox
GtkScrollbar ScrollBar
GtkTextView VclMultiLineEdit
GtkLinkButton FixedHyperlink
GtkMenu PopupMenu

Custom Widgets

For custom widgets use a custom widget name in glade of Library:Name, the vcl builder will then load the library module and call makeName to create the widget. Implement makeName like so for a custom widget NupOrderWindow which exists in the vcl project that creates a libvcllo.so under Linux and vcllo.dll under Windows

extern "C" SAL_DLLPUBLIC_EXPORT Window* SAL_CALL makeNupOrderWindow(Window *pParent)
{
    return new NupOrderWindow(pParent);
}

Here's the corresponding xml snippet

<child>
 <object class="vcllo:ShowNupOrderWindow" id="orderpreview">
 ...
</child>

Sadly, often these widgets aren't designed to be resized and just take their initial size to set up positions and font sizes. Easiest solution is generally to extract the init code, implement the virtual Resize method and re-call the init there followed by Invalidate to force a re-draw.

The lib:name scheme exists basically to make windows happy. If "lib" doesn't exist the idea is that we lookup "name" in the null/default/global scope anyway which should hopefully mean that the --merged-libs or android/iOS cases, if relevant, still work.

MetricFields and NumericFields

A GtkSpinButton is imported by default as a NumericField. There are occasions when its desirable to import them as MetricFields. MetricFields have units attached to them, e.g. cm/mm inside the spin field area. Rather than have custom widgets here, the current hack is to add the desired MetricField pattern as part of the widget id. So... Standard NumericField

<object class="GtkSpinButton" id="pagemargins">

As a MetricField with a mm unit

<object class="GtkSpinButton" id="pagemargins:0mm">

A related problem is that legacy NumericFields and MetricFields can describe that their spin buttons are omitted, again we overload the id, this tiem with "-nospin" to indicate that. So.... Standard NumericField

<object class="GtkSpinButton" id="pagemargins-nospin">

As a MetricField with a mm unit

<object class="GtkSpinButton" id="pagemargins-nospin:0mm">

RadioButtons

RadioButtons now have a true grouping feature with RadioButton::group so a chain of RadioButtons can form a group. The preexisting (I kid you not) mechanism of a RadioButton forming a group with its neighbour if that happened to be a RadioButton as well without an end-group bit set remains if no group is set so existing unconverted dialog continue to work

ListBoxes and ComboBoxes

GtkComboBox, etc. that have a model associated with them in the .ui file will automatically be filled with the contents of the model at load time. The first column is expected to be gchararray and if there's a second column that's expected to be gint and will be assigned as the associated data for that entry. Only the first column is a translation candidate.

To do this: In glade: combobox->combobox model->new, find the liststore and add a gchararray column, then fill in the entries under "data". You can add a second column of gint for automatically setting matching "userdata" per entry on our listboxes when this is needed

ImageButtons

ImageButton should be converted to PushButton*. In Glade, create a GtkImage and set this as the Image widget under 'Label with optional image'. You can use stock images here, though at the moment only some stock images are supported. See vcl/source/window/builder.cxx for the supported list You can also use arbitrary images from the office icon/graphic set with a file path relative to icon-themes/THEME e.g. res/lock.png

MenuButtons

In Glade, create a GtkButton and create a GtkMenu. Set the name of the GtkButton to foo:BAR where BAR is the name of the GtkMenu and the VclBuilder will stitch these together to create a vcl MenuButton.

Guidelines

  1. Use the stock buttons of gtk-ok, gtk-help, gtk-cancel, etc buttons instead of a generic GtkButton where applicable. That maps them directly to vcl OKButton, HelpButton and CancelButton, which keeps translation size down and allows them to be reordered to the platform-specific ordering and positioning
  2. Use "ok", "help" and "cancel", etc. as the names for the gtk-ok, gtk-help, gtk-cancel, etc stock buttons. This is just for consistency across dialogs.
  3. Generally don't insert Separator lines to emulate frames like the old dialogs do, just use Frames
  4. Don't use the simple html-style tags for bold/italic/underline in labels, use the pango font attributes instead
  5. A lot of our existing dialogs use a vertical button tower. If you want to reproduce that effect tweak a stock dialog's internal vbox to be a hbox and the hbuttonbox to a vbuttonbox, e.g. like this template Media:Vertical-template.ui
  6. See The GNOME HIG for guidelines for spacing and indentation. e.g. multiples of 6pixels
  7. Remove declarations of widgets that only appear in the .hxx and once in the .cxx in order to be initialized, e.g. labels whose contents are not changed by code, or buttons whose default handlers are not overridden. They don't need to be in the code anymore. Just search for the string in the .cxx, and if its only appears in the member init list then it can go.
  8. Otherwise convert Widget m_aWidget to Widget* m_pWidget and search and replace m_aWidget. to m_pWidget->
  9. Then remove the dialog member init entry of e.g. m_aWidget(this, RESID) and in the dialog ctor put get(m_pWidget, "id_string_as_in_the_ui_file"
  10. Remove the FreeResource line now that the dialog is no longer using resources
  11. point the dialog to use the right .ui file, e.g.
 SwSortDlg::SwSortDlg(Window* pParent, SwWrtShell &rShell) :
-    SvxStandardDialog(pParent, SW_RES(DLG_SORTING)),
+    SvxStandardDialog(pParent, "SortDialog", "modules/swriter/ui/sortdialog.ui"),

Examples

Key classes

  • VclBuilder can load the .ui format and map the widgets to vcl controls. The ui-previewer is the simplest example of direct use.
VclBuilder aBuilder(pDialog, rtl::OUString(), "path/to/some.ui");
Dialog *pTopDialog = dynamic_cast<Dialog*>(aBuilder.get_widget_root());
pTopDialog->Show()
  • VclBuilderContainer. This provides access to a VclBuilder and is a base-class for Dialogs and TabPages.
  • Dialogs, TabPage. These now have constructors which take the path to the ui file and the id of the dialog/tabpage-contents in the ui file that refers to that Dialog/TabPage contents.
Dialog( Window* pParent, const OString& rID, const OUString& rUIXMLDescription );
  • VclContainer, the base class for all the containers. It asks widgets' size requisitions through Window::get_preferred_size and doles out the resulting allocations and positions to its children.
  • Window, the base class for all the widgets/Controls. The new get_preferred_size backs onto the generally pre-existing GetOptimalSize for the usual case where the widget hasn't got an explicit size set in the .ui. Retro-fitted with various new halign, valign, grid attach positions members.
  • Dialog control methods in vcl/source/window/dlgctrl.cxx, pre-existing spaghetti to traverse through a flat dialog widget hierarchy retro-fitted to walk transparently through containers

Testing a Conversion

  1. Ensure a default button is highlighted when the dialog opens
  2. Ensure that some widget has focus when the dialog opens (typically the first widget)
  3. Ensure that pressing tab traverses in a sane order all the visible sensitive widgets
  4. Ensure the widget-context-sensitive help (F1) takes you to the same help page as pre-change

To Do Post-Integration

  1. unit tests for grid and fix unhandled edge-cases
  2. width-for-height and height-for-width support
  3. utility to convert .src files to GtkGrid-using templates for bootstrapping dialog conversion

HOWTO: Step by step dialog conversion

So far there is no tool convert .src/.hrc files pair to .ui, it is still manual work to some extent. To be able to do that, you need to follow the following steps:

  1. Find a dialog that you want to convert. To find the corresponding .hrc/.src, take a piece of text from the dialog (anything that is long enough, like "Width and spacing"), and put it to "Full Search" in http://opengrok.libreoffice.org/ including the quotes (so that it does not find thousands of results). If you are lucky, you see 1 hit in .src file (in the case of the "Width and spacing" example it is "/core/sw/source/ui/frmdlg/column.src", unless it has been already converted to .ui :-)
  2. Start 'glade' and draw something similar to the existing dialog and cut and paste the strings from the original .src into it.
  3. Follow the basic guidelines from above, e.g. OK buttons are called "ok", cancel are called "cancel" and so on.
  4. Remove from the code any declarations of widgets which only appear in the .hxx and once in the .cxx in the member initialization list (because we don't need to explicitly declare them anymore if that's all they were there for; this is the case eg. for horizontal lines)
  5. In .hxx, search and replace widgets called like m_aWidget to *m_pWidget (to pointers, like from PushButton m_aButton; to PushButton* m_pButton;
  6. In .cxx, search and replace their use from m_aWidget to m_pWidget->
  7. Convert aWidget(this, SOMERESID) in the constructor member initialization list to get(m_pWidget, SOMEUIID) in the constructor where SOMEUIID is a human-helpful name which you fill into the .ui
  8. Remove the FreeResource(); from the dialog constructor.

So when you have code like

// in .hxx
    PushButton m_aFOO;
 
// in .cxx
SomeDialog::SomeDialog() :
...
    m_aFOO(this, SwRes(DIALOG_OK_BTN),
...
{
    FreeResource();
    m_aFOO.SetClickFoo(something);
}

you need to convert it to code that looks like:

// in .hxx
    PushButton* m_pFOO;
 
// in .cxx
SomeDialog::SomeDialog() :
{
    get(m_pFOO, "foo");
    m_pFOO->SetClickFoo(something);
}

If you would like to try to convert a dialog, and are not 100% sure yet how to do the code changes, then you could just start with demoing up converting a dialog in glade first and not worry about the code yet and Caolán can convert the code over to use the new .ui. The next time, you will be able to do even the code changes yourself :-)