The PublicTransport Plasmoid
The PublicTransport applet shows departure/arrival/journey timetables for public transport (trams, buses, subways, etc.), trains, airplanes and some ships. The data comes from the PublicTransport data engine, which uses scripts for different service providers to parse timetable documents (mostly HTML). It already supports many countries in europe (Germany, Switzerland, Austria, Czechia, Slovakia, Belgium, Italy, Sweden, Denmark, Poland) and plane departures/arrivals all over the world. There is also a PublicTransport runner which shows departures/arrivals, clicking them opens the parsed website in a browser.
The data engine offers stop name suggestions, if the service provider supports that. With a data source name like "Stops stop=Bremen" it returns stop suggestions using the default service provider for the users country (if there is one). A specific service provider can be used with a source name like "Stops de_db|stop=Bremen", which uses the "de_db" service provider (Deutsche Bahn, germany). To get a list of all installed service providers there is a data source "ServiceProviders". The supported countries can be retrieved using "Locations".
The applet shows stop suggestions using KCompletion. Some providers also provide weights for suggestions which get send to KCompletion. To prevent ambiguities stop IDs can be used with some providers.
If a request for "Departures" leads to an ambiguity it tries to parse the document for stop suggestions. That way the publictransport runner shows suggestions if the input is ambiguous, the user can then correct the query (eg. "Departures Bremen Hbf"). In the applet stop suggestions are also used for journey searches, where the user can query journeys with an easy syntax (and gets suggestions to add keywords like "in 5 minutes").
Departures can be retrieved from the data engine with a source name like "Departures stop=Bremen Hbf", a specific service provider can be used just like with the "Stops" source. For arrivals eg. "Arrivals stop=Bremen Hbf" can be used.
This gives more or less information about the departures/arrivals depending on the used service provider. It contains the vehicle type, target and departure/arrival time. It may also contain delays, delay reasons, intermediate stop names, news for a departure/arrival, platforms, operator names, etc.
For each supported service provider there is an XML file describing it (name, description, urls, etc.) and a script file which parses timetable documents. The XML stores "raw URLs" with placeholders for the stop name, the date/time of the departures, etc.
Functions with special names get executed using Kross to parse the timetable documents, eg.: "parseTimetable( document )" to parse departures/arrivals, the timetable document is given as argument (eg. HTML code), "parseJourneys( document )" to parse journeys from A to B, "parsePossibleStops( document )" to parse stop suggestions. For stop suggestions there are often JSON documents available, otherwise mostly HTML is used. The data engine offers some objects to scripts to add timetable data, eg. "timetableData.set( 'Target', targetString ); result.addData( timetableData );". The "set" function accepts many different strings for timetable informations (DepatureHour, DepartureMinute, Delay, DelayReason, RouteStops, Platform, etc.).
There's also a tool called "TimetableMate" for adding support for new service providers. It provides autocompletion and tooltips for functions used by the data engine and objects offered by the data engine. It also has a GUI for all elements used in the XML files, a KWebKit viewer, a Plasma previewer (testing the new service provider in the applet), can check scripts for errors and run them with test data, showing the results.
Before scripts were supported the data engine only used regular expressions and meanings of it's matches, which were stored in the XML files. There were pretty much code and special handling needed to support the different service providers. It's much easier and more flexible now with the scripts. I ported all the old providers to now use scripts, so that the old code can be dropped.
The applet has some more features, eg. filters. Departures/arrivals can be filtered by some constraints like the target, the vehicle type, intermediate stops, etc. The GUI for the filters allows to add/remove filter widgets. The same base class is used for the stop settings dialog, where the user can add more stop settings widgets (stop name, service provider + other settings).
Another feature of the applet are alarms. There are recurring and single shot alarms, the associated departures are assigned using the filter classes. For example an alarm can be set for "On monday, bus line 20 at 8:30 am". Filters and alarms are applied in a separate thread, to not freeze plasma while many complex filters are applied on lots of departures. The departure data from the data engine is also put into structures in the thread (classes DepartureInfo / JourneyInfo).
The applet uses a Plasma::TreeView to show the departures. It uses a custom treeview class derived from QTreeView to theme the header more plasma-like and to fade the contents out at the top and bottom. It also uses a custom delegate that can display HTML.
The stop settings dialog is shared between the applet and the runner in a separate library "publictransporthelper". It offers a class StopSettingsDialog, which the applet uses to select service providers, stop names and other advanced settings. The runner uses the same class to only select a service provider. The dialog also offers a button to show more information about the service provider, to install new service providers and uses stop autocompletion. The installation works from local XML files (and automatically installs the script with it). GHNS support isn't working correctly currently.
I just commited a change to make all labels in the StopSettingsDialog the same width using the ColumnResizer class from Aurelien Gateau.
Dynamic Widget Container Class
The base class used to allow dynamic adding/removing of widgets is named "AbstractDynamicWidgetContainer". It's contained in the library. Derived classes are "DynamicLabeledLineEditList" (used for multiple stop names), "StopListWidget" (for multiple stop settings), "FilterListWidget" (for multiple filters, OR combined), "ConstraintListWidget" (for multiple constraints in a filter, AND combined) and "ChangelogWidget" (for multiple changelog entries in TimetableMate). It has some options like showing one remove button after the last widget / showing a remove button besides each widget, showing separators between widgets, minimum/maximum widget count, adding new widgets to the bottom/top. "AbstractDynamicLabeledWidgetContainer" adds a label for each widget (the text can include "%1", replaced by the widget number).
GPS Stop Finding
If GPS is available (I don't have one), the applet can offer stops that are near the users current position. This uses the geolocation data engine to get the position which gets send to the openstreetmap data engine. That data engine then finds the public transport stops. That will be nice in plasma mobile :)
I recently added unit tests to the data engine, which tests all service providers and the different data sources. This is very useful. For example, if a service provider changes the HTML structure of it's result websites, the script needs to be updated. There are also unit tests for the publictransporthelper library.
The GHNS support needs to get finished. And I think the project should move to Git, I'll try to find out how to do that. More supported service providers in more countries, preferably with non-HTML data, would also be nice of course. I recently added one for Sweden after a users request.
For more information (almost) everything has APIDOX documentation. The source code is available in SVN /trunk/playground/base/plasma/.../publictransport, where "..." is one of applets, dataengines, runners. There are pages on UserBase and kde-look.