Darktable heeft zijn eigen GUI-widgetbibliotheek, voor schuifregelaars en comboboxen (ook wel keuzelijsten of selectievakken genoemd), genaamd Bauhaus (in de broncode staat die in src/bauhaus/bauhaus.c). Hoewel ze Gtk als backend gebruiken, zijn Bauhaus-objecten aangepaste objecten. En zoals veel dingen in Darktable geldt: aangepast staat gelijk aan rot.

In 2022 ‍merkte ik parasitaire hertekeningen en vertragingen  op bij het gebruik ervan, wat tot een frustrerende gebruikerservaring leidde: het hertekenen van de widget leek te wachten tot de herberekeningen van de pipeline waren voltooid, wat betekende dat gebruikers niet echt zeker wisten of hun waardewijziging was geregistreerd, waardoor ze het opnieuw konden proberen en zo een nieuwe cyclus van dure herberekening startten, en hun computer effectief enkele zeer frustrerende minuten bevroren met nutteloze tussentijdse herberekeningen van de pipeline.

Zoals het toeval wil, ontdekken tools voor statische codeanalyse dat de Bauhaus-bibliotheek ook het 4e meest complexe broncodebestand  is in de hele Darktable-software wat betreft cyclomatische complexiteit, met een score van 735 en een technische schuld die op 1 dag en 7 uur wordt geschat. Als je geen programmeur bent: cyclomatische complexiteit meet het aantal verschillende paden dat de code kan nemen, en hoge waarden maken die niet alleen moeilijker te begrijpen (en dus te debuggen), maar ook gevoeliger voor randgevallen, bugs en contextafhankelijke rare problemen. Cyclomatische complexiteit  is een indirecte maat voor de waarschijnlijkheid dat deze code je in het gezicht ontploft wanneer je het het minst verwacht, iets om rekening mee te houden wanneer de meest productieve „ontwikkelaars" in je team een basisschoolleerkracht, een kinderarts en een bankadviseur zijn.

Wat bijzonder frustrerend is, is dat ik al aan het vereenvoudigen van dit bestand had gewerkt, in 2019. Drie jaar later was het alsof ik niets had gedaan, dankzij feature creep  en MIDI/gamepad-ondersteuning. Toen de parasitaire hertekeningen opdoken, bleef ik zitten met onbegrijpelijke spaghetticode  die ik gewoonweg niet kon repareren. Een eerste poging om verkeerde zaken te corrigeren liep op een dood spoor uit, in augustus 2022, en ontmoedigde me. Er omheen proberen te werken zou het niet redden.

Dus moest ik het bijna volledig herschrijven, wat geen pretje was en me een krankzinnig aantal uren kostte (ik stopte met tellen bij 3 weken fulltime, en dat was voor de tweede poging in augustus-november 2023).

Wist je dat je, zodra een combobox de focus had (hetzij omdat je erop klikte, hetzij omdat je die focus gaf via een sneltoets), de eerste letters van het label dat je wilde selecteren kon beginnen te typen, en dat die dan automatisch het dichtstbijzijnde item in de lijst selecteerde? Ik ook niet, voordat ik deze taak op me nam, want het is nergens gedocumenteerd. Net als veel verborgen functies daarin, toegevoegd om te voldoen aan afwijkende en marginale gebruiksscenario’s, maar die de codestructuur voor iedereen complexer maakten. (Spoiler alert: ik heb deze specifieke functie behouden, maar andere verwijderd).

‍Lijst met verbeteringen

  • Cursorcoördinaten (in pop-ups) worden slechts op één plek berekend en vervolgens opgeslagen. Dat bespaart een hoop tussentijdse herberekeningen, waarvan sommige inconsistent waren omdat de code werd gekopieerd en gedupliceerd in plaats van getters en setters  te gebruiken. Nu worden coördinaatverschuivingen en -wijzigingen via uniforme getters en setters afgehandeld, wat betekent dat elke toekomstige wijziging maar op één plek hoeft te gebeuren, en de hele code gebruikt dat.
  • Nieuwe waarden (van schuifregelaars en comboboxen) worden niet meer tijdens het scrollen of slepen naar de pixelpipeline verzonden, maar pas aan het einde. Dit steunt op een machinaal geleerde time-out die de gemiddelde tijd registreert die nodig is om een volledige pipeline te berekenen. De GUI wacht 20 ms tot 2,5 s voordat wijzigingen naar de pipeline worden verzonden, wat voorkomt dat er bij elke scroll- of sleepstap opnieuw wordt berekend, wat zorgt voor nutteloze en overbodige, maar toch dure berekeningen die de software alleen maar laten haperen. Op vergelijkbare wijze worden ze nu alleen verzonden bij de gebeurtenis knop-losgelaten, in plaats van bij de gebeurtenissen knop-ingedrukt en knop-losgelaten (aangezien een typische muisklik beide gebeurtenissen verstuurt). Het zou heel wat overbodige herberekeningen van de pipeline moeten besparen.
  • Widgets worden onmiddellijk opnieuw getekend bij gebruikersgebeurtenissen, voordat de nieuwe waarden naar de pixelpipeline worden verzonden. Dit zorgt voor onmiddellijke feedback aan de gebruiker, ook al kan het daadwerkelijke pixelresultaat later komen, en beperkt de frustratie op trage computers.
  • Verzend geen waarde-gewijzigd-gebeurtenissen als de widgets gebruikersinteractie kregen maar hun waarde niet echt veranderde.
  • Vang klikken op de chevron (pijl naar rechts) van de comboboxen op. Voorheen moest je op het label klikken om het keuzemenu van de combobox uit te rollen, wat superfrustrerend was als je van software met een fatsoenlijke GUI kwam. De chevron zelf reageerde niet op klikken.
  • Scroll niet in de keuzelijsten van comboboxen. Die functie begon als een cool project: het momenteel geselecteerde item uitgelijnd met het label hebben. Het probleem is dat de positionering van de keuze-pop-up uiteindelijk door de desktopomgeving wordt afgehandeld; we kunnen die alleen beleefd vragen te doen wat we wensen, maar er is geen garantie dat onze wensen worden ingewilligd. Ook is het mogelijk om de pop-up buiten de viewport te laten rollen, niets voorkomt dat. Al met al is het broos en willekeurig; blijf beter bij de standaard vensterpositionering.

Afgeschafte functies

De mogelijkheid om sneltoetsen aan schuifregelaars en comboboxen toe te wijzen is voorlopig verwijderd. Schuifregelaars en comboboxen zijn hoe dan ook gekoppeld aan de pijltjestoetsen en het muiswiel zodra ze de focus krijgen1, en het is nog steeds mogelijk om het vastleggen van de focus aan een sneltoets toe te wijzen. De huidige logica is dus om via sneltoetsen focus aan te vragen en vervolgens de waarde te bewerken met de pijltjestoetsen. Focusaanvragen maken de widget ook automatisch zichtbaar in de GUI, waarbij de zijbalk indien nodig wordt gescrold.

Hoe dan ook, het huidige sneltoetsensysteem zal volledig moeten worden vervangen door native Gtk-accelerators, die al voor het globale menu worden gebruikt (en die in Darktable werden gebruikt vóór 2021). Momenteel hebben we beide systemen, waarvan er één een monstruositeit van complexiteit is en waarschijnlijk verantwoordelijk is voor vertragingen. KISS.

Kanttekeningen

De „format"-combobox in de exportmodule initialiseert zijn waarde niet correct. Dit is een niet-standaard Bauhaus-widget die extra zorg vereist. Voorlopig moet je de opslagoptie vernieuwen of een voorinstelling opnieuw laden.

Telkens wanneer je de brokkelige oude verf verwijdert die de roestige muren vasthield, loop je het risico op collaterale schade. Wees voorzichtig met Ansel bij het gebruik van de AppImage en Win EXE die van vanavond de tag Ansel-57ed58d dragen, en meld alles wat vreemd is.

Downloads

Details voor techneuten

De huidige wijzigingen hebben het volgende verminderd:

  • cyclomatische complexiteit van 735 naar 494
  • cognitieve complexiteit van 796 naar 432
  • technische schuld van 1 dag en 7 uur naar 4 uur en 50 min.

Meer details 

Als je geen programmeur bent, betekenen deze metrieken gewoon dat de code in de toekomst gemakkelijker en met minder tijd te onderhouden zal zijn, en waarschijnlijk minder buggevoelig.


Translated from English by : Claude. In case of conflict, inconsistency or error, the English version shall prevail.

  1. In GUI programming, a widget has the focus when it is the one recording keyboard events. Text entries are the most obvious example, but Darktable hacked that concept to generalize to pretty much every widget. ↩︎