pcb-rnd knowledge pool
HID API policy
hid_policy by Tibor 'Igor2' Palinkas on 2018-04-14
Tags: insight, HID, API, GUI
Abstract: Specify the future directions of the HID API with detailed rationale.
Prior art and current state
It is safe to say that majority of the GUI applications support only one flavor of GUI - one toolkit, one widget set, one library at any given time. There are only a few applications that aims to provide multiple, parallel GUIs long term.
Historically PCB started out supporting only one; originally it was the host computer's native GUI on the Atari ST. After the first port to UNIX, it was rewritten to support another one toolkit: Xaw. This remained the choice for a decade between the mid '90s to the mid 2000s. Then the GUI had to be replaced as xaw considered outdated and obsolete 1 . A gtk port emerged.
Fortunately that time gEDA/PCB developers decided not to switch over the code from Xaw to gtk, but first decouple the GUI from the rest of the code. The new API was named the HID API (probably for Human Interface Device ), and the gtk code was written behind this abstract, more or less GUI-independent HID API. Around the same time DJ Delorie wrote the Lesstif HID, which is a lightweight alternative to the gtk HID. It also helped a lot in cross-checking the API and make sure it is not tailored to gtk (which would let the HID API become only an extra layer of bloat with no benefit). For automatic processing a CLI based batch HID is available.
(Since exporting very often include drawing the board, the exporter API is just a subset of the HID API and export modules are accessed through the same HID API. For this article, the exporter HIDs can be ignored.)
Another decade passed, and unlike what many anticipated, the HID API did not result in more GUIs appear in gEDA/PCB. In the same time, pcb-rnd started to grow a few more GUI HIDs: separate gdk and gl support for gtk2, and a generic remote (e.g. network) HID. More HIDs are planned for the upcoming years, including an SDL2 based one.
pcb-rnd's new HID policy
There are mainly three approaches possible:
- #1. single-HID: support only one GUI
- #2. diverse-HID: support multiple GUIs, each implemented in a way the given GUI usually works
- #3. homogeneous-HID: support multiple GUIs, each implementing a thin layer around central code that tries to do the same on every possible GUI
PCB in the Atari and Xaw years implemented #1. This is the simplest: GUI code mixed with application code. Pros: application features can be designed and written in a way that suits the customs of the given GUI; no need to worry about how the same feature will look or work on different hosts. Cons: if the application is to be ported to a new system, either the chosen toolkit/lib needs to be ported first or the application has to be rewritten. The latter happened in the Atari ST -> Xaw transition.
Then another rewrite happened: gEDA/PCB switched to #2 with HIDs. The HID API is fat. This leaves HIDs a lot of freedom. As a consequence the Lesstif HID does not look even similar to the GTK HID. Pros: it's a compromise still letting different HIDs look and work differently, while allowing common code to be coded only once; makes it easier to port to a new platform as it is possible to write a new HID using whatever lib/toolkit that is native there. Cons: it is hard to document different looking GUIs in one user doc; this leads to having two or more parallel documentations; it is hard to code new features and make sure they work on all HIDs (see below, under the F*H section). As a consequence in gEDA/PCB the Lesstif HID offers considerably less features than the gtk HID.
In #3, the HID API is thin. HIDs are not trying to invent how to do things, they are just small slave code that faithfully display what the core tells them to and report the user input. Pros: it is much easier to implement a new HID as only a small number of GUI features need to be implemented. It is easy to implement new features (see below, under the F*H section). Cons: all GUI HIDs look basically the same - the look & feel of the application is the application's, not the toolkit's; some users don't like if gtk applications don't look and work like other gtk applications.
As of 2.0.0, pcb-rnd is heading from #2 to #3. The reasons for this decision are described below.
The F*H problem
Let F be the number of features or application functionalities (not pure GUI-based, but conceptual features) and let H be the number of HIDs. The number of complications is C; that means the number of times the programmer needs to think over something, needs to care about something.
Setup #1 is the cheapest: C=F*H, but H==1, which means C=F. The programmer needs to care about each feature once. Things can not get cheaper than this.
Setup #2 is the most expensive: C=F*H; if gEDA/PCB has two HIDs, this means any new feature needs to be tested twice. Many features, especially those that need extra GUI, need to be implemented twice - once per HID.
Setup #3 is cheapish: C=F+H. Each feature communicates with the thin API only. The API is advanced enough that all feature-specific GUI can be explained through it. The API is generic enough that any HID implementation can display any feature GUI without having to understand the feature. Thus each feature needs to be implemented/tested only once, using a randomly chosen HID, and each HID needs to be implemented/tested only once, using a random complex feature.
Good and bad HID API
Bad HID API is complex functions that implement #2. Most of these are inherited from gEDA/PCB. For example the calibrate call is bad API: it lets each HID reinvent and reimplement the printer calibration GUI. The dynamic attribute dialog (DAD) is a good API: it implements a generic mechanism to build dialog boxes in a GUI-independent way. The DAD API needs to be implemented in each HID (implemented H times) and each feature that needs a dialog box has to care about only this one GUI API, not each HIDs (so feature dialog implemented only once per feature, F). That's how DAD results in C=F+H.
The following table summarizes which HID features are good and which ones are bad. The long term plan is to remove the bad API. These are the fields of struct pcb_hid_s in src/hid.h.
|name, description, user_context, gui, printer, exporter||good||HID metadata so that the core knows what the HID is capable of; no major change expected|
|holes_after, can_mask_clear_rats, force_compositing||mixed||rendering engine configuration; some of these will be removed as the rendering gets simpler and more unified|
|do_export(), init(), uninit(), do_exit(), parse_arguments(), usage()||good||main entry points; no major change expected|
|invalidate_*(), notify_*()||good||redraw notifications; some might be converted into events|
|set_layer_group(), end_layer(), render_burst()||good||render: layer compositing; no major change expected|
|make_gc(), destroy_gc(), set_drawing_mode(), set_color(), set_line*(), set_draw*()||good||render: "pen" support; no major change expected|
|draw_*(), fill_*(), thindraw_*()||good||render: object drawing; no major change expected; minor optimization (reducing number of calls) expected|
|calibrate||bad||printer calibration; to be removed and reimplemented using DAD|
|*_is_pressed()||good||may be moved into core for HID API simplification|
|get_coords(), get_view_size()||good||action GUI queries; no major change expected|
|set_crosshair(), point_cursor()||good||crosshair is expensive to draw and is drawn on any mouse movement, mouse cursor is GUI-specific; thus these should be done in the HID; no major change expected|
|add_timer(), stop_timer(), watch_file(), unwatch_file(), add_block_hook(), stop_block_hook()||good||main loop is at the HID, timers and watches need to be managed there; no major change expected|
|log(), logv()||bad||logging should be implemented centrally, using DAD|
|confirm_dialog(), close_confirm_dialog(), report_dialog(), prompt_for(), show_item(), progress(), drc_gui, edit_attributes()||bad||should be reimplemented using DAD|
|fileselect()||good||file listing and selection is platform specific; shall be extended with a format specific DAD section|
|attribute_dialog(), attr_*()||good||the DAD API; no major change expected|
|create_menu(), remove_menu*(), update_menu_checkbox()||good||menu management; no major change expected|
|propedit_*()||bad||should be reimplemented as DAD in the propedit plugin|
What a HID will need to do
After the transition to #3, a new HID will mainly need to:
- register itself, handle init/uninit
- run the main loop, watch for files and timers
- handle user input (mouse and keyboard) passing any event down to the core
- render the drawing as instructed by the core
- create and manage the dynamic menu system
- create and manage modal and non-modal DAD dialogs
- handle the mouse cursor shape
- draw the status line(s)
What a HID must not do:
- implement custom menus anywhere in the GUI
- implement custom dialog boxes
- implement custom input processing
What a feature will need to do
After the transition to #3, a new feature will mainly need to:
- if the feature has interactive aspects, implement the DAD dialog API
- directly use a GUI toolkit to draw widgets - DAD must be used
- define local, GUI-specific actions 3
- block the process for too long, without using the cancellable progress bar
After the transition to #3, the following benefits are expected:
- it will be easier to implement new HIDs as the HID API will be thinner
- it will be easier to maintain existing HIDs
- the Lesstif HID will finally catch up with the gtk HID in functionality 2
- more DAD widget features available for feature developers
1. outdated and obsolete not in technical sense but in "user experience" sense, whatever that means. I.e. old, mid '90s versions of of PCB still can be compiled in modern Linux desktops, using current versions of the Xaw toolkit; as of 2018 this works out of the box on a Debian host, without having to manually compile Xaw. In the same time, compiling a qt3 application is much more problematic, while qt3 was released around 2001. For the rest of this article, it is very important to note the difference between stable/mature kind of old (e.g. Xaw) and hard-to-use kind of old (e.g. qt3), and this is rarely determined by the age of the lib.
2. the simplicity of the Lesstif main window should not be confused with the HID implementation. The simple, stripped down version, called full screen is already available as a runtime option in the gtk HID. The Lesstif HID should make the same rich controls available and the same full screen function should switch between the simplistif Lesstif and the rich Lesstif main window.
3. part of the PCB API was actions implemented by the HIDs. It looked like a good idea: instead of inventing a new API for e.g. zoom, just let each HID implement its own Zoom() action. However, this does not make implementing a HID simpler, but induced a lot of code and doc duplication. Plus it lets HIDs diverge on user-visible API too easily. The new policy is that GUI HIDs shall not have local actions. GUI actions will be centralized in a plugin all GUI HIDs will depend on. Between the plugin and the HID there will be a small number of custom API.