Vizuálna časť programu DynaSim

Z Kiwiki
Skočit na navigaci Skočit na vyhledávání
Tnu wiki.png
Trenčianska Univerzita Alexandra Dubčeka v Trenčíne
Fakulta Mechatroniky
Fm wiki.png
Vizuálna časť programu DynaSim

Diplomový projekt


Autor:
Pedagogický vedúci:

Ing. Juraj Ďuďák

Študijný odbor: Mechatronika

Akademický rok

2009/2010

Vizuálna časť programu DynaSim

Vzhľadom na design aplikácie DynaSim, je vhodné začať najskôr s tvorbou vizuálnej časti, a až potom pokračovať v tvorbe funkčnej časti, pretože funkčná časť je priamo závislá od časti grafickej. Pred tým však treba vytvoriť novú aplikáciu na platforme NetBeans a vytvoriť v nej modul, ktorý bude predstavovať jadro tejto aplikácie.

Okno editora programu DynaSim

Ak už máme vytvorenú aplikáciu platformy NetBeans a v nej jeden prázdny modul (viz. kap 1.4), môžeme si všimnúť, že aplikácia a modul obsahujú len konfiguračné súbory. Po skompilovaní takéhoto projektu je však možné ho spustiť aj napriek tomu, že sme do neho nedodali žiaden kód. Spustí sa len základný framework aplikácie (obr.5).

Obr.5 Prázdny projekt

Ako vidno z obrázku, toolbar je neaktívny a väčšina položiek v menu je taktiež neaktívna. Aby sme teda frameworku mohli dodať funkcionalitu, musíme vytvoriť nové okná a akcie pre jednotlivé menu položky. Môžeme začať s tvorbou okna editora. Do okna editora sa budú pridávať jednotlivé simulačné komponenty z palety a tie sa následne prepoja spojovacími čiarami. To znamená, že sa bude jednať o grafické rozhranie, v ktorom užívateľ tvorí vlastné simulačné schémy.

TopComponent

Na tvorbu nových okien v rámci aplikácií postavených na platforme NetBeans sa používa TopComponent. Ten je poskytovaný cez modul Window System API. Na vytvorenie nového okna v aplikácii klikneme pravým tlačítkom na modul, ktorý bude manažovať toto okno a vyberieme položku New → Other a následne označíme Window Componenet z kategórie Module Developement. Po kliknutí na tlačítko Next sa nám zobrazí nové okno. V ňom v drop-down menu s názvom Window Position vyberieme jednu z preddefinovaných pozícií okna. Kedže chceme vytvoriť okno editora, je vhodné zvoliť si pozíciu editor. Tým sa celá nevyužitá šedá plocha v strede našej aplikácie (obr.5) pridelí TopComponentu. Ďalej je možné vo wizardovi nastaviť, či sa má okno spúšťať so spustením aplikácie. V ďalšom kroku je treba doplniť predponu pre názov triedy a môžeme dodať ikonu pre toto okno. Po stlačení tlačidla finish wizard vytvorí mimo hlavnej triedy aj niekoľko jej prislúchajúcich súborov. Je vhodné všimnúť si, že nám wizard automaticky vytvoril aj závislosti na potrebné moduly, konkrétne na Settings API, Swing Layout Extensions integration, UI utilities API, Utilities API a Window System API. Teraz je nutné ísť do našeho novovytvoreného java súboru s koncovkou TopComponent a predponou, ktorú sme zvolili vo wizardovi (napr. DynaSimTopComponent). V source časti tohto súboru sa nám vygeneroval kód a môžeme si všimnúť aj niekoľko preťažených metód, ktoré predstavujú abstraktné metódy rodičovskej triedy. Zatiaľ však nie je nutné ich meniť. V design časti nastavíme TopComponentu BorderLayout a a pridáme jScrollPane. Tým máme zaručené, že celá plocha editora bude scrollovateľná.

GraphPinScene

Na simulačné schémy, ktoré budeme tvoriť v okne editora sa treba pozerať ako na grafovo orientovaný model. Simulačné komponenty predstavujú uzly a spojovacie čiary medzi nimi hrany. Visual Library API je vhodná práve na takéto účely. Visual Library API nám ponúka dva druhy koreňových elementov – scén vhodných na tieto účely a to GraphScene a GraphPinScene. Scéna samotná je widget a ako bolo načrtnuté v kapitole 1.6, widgety majú stromovú štruktúru. Scéna je na úplnom vrchu hierarchie tejto stromovej štruktúry a stará sa o zobrazovanie ostatných widgetov na editačnej ploche. GraphScene je scéna ktorá sa skladá len z uzlov a hrán, kým GraphPinScene obsahuje komponenty skladajúce sa z uzlov, hrán a pinov, čiže vstupov/výstupov jednotlivých uzlov. Jeden uzol môže mať ľubovoľný počet pinov. GraphPinScene je vhodnejšia pre účely DynaSimu, lebo jeho simulačné komponenty sa tiež skladajú z uzlov, hrán a terminálov.

Obr.6 Porovanie GraphScene a GraphPinScene

GraphPinScene<N, E, P> je abstraktná trieda, preto z nej treba vytvoriť novú triedu dedením. Takto vytvorená trieda v programe Dynasim má názov DynaSimScene, jej delarácia je nasledovná:

 public class DynaSimScene extends GraphPinScene<MyNode,MyEdge,MyPin>

Po jej vytvorení sa automaticky vygeneruje časť kódu pre jej abstraktné metódy, to znamená, že sa v detskej triede preťažia. Ich parametre sú priamo závislé od dátového typu zadaného v šablóne pre GraphPinScene. MyNode, MyEdge a MyPin predstavujú dátové uzly jednotlivých widgetov. Tie budú rozobrané v kapitole 3. Od vývojára sa očakáva, že doplní kód týchto metód podľa špecifických potrieb jeho programu. Tieto metódy sú volané inými metódami danej triedy a neodporúča sa volať tieto preťažené metódy priamo. Inštancia tejto triedy sa skladá z vrstiev LayerWidget, ktoré slúžia na zobrazovanie widgetov na scéne. DynaSimScene má 3 takéto vrstvy a to:

mainLayer – slúži na zobrazovanie simulačných komponentov

connectionLayer – slúži na zobrazovanie spojovacích čiar

interractionLayer – slúži na zobrazovanie select obdĺžnika pri multiselecte

Pri vytváraní takýchto vrstiev a postupuje nasledovne:

1. Vytvorí sa inštancia triedy LayerWidget

2. Vytvorená inštancia sa scéne pridá ako detský widget

Príklad vytvorenia novej vrstvy v konštruktore triedy DynaSimScene:

 LayerWidget mainLayer = new LayerWidget(this);
 this.addChild(mainLayer);

Teraz sa bližšie pozrieme na preťažené metódy, ktoré sa automaticky vygenerovali:

protected Widget attachNodeWidget(MyNode node) – táto metóda je volaná metódou addNode. Tá sa v tomto programe volá po dropnutí vybraného komponentu z palety na editačnú plochu. Metóda je zodpovedná za tvorbu widgetu pre simulačný komponent a jeho pridanie na scénu. Konkrétne v programe DynaSim sa pod widgetom rozumie inštancia typu Component_Widget, ktorý sa vytvorí na základe údajov z parametra node a následne sa prilepí na mainLayer.

protected Widget attachEdgeWidget(MyEdge edge) – táto metóda je volaná metódou addEdge. Metóda je zodpovedná za tvorbu widgetu pre spojovaciu čiaru a jeho pridanie na scénu. V programe DynaSim sa pod týmto widgetom rozumie inštancia typu MyConnectionWidget, ktorý sa vytvorí na základe údajov z parametra edge a následne sa prilepí na connectionLayer. Ďalej sa nastaví router, ktorý riadi cestu spojovacej čiary.

protected Widget attachPinWidget(MyNode node, MyPin pin) – táto metóda je volaná metódou addPin. Metóda je zodpovedná za tvorbu widgetu pre terminál simulačného komponentu a jeho pridanie na scénu. V programe DynaSim sa pod widgetom rozumie inštancia typu Pin_Widget, ktorý sa vytvorí na základe údajov z parametra funkcie a následne sa prilepí na SVG_Widget, ktorý je súčasťou Component_Widget-u. Pin_Widgetu sa tu pridá aj akcia na tvorbu spojovacích čiar.

protected void attachEdgeSourceAnchor(MyEdge edge, MyPin oldSourcePin, MyPin sourcePin) – táto metóda je volaná metódou setEdgeSource. Metóda má vo vizuálnej reprezentácii na starosti pripojenie nového zdrojového pinu ku hrane. Pomocou parametra edge sa nájde inštancia našej spojovacej čiary a pomocou parametra sourcePin sa nájde widget, ktorému tento pin patrí. Pre tento widget sa vytvorí kotva dátového typu Anchor. Nakoniec sa spojovacej čiare cez jej metódu setSourceAnchor kotva pridá. Tým sa upevní kotva spojovacej čiary v zdrojovom termináli.

protected void attachEdgeTargetAnchor(MyEdge edge, MyPin oldTargetPin, MyPin targetPin) – táto metóda je volaná metódou setEdgeTarget. Metóda má vo vizuálnej reprezentácii na starosti pripojenie nového cieľového pinu ku hrane a funguje obdobne ako predchádzajúca metóda.

Trieda DynaSimScene obsahuje aj ďalšie metódy, tie sú však využívané zväčša menu a toolbar tlačítkami a je im venovaný priestor v príslušnej kapitole.

SVG – Škálovateľná vektorová grafika

Jedná sa o špecifikáciu založenú na XML súborovom formáte, ktorá slúži na popis dvojrozmernej vektorovej grafiky. SVG je otvorený štandard, ktorý vytvorilo konzorcium World Wide Web. Myšlienka programu DynaSim je založená na popise komponentov XML a SVG súbormi, kde XML v sebe drží dátovú časť a obsahuje inicializačné hodnoty a popisy komponentu a SVG obsahuje popis grafickej reprezentácie daného komponentu. Zápis SVG dokumentu môže vyzerať napríklad takto:

 <?xml version="1.0" standalone="no"?>
 <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" 
   "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
 <svg width="160" height="160" viewBox="0 0 160 160" version="1.1"
      xmlns="http://www.w3.org/2000/svg">
   <rect x="1" y="1" width="100" height="100"
         fill="none" stroke="blue" stroke-width="2" />
   <circle cx="100" cy="100" r="50"
         fill="none" stroke="lightseagreen" stroke-width="5"/>
   <text x="15" y="40"
         font-family="Verdana" font-size="30" fill="red" >
     Hello
   </text>
 </svg>

Ako sa takáto časť kódu zobrazí v prehliadači Mozilla Firefox môžeme vidieť na obr.7. SVG dokáže natívne zobrazovať väčšina internetových prehliadačov. Ako možno vidieť z príkladu, jednotlivé geometrické tvary sa zadávajú cez xml tagy a ich poloha, rozmery a farby sú určené cez ich atribúty. Je dôležité v koreňovom svg tagu nastaviť veľkosť zobrazovanej plochy cez width a height.

Obr.7 Príklad SVG obrázku

Ďalšie podrobnejšie informácie o SVG je možno získať na adrese http://www.w3.org/TR/SVG/ .

Grafická časť simulačných komponentov

Simulačné komponenty sú zložené z widgetov, ktoré tvoria grafickú reprezentáciu komponentu a z nodes – dátových uzlov, ktoré v sebe uchovávajú informácie o vnútornom stave komponentu. Ako už bolo načrtnuté, widgety majú stromovú štruktúru, kde rodičovský widget môže mať niekoľko detských widgetov a tieto detské widgety opäť môžu mať svoje vlastné detské widgety atď. V prípade grafickej časti komponentu je koreňovým widgetom Component_Widget a jeho štrukúru možno vidieť na obr.8.

Obr.8 Architektúra grafickej časti komponentu

Component_Widget má 2 detské widgety a to SVG_Widget, zodpovedný za zobrazovanie grafiky načítanej z SVG súboru a LabelWidget, ktorý slúži na zobrazovanie mena komponentu. SVG_Widget môže mať ľubovoľný počet detských widgetov typu Pin_Widget, ktoré označujú miesta pre vstupy a výstupy z komponentu.

SVG_Widget

SVG_Widget je detskou triedou triedy Widget a je zodpovedný za parsovanie SVG súboru a vykreslenie objektu z načítaných údajov na scénu. Deklarácia konštruktora je nasledovná:

 public SVG_Widget (DynaSimScene scene, String svgfile, MyNode node)

Scene je scéna na ktorú sa daný widget prilepí, svgfile je cesta k SVG súboru z ktorého si widget načíta potrebné grafické údaje a node je dátový uzol, v ktorom sú uložené vnútorné stavy simulačného komponentu a jeho popis. V konštruktore sa volá metóda na parsovanie SVG súboru readSVG() a vytvorí sa popup menu pre tento komponent typu JPopupMenu. Následne sa do neho postupne pridajú jednotlivé položky typu JMenuItem. Na záver sa widgetu pridajú preddefinované akcie cez metódu getActions().addAction(WidgetAction action). Tieto akcie dodávajú widgetu funkcionalitu a majú vplyv aj na rodičovské widgety. V konštruktore je zadefinovaná akcia pre označenie widgetu, hover akcia, ktorá má hodnotu true ak sa kurzor myši nachádza nad týmto widgetom, akcia pre implementáciu popup menu a delete akcia, ktorá obsluhuje mazanie widgetov. Niektoré významné metódy tejto triedy:

private void readSVG (String svgFile) – táto metóda je určená na parsovanie SVG súboru, ku ktorému je zadaná cesta v jej parametri. Súbor sa parsuje pomocou SAXBuilder-u a získané hodnoty sa ukladajú medzi členské premenné inštancie.

protected Rectangle calculateClientArea () – je to abstraktná metóda triedy Widget. Vracia obdĺžnik do ktorého sa widget vykresľuje.

public void paintWidget () – je to abstraktná metóda triedy Widget a je zodpovedná za vykresľovanie widgetu. Metóda sa volá automaticky vždy, keď sa má widget prekresliť. Na vykresľovanie samotné sa používa AWT a údaje získné z parsovania SVG súboru metódou readSVG.

Pin_Widget

Pin_Widget je detskou triedou triedy Widget a slúži na grafické zobrazenie terminálu simulačného komponentu. Konštruktor vypadá následovne:

 public Pin_Widget (Scene scene, MyPin pin)

Scene je scéna na ktorú sa daný widget prilepí, pin je dátový uzol, v ktorom sú uložené informácie o pine ako meno a poradové číslo. Metóda paintWidget() vykresľuje červený kríž. Ten označuje miesto terminálu. Informácie o počte a polohe jednotlivých terminálov sú obsiahnuté v príslušnom XML súbore.

Component_Widget

Component_Widget je detskou triedou triedy Widget a zapúzdruje v sebe SVG_Widget a LabelWidget. Táto trieda má na starosti centrovanie svojich detských widgetov voči sebe. Konštruktor vypadá následovne:

 public Component_Widget (DynaSimScene scene, String svgfile, MyNode node, InstanceContent content, PaletteController controller)

Scene je scéna na ktorú sa daný widget prilepí, svgfile je cesta k SVG súboru a node je dátový uzol, v ktorom sú uložené vnútorné stavy simulačného komponentu a jeho popis. Tieto 3 parametre sa použíjú na vytvorenie inštancie SWG_Widget priamo v tomto konštruktore. Následne sa vytvorí inštancia LabelWidgetu s parametrom scene. LabelWidget je súčasťou Visual Library API a slúži na reprezentáciu textu na scéne. Tu slúži vlastne ako podpis komponentu a na scéne sa zobrazuje priemo pod grafickou časťou komponetu. V konštruktore sa ešte nastaví pomocou metódy setLayout centrovanie detských widgetov Component_Widget-u.

Zaujímavé metódy tejto triedy:

public final void setLabel(String label) – cez túto metódu sa mení meno komponentu. Je dôležité, aby sa nemenil len podpis samotný v LabelWidgete, ale treba zmenit meno komponentu aj v jeho dátovom uzle „node“.

public void notifyStateChanged(ObjectState previousState, ObjectState newState) – je to abstraktná metóda triedy Widget. Volá sa vždy, keď sa zmení stav widgetu. To môže znamenať, že bol widget napr. označený, alebo je nad ním kurzor myši, resp. má tento widget na sebe focus. V tomto prípade, ak je widget označený, zmení sa jeho farba a ďalej sa cez PropertyNode vytvorí property sheet. Akým spôsobom, to je rozobrané v príslušnej kapitole. Component_Widget sa na scénu typu DynaSimScene pridáva cez metódu attachNodeWidget(MyNode node). Tá je volaná po dropnutí vybraného komponentu z palety na editačnú plochu. Ako vypadá inštancia Component_Widget-u na editačnej ploche je vidno na obr.9.

Obr.9 Príklad pre Component_Widget

Spojovacie čiary

V grafovo orientovanom modeli sa uzly prepájajú pomocou hrán. To znamená, že jednotlivé komponenty musia byť prepojiteľné medzi sebou spojovacími čiarami. Treba brať na zreteľ, že sa komponenty neprepájajú priamo medzi sebou, ale každý komponent má svoje terminály. Tie môžu zastávať úlohu vstupu alebo výstupu pre tieto komponenty a práve pomocou nich sa prepájajú medzi sebou. Tiež treba brať do úvahy, že komponent môže mať viacero vstupov a tie môžu požadovať rôzne dátové typy aj v rámci jediného komponentu. Tak isto aj viacero výstupov z čoho každý môže predstavovať iný dátový typ.

MyConnectionWidget

Inštancie triedy ConnectionWidget majú za úlohu vykresľovať spojovacie čiary na scéne medzi dvoma bodmi. Tieto body sú určené inštanciami triedy Anchor – kotvami, ktoré sa dajú upevniť len v pinoch komponentu. ConnectionWidget je detskou triedou triedy Widget. Cesta spojenia je špecifikovaná kontrolnými bodmi, ktoré sa môžu pridať na čiaru automaticky podľa typu použitého routera, alebo ich môže užívateľ pridávať sám dvojklikom na spojovaciu čiaru. Vzhľadom na to, že chceme spojovacím čiaram pridať aj ďalšie vlastnosti ako popup menu, musíme triedu ConnectionWidget rozšíriť na MyConnectionWidget. Konštruktor triedy:

 public MyConnectionWidget(Scene scene, MyEdge edge)

scene - scéna na ktorú sa daný widget prilepí.

edge – inštancia triedy, ktorá v sebe uchováva dáta tejto spojovacej čiary, ako meno, zdrojový a cieľový pin.

V konštruktore sa vytvorí popup menu rovnakým spôsobom ako v SVG_Widgete. Ďalej sa nastavia tvary kontrolných bodov a kotiev cez metódy setControlPointShape a setTargetAnchorShape. Nakoniec cez metódy getActions().addAction pridáme tejto spojovacej čiare akcie, ktoré s ňou môžeme vykonávať a to sú akcia na mazanie, hover akcia, možnosť označenia, možnosť rekonekcie, vytvorenie popup menu, možnosť pridávania a odoberania kontrolných bodov dvojklikom na čiaru a nakoniec akciu na pohyb kontrolnými bodmi myšou. Spojovacie čiary sa na scénu typu DynaSimScene pripájajú na vrstvu connectionLayer cez metódu addEdge. Samotná spojovacia čiara sa však vytvára v metóde attachEdgeWidget, ktorá je volaná metódou addEdge.

MyConnectionProvider

Na to, kde sa môže spojovacia čiara ukotviť existujú určité pravidlá. Na ich určenie sa používa interface ConnectProvider. Preto si treba vytvoriť novú triedu:

 public class MyConnectionProvider implements ConnectProvider

V triede DynaSimScene sa vytvorí nová akcia pre widgety a má následovný tvar:

 private WidgetAction connectAction = ActionFactory.createConnectAction (interractionLayer, new MyConnectionProvider(this));

Táto akcia sa cez getActions().addAction pridá každému widgetu typu Pin_Widget. Tým máme zaručené, že sa môžu medzi sebou prepájať komponenty medzi sebou len cez piny. Potom môžeme po kliknutí na zdrojový pin myšou ťahať spojenie k cieľovému pinu a pustiť tlačítko myši. Tým sa vytvorí spojenie medzi pinmi.

Niektoré metódy zdedené cez implementáciu, ktoré určujú pravidlá pre prepojenie:

public boolean isSourceWidget(Widget sourceWidget) – Metóda, ktorá zisťuje, či daný widget môže byť zdrojom pre ukotvenie spojovacej čiary. Ak je widget inštanciou triedy Pin_Widget, vyhovuje podmienke.

public ConnectorState isTargetWidget (Widget sourceWidget, Widget targetWidget) – Metóda, ktorá zisťuje či daný widget môže byť zdrojom pre ukotvenie spojovacej čiary. Ak je widget inštanciou triedy Pin_Widget, vráti sa hodnota ConnectorState.ACCEPT, tým pádom je možné vytvoriť prepojenie medzi zdrojovým a cieľovým widgetom.

public void createConnection(Widget sourceWidget, Widget targetWidget) – Metóda na vytvorenie spojenia medzi dvoma terminálmi typu Pin_Widget. Podmienkou pre prepojenie dvoch terminálov je to, že jeden terminál je vstup a druhy výstup. Táto informácia je uložená v inštancii triedy MyPin, ktorá sa použila na tvorbu pinu. Ak je táto podmienka splnená vytvorí sa spojenie medzi zdrojom a cieľom.

MyReconnectProvider

Užívateľovi sa môže stať, že prepojí medzi sebou nesprávne piny. Aby napravil chybu, musel by zmazať celé spojenie a vytvoriť ho znovu. Avšak vďaka akcii na opätovné prepojenie mu stačí chytiť koncový bod spojovacej čiary, zdrojový alebo cieľový a spojiť ho s iným terminálom, čím môže opraviť svoju chybu, bez nutnosti mazania spojenia a tvorby nového. Na to slúži interface ConnectionProvider, ktorý je implementovaný nasledovne:

 public class MyReconnectProvider implements ReconnectProvider

Cez getActions().addAction sa cez ActionFactory vytvorí nová reconnect akcia s použitím tohto provideru. Takto vytvorená akcia je volaná v konštruktore triedy MyConnectionWidget. To znamená, že každá spojovacia čiara sa dá znovu prepojiť podľa stanovených podmienok provideru.

Niektoré zdedené triedy z implementácie:

public boolean isSourceReconnectable(ConnectionWidget connectionWidget) – Metóda, ktorá zisťuje, či je zdrojový widget prepojiteľný. Metóda vráti true hodnotu ak je zdrojový widget typu Pin_Widget.

public boolean isTargetReconnectable(ConnectionWidget connectionWidget) – Metóda, ktorá zisťuje, či je cieľový widget prepojiteľný. Metóda vráti true hodnotu ak je cieľový widget typu Pin_Widget.

public void reconnectingStarted (ConnectionWidget connectionWidget, boolean reconnectingSource) – V tejto metóde sa riešia akcie, ktoré sa majú vykonať, ak sa začne s novým prepájaním. V tomto prípade chceme aby sa na mieste kde bola pôvodne spojovacia čiara spojená s Pin_Widgetom znova objavil červený kríž, reprezentujúci terminál, keďže už viac nie je čiara s týmto terminálom spojená. public void reconnectingFinished (ConnectionWidget connectionWidget, boolean reconnectingSource) – Metóda, ktorá sa vykoná po dokončení opätovného prepojenia rozpojeného konca spojovacej čiary s náhradným terminálom. Chceme, aby červený kríž reprezentujúci tento terminál zmizol.

public ConnectorState isReplacementWidget (ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource) – Metóda, ktorá zisťuje či daný widget môže byť náhradným pinom. Ak je typu Pin_Widget, nemá hodnotu null a je vstup ak bol aj predošlý pin vstup, resp výstup ak bol rpedošlý pin vystup, metóda vráti hodnotu ConnectorState.ACCEPT. Potom sa môže zavolať nasledujúca metóda.

public void reconnect (ConnectionWidget connectionWidget, Widget replacementWidget, boolean reconnectingSource) – V tejto metóde sa zmení zdrojový, respektíve cieľový pin, za ten, ktorý sme prepájali.