Skip to content

Monday, 15 May 2023

As you probably have seen from other people’s blog posts there was the 2023 Plasma Sprint last week. It was generously hosted by TUXEDO Computers in their offices in Augsburg, Germany. Many thanks to TUXEDO for that!

Other people have already well summarized what happend there, so let’s have a look at what I have been doing:

Together with Kai Uwe, Volker, and Ismael I looked at notifications. This includes internal simplifications in KNotifications, API design questions, a proposed V2 for the notification portal API, and a new UI for per-event configuration in the notification settings module.

Together with Marco I looked into some API design topics around the Applet class in plasma-framework, and worked on a proposed new API for applet actions.

Given that Wayland was a huge topic at the sprint I continued the work of mine on enabling some accessibility options on Wayland. Sticky keys is not only what happens when you spill Spezi over your laptop, it is also an accessibility feature that allows people that cannot press multiple keys simultaneously to type key combinations like Control+C/Control+V. Basic support for this on Wayland is coming with Plasma 6. Not all of the options that are present on X11 work yet though. If you are a user of sticky keys please leave your feedback on https://bugs.kde.org/show_bug.cgi?id=444335 for which of these options should be prioritized.

Together with Natalie, KDE e.V.’s Hardware Integration Engineer, I discussed our touchpad configuration module and how to make it more robust and maintainable. We also discussed various topics around debugging and profiling KDE software.

Together with Xaver I looked into how Dolphin/KIO handles multi-GPU setups (so that apps preferring discrete GPUs will use them when launched via KIO). As it turns out there’s a few things to be improved there so we discussed approaches to do that.

With David I discussed some of my open changes to our global shortcuts infrastructure and continued working on improving that.

We also got a visit from two people from the city of Treuchtlingen, Germany. They have been using KDE software in their communal IT for over 20 years. We discussed their vision of using open source and open standards in government IT and some of their pain points with using KDE software for that.

Overall it was a very nice and productive week and it was great to meet so many fellow KDE hackers, some of them for the first time! Gatherings like this are only possible because of generous donations to KDE e.V. Please consider donating to make more such sprints possible.

Sunday, 14 May 2023

My Painting

Text

About Me - I am Groot.

I’m Srirupa Datta, about to finish my undergraduate Electrical Engineering degree at Jadavpur University, India, in June. This year, I got selected for Google Summer of Code and will be working on improving the Bundle Creator in Krita.

My Introduction to Krita…

It’s been more than a year since my last blogpost where I posted monthly updates on my progress on adding the Perspective Ellipse assistant tool in Krita during SoK’22. Being a painter who’s interested in software development, I’ve been interested in Krita ever since I started using it.

What it’s all about

The primary format to share resources in Krita is a Resource Bundle, which is a compressed file containing all the resources together. It also contains some other information like metadata and a manifest so Krita can check there’s no errors in the file.

Krita’s Bundle Creator allows one to create their own bundle from the resources of their choice. The project that I would be working on, aims to improve the user interface of the current Bundle Creator, and allow the ability to edit bundles (which is currently not supported in Krita).

The new Bundle Creator

The new Bundle Creator would look like an installation wizard with four pages which can be navigated using the Next and Back buttons, as well as buttons on the left side panel.

I think the primary objective behind designing the new Bundle Creator was to organize its workflow, that is, segregate sections devoted to a particular function or job. This is what led to the idea of using a wizard, instead of simple dialogs. Hence it would have four wizard pages:

  • Choose Resources
  • Choose Tags
  • Enter Bundle Details
  • Choose Save Location

Some of the cool features you can expect in the new Bundle Creator are a gridview like that of Resource Manager’s to view all the resources, filter resources by name or tag before selecting, and an option to change back to the default listview from gridview if one wishes to stick to the previous layout.

W1

Adding custom tags to selected resources is a feature that we wish to integrate, but it would require a redesign of the Choose Tags wizard page that has been shown below. Just to clarify, these are all mockups!

W2

Yet another important feature would be reloading last bundle data when opened/on startup - this is particularly useful when making a bundle for other people.

Apart from these, the new Bundle Creator would be resizable(Yaay!), and a separate Menu entry called Bundle Creator would be created. We plan to move Manage Resource Libraries , Manage Resources and Bundle Creator from Menu > Settings to Menu > Resources.

And lastly, I would be working on adding the feature of editing bundles - this however needs to be discussed more and would be dealt with post my mid term evaluations.

And of course, if you want to suggest some ideas or improvements, feel free to drop a comment on this post I created on Krita Artists Forum!

Saturday, 13 May 2023

I’ve been happily using Woodpecker CI to get CI for my repositories on Codeberg. Codeberg is a non-profit community-driven git repository hosting platform, so they can’t provide free CI to everyone.

Since I run lots of stuff on small arm boards (for example this website), I need my CI jobs to create arm executables. The easiest way to get that done is to just compile on arm devices, so I was happy to see that Hetzner is now offering arm nodes in their cloud offering.

To make that as cheap as possible, the CI should ideally create a VM before running its job, and remove it again afterwards. Unfortunately Woodpecker does not seem to support that out of the box at this point.

My solution to that was to build a docker proxy, that creates VMs using docker-machine, and then proxies the incoming requests to the remote VM. That works really well now, so maybe you will find it useful.

Setting that up is reasonably simple:

  • Install docker-machine. I recommend using the fork by GitLab
  • Install the backend for your cloud provider. For Hetzner I use this one
  • Grab a binary release of docker-proxy (if you need arm executables), or compile it yourself.
  • Create a systemd unit to start the service on boot in /etc/systemd/system/docker-proxy.service. This particular one just runs it on the woodpecker-agent user that you may already have if you use Woodpecker CI.
[Unit]
Description=Docker CI proxy
After=network.target

[Service]
User=woodpecker-agent
Group=nogroup
Restart=always
ExecStart=/usr/local/bin/docker-proxy

[Install]
WantedBy=multi-user.target
  • Fill in /etc/docker-proxy/config.toml This example works for Hetzner, but everything that has a docker-machine provider should work. You just need to supply the arguments for the correct backend.
[docker_machine]
driver="hetzner"
args=[
    "--hetzner-api-token=<token>",
    "--hetzner-server-type=cax11",
    "--hetzner-image-id=103907373",
]

[general]
timeout=300
port=8000
  • Finally, make woodpecker-agent use the new docker proxy, by setting DOCKER_HOST=http://localhost:8000 in its environment.

I hope this may be useful for you as well :)

Friday, 12 May 2023

IT feels like a billion years before the last plasma sprint, which was in 2019 in Valencia, before the pandemic, but finally this year we are back on track, and was great to see again many old friends as well as seeing many new faces for which it was the first sprint.

We were gracefully hosted by Tuxedo Computers in Augsburgh, makers of very nice laptops that come with Linux and KDE Plasma, as well as being KDE patrons.

First of all, everybody got up to speed with a full git build of a Plasma 6 session, so that everybody could participate in development and discussions from the same level.

There were many discussions about Plasma 6, about what we want to do in Plasma and in Kirigami, how we want to change the look and defaults for the new major release. Most of the user-facing changes have been wonderfully described by Nate.

On my part, I worked on mainly 2 things, that were fairly not “glamorous” but quite important never the less (and mildly painful to do) : a refactor of the plasmoid loading code and splitting all the Svg themes code to a new framework with far less dependencies, ideally usable by any application.

Plasma API

I spent most of my hacking time at the sprint on a refactor of the plasmoid loading code, which won’t be really “seen” by the user, but will make the infrastructure much more robust and the API cleaner.

The person which must pay attention to it is the plasmoid author, which will need to adapt the plasmoid code in a few places.

Most notable is that, just like when you are writing a QML application you have to use the ApplicationWindow root QML Item, for a plasmoid you now have to use a PlasmoidItem root object, so something like

Item {
    Plasmoid.compactRepresentation: Label {text: Plasmoid.title}
    Plasmoid.fullRepresentation: Item {...}
}

becomes:

PlasmoidItem {
    compactRepresentation: Label {text: Plasmoid.title}
    fullRepresentation: Item {...}
}

A full porting guide is in progress.

KSvg

Using the Plasma Svg code to support stylesheet recoloring, on disk image cache to speed up loading, and the 9-patches FrameSvg is something the several applications would be interested to, and some actually are already doing, but since plasma-framework has a lot of dependencies, for some applications that is a blocker. All the svg code has now been broken out into a new framework called KSvg, which is still work in progress, but in the end will support all existing plasma themes with no change, and if an application wishes to use it, the svg sets will be loaded from the app own data folder (or anywhere else the application configures it to) instead of the share/plasma/desktoptheme folder, where plasmashell looks for them (so they can also use a complete different theme structure and don’t have to provide the same elements)

Tuesday, 9 May 2023

While at the 2023 Plasma Sprint at the Tuxedo Computers office in Augsburg, Germany, I created a patch to add ButtonSegment, SegmentedControl and SegmentSeparator to Qt Quick Controls 6.6: https://codereview.qt-project.org/c/qt/qtdeclarative/+/476564

The patch has not been merged yet and may change significantly.

ButtonSegment is a Button subclass with an edges property that can be used to specify which edges are exposed to the outside of the group. The base flags available are NoEdges (0), TopEdge (Qt::TopEdge), LeftEdge (Qt::LeftEdge), RightEdge (Qt::RightEdge) and BottomEdge (Qt::BottomEdge). Why the outside and not the inside? It doesn’t matter a whole lot, it’s just what I decided to do. My thoughts were "These are the edges where extra visuals will be." There are also flag combination values like AllEdges, TopLeftEdges, TopRightEdges, BottomLeftEdges and BottomRightEdges to make your lines of code shorter.

SegmentedControl is a Control subclass with flat and down properties. This control doesn’t actually do much. Its main purpose is for providing a way to define unified graphics for a segmented button group (e.g., macOS Big Sur has a background underneath all of the button segments). flat can be used to define flat and raised appearances. down can be used to define a unified pressed/down appearance (e.g., a shadow used by a unified raised background could disappear when any segment is pressed). down does not change in response to ButtonSegment::down changes by default. You must set that up yourself. There are ways to make this behavior more automatic, but I have not used them yet. I might make the behavior more automatic because otherwise it will be difficult to support down when button segments are created from delegate components.

SegmentSeparator is a Control subclass that has orientation, vertical and horizontal properties. It has the same API as ToolSeparator. Its purpose is to provide styling for separators between button segments.

Automatic Behavior VS Boilerplate Code

Unlike similar controls provided by Flutter (SegmentedButton) and AppKit (NSSegmentedControl), there is no built-in API for getting/setting the current selected button(s), index(es) or other value(s) from segments. This isn’t a big problem in QML because it is easy to add the additional behaviors or properties you need. You can even choose the ones that suit your use cases the best. Don’t forget that you have ButtonGroup, ActionGroup, Repeater, ListView, GridView and various ways to define models.

Some, maybe even many of you may have felt a bit disappointed after reading the paragraph above. You may have been thinking something along the lines of "Why doesn’t it just do what I want without having to write extra code?" It’s a good question. When people (including myself) try to reduce the amount of code they need to write by making controls do things automatically, they often cause more harm than good. If the problems were obvious and the temptation wasn’t so strong, then people (including myself) wouldn’t make those kinds of mistakes over and over. What often happens is that controls that were intended to be generally useful become more specialized for certain use cases and the people implementing the automatic behavior don’t realize it until they discover that a valid use case they weren’t thinking about has become more difficult or even impossible to support. There are examples of great automatic behavior out there and specialized components where a lack of flexibility is completely acceptable. My point is that people should be careful when implementing automatic behavior. I chose greater flexibility at the cost of requiring users to write more code.

Example Code

This creates three independently checkable segments in a row.

SegmentedControl {
    id: control

    ButtonGroup {
        buttons: control.contentItem.children
        exclusive: false
    }

    contentItem: Row {
        spacing: control.spacing

        ButtonSegment {
            edges: ButtonSegment.TopLeftEdges | ButtonSegment.BottomLeftEdges
            checkable: true
            text: qsTr("Segment 1")
        }

        SegmentSeparator {}

        ButtonSegment {
            edges: ButtonSegment.TopEdge | ButtonSegment.BottomEdge
            checkable: true
            text: qsTr("Segment 2")
        }

        SegmentSeparator {}

        ButtonSegment {
            edges: ButtonSegment.TopRightEdges | ButtonSegment.BottomRightEdges
            checkable: true
            text: qsTr("Segment 3")
        }
    }
}

Planned Supported Styles

Tuesday, 11 October 2022

This year, I had the amazing opportunity to attend KDE Akademy in person for the first time! The host city was Barcelona. It is my second time visiting the city but it was my first time to attend KDE Akademy. Actually it was my first KDE event.

For KDE friends who don't know me, I mainly contribute to openSUSE, GNOME, Nextcloud, ownCloud and GNU Health. I have fewer contributions to Fedora, Ubuntu and ONLYOFFICE and a few here and there to FOSS projects.

Question. Why did you attend KDE Akademy? Two were the reasons. The first and main reason was to see the organization of the conference from the inside, since my University will host the next KDE Akademy. The second reason was to "introduce" myself to the KDE community, since I contribute to other projects. Actually, I know a person from the KDE board but community is not only one person.

The only familiar person I could meet was openSUSE's community manager. Unfortunately he couldn't attend, so he asked me to represent openSUSE. The duties were to have a booth and present something openSUSE related for 3 minutes. I had an idea to propose my friend George to do his first presentation to an open source conference and start his open source journey. He was very excited and he did it.

Day 0

There was a welcome event on Friday for us, where attendees got to know each other. Unfortunately, my flight was delayed and I arrived too late to attend the event. So I stayed at the hotel and tried to rest for my first Akademy day. I felt like going to school.

Day 1

The first thing we had to do was set up our booth. Well, the only promo material we had was stickers. I think all geeks like stickers so it was the best gift for everyone. I love stickers, not only from openSUSE but from other projects as well.
Stathis at openSUSE booth
During setting up the booth, I met the rest of the guys from the sponsors like Ubuntu, Fedora, Qt and Slim Book.

I attended quite a few interesting talks:Food at the coference wasn't the best for my taste. Maybe it's me. But the most interesting part of the conference was the fact that I had the chance to meet realy important people, developers that changed my point of view on softare developement.

You can see the first day, Room 1 here:

Day 2

After having fun the first day, I was excited for the second day. The first reason was that George and I (actually only George) will have the sponsor talk and the second reason was that the fact that the organizers would announce the place of next year's Akademy. Of cource that place is Thessaloniki and my University.

I attended quite a few interesting talks:You can see the second day, Room 1 here:
Unfortunately I didn't have any team to join the next BoFs days. I had a small hope that we could setup the working environment for the next Akademy but that didn't happen.

We didn't join the trip to the mountain. We went to see the city. It was my second time and I skipped some sites.

I really loved my first KDE Akademy. I would like to thank KDE ev that sponsored my trip to attend the Akademy.

I have a lot of stuff to work here with the organizing committee.
We are working to host you all next year.

Wednesday, 17 August 2022

KDE Akademy 2022
Happy traveller is back. Happy open source conference guy is ready for another trip. This time my destination is KDE Akademy and Barcelona. It's my first time attending to Akademy and I am soooooo excited. It's also my second time in Barcelona. Thanks to my highschool, I have been to Barcelona participating in the Erasmus + mobility program (article in Greek). According to the legent, maybe me kissing weird things in Girona worked just fine (click to see the picture).

You don't need to be a "KDE expert" to join, I know I am not. If you're interested in KDE you should really attend if you can (in person if possible), and not only the weekend of talks, but the whole week! And you should register today!

For those of you who know me, I used to attend conferences alone. This time we are 3 people from my university, the University of Macedonia. We have a newly formed Open Source Team and I would like to bring more people with me, to join global communities.

I will keep this short. More to come soon.

I would like to thank KDE and the community for the opportunity to join such a big conference. I am so happy that I will meet you in person after those 2 years of COVID-19 era.

Thursday, 25 August 2016

The function evolution…

I would like to show the evolution of one function in rolisteam code.
This function is called when the user (Game Master) closes a map or image.

The function must close the map or image and send a network order to each player to close the image or plan.

At the beginning, the function looked like that.
Some code line and comments were written in French. No coding rules were respected.
Low-level code for networking. The function was very long.

[pastacode lang=”cpp” message=”” highlight=”” provider=”manual” manual=”%20%2F%2F%20On%20recupere%20la%20fenetre%20active%20(qui%20est%20forcement%20de%20type%20CarteFenetre%20ou%20Image%2C%20sans%20quoi%20l’action%0A%20%20%20%20%20%20%20%20%2F%2F%20ne%20serait%20pas%20dispo%20dans%20le%20menu%20Fichier)%0A%20%20%20%20%20%20%20%20QWidget%20*active%20%3D%20workspace-%3EactiveWindow()%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Ne%20devrait%20jamais%20arriver%0A%20%20%20%20%20%20%20%20if%20(!active)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20qWarning(%22Close%20map%20action%20called%20when%20no%20widget%20is%20active%20in%20the%20workspace%20(fermerPlanOuImage%20-%20MainWindow.h)%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20On%20verifie%20pour%20le%20principe%20qu’il%20s’agit%20bien%20d’une%20CarteFenetre%20ou%20d’une%20Image%0A%20%20%20%20%20%20%20%20if%20(active-%3EobjectName()%20!%3D%20%22CarteFenetre%22%20%26%26%20active-%3EobjectName()%20!%3D%20%22Image%22)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20qWarning(%22not%20expected%20type%20of%20windows%20(fermerPlanOuImage%20-%20MainWindow.h)%22)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Creation%20de%20la%20boite%20d’alerte%0A%20%20%20%20%20%20%20%20QMessageBox%20msgBox(this)%3B%0A%20%20%20%20%20%20%20%20msgBox.addButton(QMessageBox%3A%3AYes)%3B%0A%20%20%20%20%20%20%20%20msgBox.addButton(QMessageBox%3A%3ACancel)%3B%0A%20%20%20%20%20%20%20%20msgBox.setIcon(QMessageBox%3A%3AInformation)%3B%0A%20%20%20%20%20%20%20%20msgBox.move(QPoint(width()%2F2%2C%20height()%2F2)%20%2B%20QPoint(-100%2C%20-50))%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20On%20supprime%20l’icone%20de%20la%20barre%20de%20titre%0A%20%20%20%20%20%20%20%20Qt%3A%3AWindowFlags%20flags%20%3D%20msgBox.windowFlags()%3B%0A%20%20%20%20%20%20%20%20msgBox.setWindowFlags(flags%20%5E%20Qt%3A%3AWindowSystemMenuHint)%3B%0A%20%20%20%20%20%20%20%20%2F%2F%20M.a.j%20du%20titre%20et%20du%20message%0A%20%20%20%20%20%20%20%20if%20(active-%3EobjectName()%20%3D%3D%20%22CarteFenetre%22)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20msgBox.setWindowTitle(tr(%22Close%20Map%22))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20msgBox.setText(tr(%22Do%20you%20want%20to%20close%20this%20map%3F%5CnIt%20will%20be%20closed%20for%20everybody%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20msgBox.setWindowTitle(tr(%22Close%20Picture%22))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20msgBox.setText(tr(%22Do%20you%20want%20to%20close%20this%20picture%3F%5CnIt%20will%20be%20closed%20for%20everybody%22))%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20msgBox.exec()%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Si%20l’utilisateur%20n’a%20pas%20clique%20sur%20%22Fermer%22%2C%20on%20quitte%0A%20%20%20%20%20%20%20%20if%20(msgBox.result()%20!%3D%20QMessageBox%3A%3AYesRole)%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Emission%20de%20la%20demande%20de%20fermeture%20de%20la%20carte%0A%20%20%20%20%20%20%20%20if%20(active-%3EobjectName()%20%3D%3D%20%22CarteFenetre%22)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Recuperation%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20QString%20idCarte%20%3D%20((CarteFenetre%20*)active)-%3Ecarte()-%3EidentifiantCarte()%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Taille%20des%20donnees%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quint32%20tailleCorps%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Taille%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sizeof(quint8)%20%2B%20idCarte.size()*sizeof(QChar)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Buffer%20d’emission%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20char%20*donnees%20%3D%20new%20char%5BtailleCorps%20%2B%20sizeof(enteteMessage)%5D%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Creation%20de%20l’entete%20du%20message%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20enteteMessage%20*uneEntete%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete%20%3D%20(enteteMessage%20*)%20donnees%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3Ecategorie%20%3D%20plan%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3Eaction%20%3D%20fermerPlan%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3EtailleDonnees%20%3D%20tailleCorps%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Creation%20du%20corps%20du%20message%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20int%20p%20%3D%20sizeof(enteteMessage)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Ajout%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quint8%20tailleIdCarte%20%3D%20idCarte.size()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20memcpy(%26(donnees%5Bp%5D)%2C%20%26tailleIdCarte%2C%20sizeof(quint8))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%2B%3Dsizeof(quint8)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20memcpy(%26(donnees%5Bp%5D)%2C%20idCarte.data()%2C%20tailleIdCarte*sizeof(QChar))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%2B%3DtailleIdCarte*sizeof(QChar)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Emission%20de%20la%20demande%20de%20fermeture%20de%20la%20carte%20au%20serveur%20ou%20a%20l’ensemble%20des%20clients%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20emettre(donnees%2C%20tailleCorps%20%2B%20sizeof(enteteMessage))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Liberation%20du%20buffer%20d’emission%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20delete%5B%5D%20donnees%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Suppression%20de%20la%20CarteFenetre%20et%20de%20l’action%20associee%20sur%20l’ordinateur%20local%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20((CarteFenetre%20*)active)-%3E~CarteFenetre()%3B%0A%20%20%20%20%20%20%20%20%7D%0A%0A%20%20%20%20%20%20%20%20%2F%2F%20Emission%20de%20la%20demande%20de%20fermeture%20de%20l’image%0A%20%20%20%20%20%20%20%20else%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Recuperation%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20QString%20idImage%20%3D%20((Image%20*)active)-%3EidentifiantImage()%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Taille%20des%20donnees%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quint32%20tailleCorps%20%3D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Taille%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20sizeof(quint8)%20%2B%20idImage.size()*sizeof(QChar)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Buffer%20d’emission%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20char%20*donnees%20%3D%20new%20char%5BtailleCorps%20%2B%20sizeof(enteteMessage)%5D%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Creation%20de%20l’entete%20du%20message%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20enteteMessage%20*uneEntete%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete%20%3D%20(enteteMessage%20*)%20donnees%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3Ecategorie%20%3D%20image%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3Eaction%20%3D%20fermerImage%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20uneEntete-%3EtailleDonnees%20%3D%20tailleCorps%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Creation%20du%20corps%20du%20message%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20int%20p%20%3D%20sizeof(enteteMessage)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Ajout%20de%20l’identifiant%20de%20la%20carte%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20quint8%20tailleIdImage%20%3D%20idImage.size()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20memcpy(%26(donnees%5Bp%5D)%2C%20%26tailleIdImage%2C%20sizeof(quint8))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%2B%3Dsizeof(quint8)%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20memcpy(%26(donnees%5Bp%5D)%2C%20idImage.data()%2C%20tailleIdImage*sizeof(QChar))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20p%2B%3DtailleIdImage*sizeof(QChar)%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Emission%20de%20la%20demande%20de%20fermeture%20de%20l’image%20au%20serveur%20ou%20a%20l’ensemble%20des%20clients%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20emettre(donnees%2C%20tailleCorps%20%2B%20sizeof(enteteMessage))%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Liberation%20du%20buffer%20d’emission%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20delete%5B%5D%20donnees%3B%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20Suppression%20de%20l’Image%20et%20de%20l’action%20associee%20sur%20l’ordinateur%20local%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20((Image%20*)active)-%3E~Image()%3B%0A%20%20%20%20%20%20%20%20%7D”/]

A big step forward, the networking code has been reworked.
So the readability is improved. It is also possible to see the beginning of polymorphism. MediaContener is used but not everywhere.

[pastacode lang=”cpp” message=”Version Intermédiaire” highlight=”” provider=”manual” manual=”QMdiSubWindow*%20subactive%20%3D%20m_mdiArea-%3EcurrentSubWindow()%3B%0AQWidget*%20active%20%3D%20subactive%3B%0AMapFrame*%20bipMapWindow%20%3D%20NULL%3B%0A%0Aif%20(NULL!%3Dactive)%0A%7B%0A%0A%20%20%20%20QAction*%20action%3DNULL%3B%0A%0A%20%20%20%20Image*%20%20imageFenetre%20%3D%20dynamic_cast(active)%3B%0A%0A%20%20%20%20QString%20mapImageId%3B%0A%20%20%20%20QString%20mapImageTitle%3B%0A%20%20%20%20mapImageTitle%20%3D%20active-%3EwindowTitle()%3B%0A%20%20%20%20bool%20image%3Dfalse%3B%0A%20%20%20%20%2F%2Fit%20is%20image%0A%20%20%20%20if(NULL!%3DimageFenetre)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20m_pictureList.removeOne(imageFenetre)%3B%0A%0A%20%20%20%20%20%20%20%20mapImageId%20%3D%20imageFenetre-%3EgetMediaId()%3B%0A%20%20%20%20%20%20%20%20image%20%3D%20true%3B%0A%20%20%20%20%20%20%20%20action%20%3D%20imageFenetre-%3EgetAction()%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%2F%2Fit%20is%20a%20map%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20bipMapWindow%3D%20dynamic_cast(active)%3B%0A%20%20%20%20%20%20%20%20if(NULL!%3DbipMapWindow)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20mapImageId%20%3D%20bipMapWindow-%3EgetMediaId()%3B%0A%20%20%20%20%20%20%20%20%20%20%20%20action%20%3D%20bipMapWindow-%3EgetAction()%3B%0A%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20else%2F%2F%20it%20is%20undefined%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20return%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20QMessageBox%20msgBox(this)%3B%0A%20%20%20%20msgBox.setStandardButtons(QMessageBox%3A%3AYes%20%7C%20QMessageBox%3A%3ACancel%20)%3B%0A%20%20%20%20msgBox.setDefaultButton(QMessageBox%3A%3ACancel)%3B%0A%20%20%20%20msgBox.setIcon(QMessageBox%3A%3AInformation)%3B%0A%20%20%20%20msgBox.move(QPoint(width()%2F2%2C%20height()%2F2)%20%2B%20QPoint(-100%2C%20-50))%3B%0A%20%20%20%20Qt%3A%3AWindowFlags%20flags%20%3D%20msgBox.windowFlags()%3B%0A%20%20%20%20msgBox.setWindowFlags(flags%20%5E%20Qt%3A%3AWindowSystemMenuHint)%3B%0A%0A%20%20%20%20if%20(!image)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20msgBox.setWindowTitle(tr(%22Close%20Map%22))%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20msgBox.setWindowTitle(tr(%22Close%20Picture%22))%3B%0A%20%20%20%20%7D%0A%20%20%20%20msgBox.setText(tr(%22Do%20you%20want%20to%20close%20%251%20%252%3F%5CnIt%20will%20be%20closed%20for%20everybody%22).arg(mapImageTitle).arg(image%3Ftr(%22%22)%3Atr(%22(Map)%22)))%3B%0A%0A%20%20%20%20msgBox.exec()%3B%0A%20%20%20%20if%20(msgBox.result()%20!%3D%20QMessageBox%3A%3AYes)%0A%20%20%20%20%20%20%20%20return%3B%0A%0A%20%20%20%20if%20(!image)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20NetworkMessageWriter%20msg(NetMsg%3A%3AMapCategory%2CNetMsg%3A%3ACloseMap)%3B%0A%20%20%20%20%20%20%20%20msg.string8(mapImageId)%3B%0A%20%20%20%20%20%20%20%20msg.sendAll()%3B%0A%0A%20%20%20%20%20%20%20%20m_mapWindowMap.remove(mapImageId)%3B%0A%20%20%20%20%20%20%20%20m_playersListWidget-%3Emodel()-%3EchangeMap(NULL)%3B%0A%20%20%20%20%20%20%20%20m_toolBar-%3EchangeMap(NULL)%3B%0A%20%20%20%20%7D%0A%20%20%20%20else%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20NetworkMessageWriter%20msg(NetMsg%3A%3APictureCategory%2CNetMsg%3A%3ADelPictureAction)%3B%0A%20%20%20%20%20%20%20%20msg.string8(mapImageId)%3B%0A%20%20%20%20%20%20%20%20msg.sendAll()%3B%0A%20%20%20%20%7D%0A%0A%20%20%20%20MediaContainer*%20%20mediaContener%20%3D%20dynamic_cast(subactive)%3B%0A%20%20%20%20if(NULL!%3DmediaContener)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20CleverURI*%20cluri%20%3D%20mediaContener-%3EgetCleverUri()%3B%0A%20%20%20%20%20%20%20%20cluri-%3EsetDisplayed(false)%3B%0A%20%20%20%20%20%20%20%20if(NULL!%3Dm_sessionManager)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20m_sessionManager-%3EupdateCleverUri(cluri)%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A%0A%20%20%20%20delete%20action%3B%0A%20%20%20%20delete%20subactive%3B%0A%7D%0A”/]

Then, we implements a solution to describe map, vmap (v1.8) or image as mediacontener.
They share a large part of their code, so it becomes really easy to do it.

[pastacode lang=”cpp” message=”Actuel” highlight=”” provider=”manual” manual=”QMdiSubWindow*%20subactive%20%3D%20m_mdiArea-%3EcurrentSubWindow()%3B%0A%20%20%20%20MediaContainer*%20container%20%3D%20dynamic_cast(subactive)%3B%0A%20%20%20%20if(NULL%20!%3D%20container)%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20CleverURI%3A%3AContentType%20type%20%3D%20container-%3EgetContentType()%3B%0A%20%20%20%20%20%20%20%20if(CleverURI%3A%3AVMAP%20%3D%3D%20type)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20removeVMapFromId(container-%3EgetMediaId())%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20else%20if(CleverURI%3A%3AMAP%20%3D%3D%20type)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20removeMapFromId(container-%3EgetMediaId())%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20else%20if(CleverURI%3A%3APICTURE%20%3D%3D%20type%20)%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20removePictureFromId(container-%3EgetMediaId())%3B%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D%0A”/]

To prepare my conference at Pas Sage En Seine [FR], a French hacking festival, I chose to write my slide presentation in QML.
It allows me to have better control and be free to do whatever I want (such as a timeline or any kind of animation).
Of course, That comes with a price. It is longer to do it that way but now I find some solutions. So, next time will be faster.

File hierarchy:

I preferred use QML through C++ Application. It provides more helpful feature, such as: the ability to make screenshots of your presentation at any time (useful as backup plan).
At the top level, you will found all C++ classes, project files and the main qml. Then, you will have a directory with all your pages and if it is required a directory with all your images.

├── cpphighlighter.cpp
├── cpphighlighter.h
├── deployment.pri
├── LICENSE
├── main.cpp
├── main.qml
├── pages
│   ├── 01_intro.qml
│   ├── 02_presentation.qml
│   ├── 03_jdr_et_rolisteam.qml
│   ├── 043_Exemple_code_1.qml
│   ├── 04_jdr_avantages_pb.qml
│   ├── 05_avantage_jdr_virtuel.qml
│   ├── 06_fonctionnalites_rolisteam.qml
│   ├── 07_rolisteam_debut.qml
│   ├── 08_Rolistik_a_Rolisteam.qml
│   ├── 10_frise_chronologique.qml
│   ├── 11_son_usage.qml
│   ├── 12_son_fonctionnement.qml
│   ├── 13_dice_parser.qml
│   ├── 14_themes_audio_3_pistes.qml
│   ├── 15_nouveaute_1_8.qml
│   ├── 16_projet_avenir.qml
│   ├── 17_reussites.qml
│   ├── 18_les_lecons.qml
│   ├── 19_objectif_rolisteam_libre.qml
│   ├── 20_FAQ.qml
├── pasSageEnSeine.pro
├── pasSageEnSeine.pro.user
├── qmlcontroler.cpp
├── qmlcontroler.h
├── qmlcontroler.ui
├── qml.qrc
├── README.md
├── rsrc
│   ├── all.png
│   ├── cc.png
│   └── chat.png

The C++ application

The main

It can be useful to see the state of the presentation and to read some extra notes about the current slide. To manage that, I wrote a small C++ application.
The first goal is to show the QML view, then add some features and communication between the QML view and the C++ window.

[pastacode lang=”cpp” manual=”%23include%20%3CQApplication%3E%0A%23include%20%3CQQmlApplicationEngine%3E%0A%23include%20%22qmlcontroler.h%22%0A%23include%20%3CQQmlContext%3E%0A%23include%20%3CQQuickTextDocument%3E%0A%0A%23include%20%22cpphighlighter.h%22%0A%0Aint%20main(int%20argc%2C%20char%20*argv%5B%5D)%0A%7B%0A%20%20%20%20QApplication%20app(argc%2C%20argv)%3B%0A%0A%20%20%20%20QQmlApplicationEngine%20engine%3B%0A%0A%20%20%20%20engine.rootContext()-%3EsetContextProperty(%22ScreenW%22%2C1280)%3B%0A%20%20%20%20engine.rootContext()-%3EsetContextProperty(%22ScreenH%22%2C720)%3B%0A%0A%20%20%20%20engine.load(QUrl(QStringLiteral(%22qrc%3A%2Fmain.qml%22)))%3B%0A%0A%20%20%20%20QmlControler%20ctr%3B%0A%20%20%20%20ctr.setEngine(%26engine)%3B%0A%0A%20%20%20%20return%20app.exec()%3B%0A%7D%0A” message=”main.cpp” highlight=”” provider=”manual”/]

Really easy, it loads the main.qml from the resources management system provided by Qt. It defines the targeted resolution by setting two constant into QML word: ScreenW and ScreenH.

In this project, the QmlControler class is the C++ window which provides slide feedback and additional information.

Let’s take a look to it:

Feedback window

This window is really simple. It has two parts: On the left, there is a label which displays screenshot of the qml view, and the right part is a QTextArea which display any additional note about the current slide.

[pastacode lang=”cpp” manual=”void%20QmlControler%3A%3AcurrentPageHasChanged(int%20i)%0A%7B%0A%20%20%20%20m_currentScreen%20%3D%20i%3B%0A%20%20%20%20QImage%20img%20%3D%20m_window-%3EgrabWindow()%3B%0A%0A%20%20%20%20if(img.isNull())%0A%20%20%20%20%20%20%20%20return%3B%0A%0A%20%20%20%20static%20int%20count%20%3D%200%3B%0A%0A%0A%20%20%20%20img.save(tr(%22screens%2F%251_screen.png%22).arg(%2B%2Bcount%2C3%2C10%2CQChar(‘0’))%2C%22png%22)%3B%0A%20%20%20%20qDebug()%20%3C%3C%20%22screen%20shot%20save%22%20%3C%3C%20count%3B%0A%0A%20%20%20%20m_ratioImage%20%3D%20(double)img.size().width()%2Fimg.size().height()%3B%0A%20%20%20%20m_ratioImageBis%20%3D%20(double)img.size().height()%2Fimg.size().width()%3B%0A%0A%20%20%20%20m_label-%3EsetPixmap(QPixmap%3A%3AfromImage(img))%3B%0A%0A%20%20%20%20if((i%2B1%3E%3D0)%26%26(i%2B1%3Cm_commentData.size()))%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20ui-%3EtextEdit-%3EsetHtml(m_commentData.at(i%2B1))%3B%0A%20%20%20%20%7D%0A%20%20%20%20resizeLabel()%3B%0A%7D” message=”Current slide has changed” highlight=”” provider=”manual”/]

When the current slide has changed, the c++ window is notified thought the slot currentPageHasChanged, The application gets screenshot of the qml view, save it as a file, display it thought the label, then it looks for data about the current slide into the model. If any, there are displayed in the textedit.

Saving screenshots into file allows you to create a pdf file as backup plan for your presentation.

$ convert *.png mypresentation.pdf

 

QML Application

Loader system.

For readability reason, it is easier to have each page into one qml file. The application has to load those pages in the right order. To reach this goal, we have to define the order. I did it thank to a data model inside the main.qml file.
The main.qml displays all pages as item of a pathview. All items are loaded from the qt resource management system.

[pastacode lang=”css” manual=”ListModel%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20id%3A%20panelModel%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Intro%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20path%3A%20%2201_intro.qml%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20time%3A%201%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20next%3A%20%22Pr%C3%A9sentation%20de%20Rolisteam%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D” message=”First item of the model.” highlight=”” provider=”manual”/]

A page is mainly defined by two data: name and path. The path is the name of the qml file.
All other data are here as help, the time has not been used.

Then, the loader does its job, the key lines are the following:

[pastacode lang=”css” manual=”%20%20%20%20PathView%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20view%0A%20%20%20%20%20%20%20%20anchors.fill%3A%20parent%0A%20%20%20%20%20%20%20%20model%3A%20panelModel%0A%20%20%20%20%20%20%20%20highlightRangeMode%3APathView.StrictlyEnforceRange%0A%20%20%20%20%20%20%20%20snapMode%3A%20PathView.SnapOneItem%0A%20%20%20%20%20%20%20%20delegate%3A%20%20Loader%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20source%3A%20%22pages%2F%22%2Bpath%0A%20%20%20%20%20%20%20%20%7D” message=”Path View” highlight=”source: “pages/”+path” provider=”manual”/]

 

Table of Contents

To manage the table of contents, I added a listview with a model:

[pastacode lang=”css” manual=”%20%20%20%20ListView%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20listView1%0A%20%20%20%20%20%20%20%20x%3A%20ScreenW*0.02%0A%20%20%20%20%20%20%20%20y%3A%20ScreenH*0.3%0A%20%20%20%20%20%20%20%20width%3A%20ScreenW%2F2%0A%20%20%20%20%20%20%20%20height%3A%20ScreenH*0.2%0A%20%20%20%20%20%20%20%20delegate%3A%20Item%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20width%3A%20ScreenW%2F2%0A%20%20%20%20%20%20%20%20%20%20%20%20height%3A%20listView1.height%2FlistView1.count%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20Text%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3A%20view.currentIndex%3E%3Dindex%20%3F%20%22black%22%20%3A%20%22gray%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20text%3A%20name%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20font.pointSize%3A%20ScreenH%2F48%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20anchors.verticalCenter%3A%20parent.verticalCenter%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20font.bold%3A%20true%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20visible%3A%20view.currentIndex%3E0%20%3F%20true%20%3A%20false%0A%0A%20%20%20%20%20%20%20%20model%3A%20ListModel%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Concepts%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A1%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Chroniques%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A6%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Logiciel%22%2F%2Fsyst%C3%A8me%20de%20build%2C%20code%20sp%C3%A9cifique%20par%20OS.%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A9%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%20%20%20%20ListElement%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20name%3A%20%22Bilan%22%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20index%3A15%0A%20%20%20%20%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%7D” message=”Table of contents in QML ” highlight=”” provider=”manual”/]

Next slide

When you have many slides it can be helpful to have indication about the next one. I chose to display the title in the top-right corner. It was the easier way.

[pastacode lang=”css” manual=”%20%20%20%20Text%20%7B%0A%20%20%20%20%20%20%20%20anchors.top%3A%20parent.top%0A%20%20%20%20%20%20%20%20anchors.right%3A%20parent.right%0A%20%20%20%20%20%20%20%20text%3A%20panelModel.get(view.currentIndex).next%2B%22%3E%22%0A%20%20%20%20%7D” message=”Next slide” highlight=”” provider=”manual”/]

 

Design a page

Each page are independent but they are all based on the same pattern. In my case, they have a listview with model. Each item of the model is an point I should talk about it.

Each item has a index. The index is controlled with keyboard (down to increase, up to decrease). The page manages what is shown or hidden given the value of the index.

For example, the feature of dice alias has 10 as index. When the index page value becomes 10, the «Dice Alias»  item is displayed with an animation. Then, at 11, I can show a screen shot about the dice alias. At 12, the screenshot disappears and another text is displayed.

Position and Size

To ensure that all items will be display at the proper position and size.  I have based all computation on anchor or the screen size.

[pastacode lang=”css” manual=”%20%20%20%20Image%20%7B%0A%20%20%20%20%20%20%20%20id%3A%20image1%0A%20%20%20%20%20%20%20%20anchors.left%3A%20parent.left%0A%20%20%20%20%20%20%20%20anchors.top%3A%20parent.top%0A%20%20%20%20%20%20%20%20anchors.leftMargin%3A%20ScreenW*0.04%0A%20%20%20%20%20%20%20%20fillMode%3A%20Image.PreserveAspectFit%0A%20%20%20%20%20%20%20%20source%3A%20%22qrc%3A%2Frsrc%2FRolisteam.svg%22%0A%20%20%20%20%20%20%20%20width%3A%20ScreenW*0.2%0A%20%20%20%20%7D” message=”Display the logo at the right position and size.” highlight=”” provider=”manual”/]

Other way

There is a module that provides Items to create QML presentation. I don’t use it for this one but it may provide interesting things.

https://github.com/qt-labs/qml-presentation-system

Get the code

You are invited to clone the code at : https://github.com/obiwankennedy/pses