Monday, May 30, 2016

This week-end Lino had a “heart surgery”: I wrote a new module lino.core.widgets with the “general” functionality of what I used to call “user interface elements”.

The widgets branch

(01:00 am) Before explaining what that heart surgery was about, here is what I had to learn once more this morning because I don’t use it often enough: how to create a branch after having done modifications in my working copy which is still on origin/master (but before checkin).

$ git status
On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

        modified:   lino/core/actions.py
        modified:   lino/core/kernel.py
        modified:   lino/core/requests.py
        modified:   lino/core/site.py
        modified:   lino/core/tablerequest.py
        modified:   lino/modlib/extjs/__init__.py
        modified:   lino/modlib/extjs/elems.py
        modified:   lino/modlib/extjs/ext_renderer.py
        modified:   lino/modlib/jinja/__init__.py
        modified:   lino/utils/jsgen.py

Untracked files:
  (use "git add <file>..." to include in what will be committed)

        lino/core/widgets.py

no changes added to commit (use "git add" and/or "git commit -a")

Above changes are almost ready to go into master, but only almost. There seems to be some difference in the generated lino_900_en.js file. So I’d like to compare that file with the same file as is was generated by Lino before my work.

IOW I want to switch back to my last published version for a short time before publishing my work.

I create and select a new local branch “widgets”:

$ git checkout -b widgets

Note: git checkout widgets without -b would have created the branch but not selected it. To make sure that I am now on branch “widgets”, I can run:

$ git status
On branch master
...

I add my modifications and check them in:

$ git add lino/
$ git checkin -m "almost done"

Now I check out master:

$ git checkout master
M   lino/core/actions.py
M   lino/core/kernel.py
M   lino/core/requests.py
M   lino/core/site.py
M   lino/core/tablerequest.py
M   lino/modlib/extjs/__init__.py
M   lino/modlib/extjs/elems.py
M   lino/modlib/extjs/ext_renderer.py
M   lino/modlib/jinja/__init__.py
M   lino/utils/jsgen.py
Switched to branch 'master'
Your branch is up-to-date with 'origin/master'.

So I am now back to the old version. I let it create a lino_900_en.js file, then switch back to widgets before continuing to work on it:

$ git checkout widgets
Switched to branch 'widgets'

After working on it some more time, I can say:

$ git ci -a -m "widgets branch now seems ok (tests are running)"

And before going to sleep I’d actually would like to publish the widgets branch. I tried to publish them as follows:

$ git push
Everything up-to-date

That didn’t work. Because “Your local branches aren’t automatically synchronized to the remotes you write to – you have to explicitly push the branches you want to share.” (as explained here). So I must do:

$ git push origin widgets
Counting objects: 101, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (30/30), done.
Writing objects: 100% (30/30), 12.50 KiB | 0 bytes/s, done.
Total 30 (delta 25), reused 0 (delta 0)
To git@github.com:lsaffre/lino.git
 * [new branch]      widgets -> widgets
$

Voilà. Now I can go to sleep…

A heart surgery for Lino

Until today, lino.modlib.extjs.elems contained two very different things:

  • Lino’s “widgeting system” which is generally valid and applies also for user interfaces that have nothing to do with ExtJS (e.g. lino.modlib.bootstrap3).
  • The “layout elements” are a pythonic representation of the many JavaScript components used by the ExtJS user interface. Their main purpose is to generate the linoweb.js file.

The “widgeting system” is now in lino.core.widgets, and only the ExtJS specific stuff remains in lino.modlib.extjs.elems.

This splitting was a rather difficult and dangerous enterprise. Although the test suites of all projects now pass, I expect more problems to appear in the near future because we are far from having 100% of test coverage.

Why did I do this? It was a design problem which I had been feeling for quite some time: people who want to write a user interface for Lino but who don’t want to know about ExtJS, were discouraged when they saw lots of ExtJS-specific stuff when looking at the lino.modlib.extjs.elems module.

Why just now? It started “by itself”, probably because I had a few problems to get the http://bugs.lino-framework.org/ website back to work after playing around on that server for #920 last Friday, and I decided to repair it by doing it finally “the way it should be done”. I underestimated somewhat how deep this would go.

Application code requires no changes at all, and lino_extjs6 required only a few changes. Especially I removed lino_extjs.extjs6.elems, which had been a copy of lino.modlib.extjs.elems where Hamza (fortunately) did not yet need to change a single line.

About layouts

Once more I try to explain what this “heart surgery” was about and why I wanted to separate lino.core.widgets from lino.modlib.extjs.elems.

Lino’s “layouts” are an abstract pythonic description of the forms and grids of an application. Layouts are an integral part of the application and should not depend on the choice of user interface.

A Layout is the object defined by the application programmer. Lino creates (for every user interface) one LayoutHandle per Layout. A LayoutHandle is a “compiled” version of a Layout: it contains “widgets” which have been created by the WidgetFactory of the plugin which acts as user interface. HtmlRenderer and TextRenderer use the plain widgets, but ExtJS is much more complex and therefore ExtRenderer has its own WidgetFactory which doesn’t creates extended widgets instead of the plain widgets. They are called “layout elements”, but actually they are extended widgets.

About multiple inheritance

One worry is that I have now created a “double class hierarchy”. Here is a simplified inheritance diagram for TextFieldElement:

   (widgets)          (elems)

     Widget --------> LayoutElement
       |                  |
       V                  V
  FieldElement ------> FieldWidget
       |                  |
       V                  V
TextFieldWidget ----> TextFieldElement

This is not just simple diamond inheritance, it is a “carpet of diamonds”. This carpet causes lots of subtle pitfalls due to multiple inheritance.

One possibility to get rid of this might be to use composition instead of inheritance. That is: the elements would not inherit from the widgets, they would just point to “their” widget. But I won’t do that right now, because (a) I am glad that Lino seems to be well after that big operation and (b) I am not yet sure whether we want to get rid of it. Maybe this is a case where MI is needed (“Multiple inheritance is one of those things that is not used often, and can be misused, but is sometimes needed.”, KeithB)