Tämä luku sisältää lähinnä vain oppikirjan ja vanhan www-materiaalin luvun 6 joitakin vanhentuneita ohjelmaesimerkkejä nykyaikaistettuna Swing-versioiksi. Tarkoituksena ei ole vielä oppia itse tekemään graafisia käyttöliittymiä ("graphical user interface", GUI), tarkoituksena on vain saada ideatason käsitys, miten Javassa graafisia näkymiä luodaan ja miten ohjelmoidaan tapahtumiin reagointia.
Sovellus on main-metodilla käynnistettävä Java-ohjelma. Graafiseen käyttöliittymään (GUI) perustuvan sovelluksen päämetodi rakentaa käyttöliittymän näkymän.
Pieni esimerkki: (HoiGSovellus0.java)
import javax.swing.*; public class HoiGSovellus0 extends JFrame { public HoiGSovellus0() { // konstruktori! JTextField hoi = new JTextField("Hoi maailma!"); add(hoi); // lisätään kenttä luokan näkymään } public static void main(String args[]) { HoiGSovellus0 ikkuna = new HoiGSovellus0(); ikkuna.setTitle("Hoi 0!"); ikkuna.pack(); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setVisible(true); } }
Graafista käyttöliittymää käyttävän sovelluksen luokka on luokan JFrame aliluokka. JFrame määrittelee ikkunan, jolla on reunat ja otsikkopalkki. Se tarjoaa yliluokkineen välineet mm. ikkunoiden käsittelyyn. Pääohjelma luo ilmentymän omasta luokastaan HoiGSovellus0. Pääohjelma myös asettaa sovelluksen ikkunaan otsikkorivin (setTitle), antaa luvan asettaa ikkunan komponentit oletuskokoonsa (pack) ja asettaa ikkunan näkyväksi (setVisible).
public void actionPerformed(ActionEvent e)
public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); ...
import javax.swing.*; import java.awt.event.*; public class HoiGSovellus2 extends JFrame implements ActionListener { private JTextField hoi = new JTextField("Paina enter!"); private int elkm = 0; public HoiGSovellus2() { add(hoi); hoi.addActionListener(this); } public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); if (aiheuttaja == hoi) { ++elkm; hoi.setText(elkm+". enter"); } } public static void main(String args[]) { HoiGSovellus2 ikkuna = new HoiGSovellus2(); ikkuna.setTitle("Hoi 2!"); ikkuna.pack(); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setVisible(true); } }
Laaditaan muutamia graafisen käyttöliittymän komponentteja (luokat JButton, JTextField, JTextArea) käyttäen sovellus, joka osaa reagoida nappuloiden painamiseen ja tekstikenttään kirjoittamiseen. Komponentteja on monia, monia muitakin: ks. Java-API:n pakkaus javax.swing.
Näihin luokkiin sekä esimerkkiin tutustuminen jätetään oman harrastuksen varaan: Suorita ohjelmaa, tutki luokkien kuvailuja, kokeile ohjelman muuttamista, ...
Esimerkki (Nappuloita.java)
import javax.swing.*; import java.awt.event.*; import java.awt.FlowLayout; public class Nappuloita extends JFrame implements ActionListener { // muuttujia ikkunan komponenteille: private JButton nappula, tyhjaa, loppu; private JTextField tekstiKentta1, tekstiKentta2; private JTextArea tekstiAlue; public Nappuloita() { // konstruktori setLayout(new FlowLayout()); // "kun rivi loppuu, // uusi alkaa" // luodaan komponenttioliot: nappula = new JButton("nappula"); tyhjaa = new JButton("tyhjää"); loppu = new JButton("loppu"); tekstiKentta1 = new JTextField(10); tekstiKentta2 = new JTextField(15); tekstiKentta2.setEditable(false); tekstiAlue = new JTextArea(5,14); // lisätään komponenttioliot ikkunaan: add(nappula); add(tyhjaa); add(loppu); add(tekstiKentta1); add(tekstiKentta2); add(tekstiAlue); // asetetaan kuuntelijat: nappula.addActionListener(this); tyhjaa.addActionListener(this); loppu.addActionListener(this); tekstiKentta1.addActionListener(this); } // tapahtumien käsittely: public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); if (aiheuttaja == nappula) { tekstiKentta2.setText("Painoit nappulaa!"); tekstiAlue.append("Painoit nappulaa!\n"); } else if (aiheuttaja == tyhjaa) { tekstiKentta1.setText(""); tekstiKentta2.setText(""); tekstiAlue.setText(""); } else if (aiheuttaja == loppu) System.exit(0); // lopetetaan else if (aiheuttaja == tekstiKentta1) tekstiAlue.append(tekstiKentta1.getText()+"\n"); } public static void main(String args[]) { Nappuloita ikkuna = new Nappuloita(); ikkuna.setSize(300, 200); ikkuna.setTitle("Nappuloita.java"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setVisible(true); } }
Käytössä on mm. seuraavat asemointitavat:
setLayout(new BorderLayout()); add("North", nappula1); add("South", nappula2); add("East", nappula3); add("West", nappula4); add("Center", nappula5);
setLayout(new GridLayout(2,3)); // rivejä, sarakkeita add(nappula1); add(nappula2); add(nappula3); add(nappula4); add(nappula5); add(nappula6);
Sovelluksen ikkuna voidaan rakentaa "ali-ikkunoista", paneeleista, joilla jokaisella voi olla oma asemointitapansa. Ja koko ikkunalla omansa.
Ja toki paneeleilla voi olla omia alipaneeleitaan...
Esimerkki esittelee paneelien ja muutamien layout-asetusten käyttöä. Esimerkkiin perehtyminen jää oman harrastuksen varaan. (Paneeleja.java)
import java.awt.BorderLayout; import java.awt.FlowLayout; import java.awt.GridLayout; import javax.swing.*; import java.awt.event.*; public class Paneeleja extends JFrame { private JButton b111 = new JButton("Nappula 111"); private JTextField t112 = new JTextField("Kentta 112"); private JLabel l121 = new JLabel("Teksti 121"); private JButton b122 = new JButton("Nappula 122"); private JButton b21 = new JButton("Nappula 21"); private JTextField t22 = new JTextField("Kentta 22"); private JButton b23 = new JButton("Nappula 23"); private JButton b31 = new JButton("31"); private JButton b32 = new JButton("32"); private JButton b33 = new JButton("33"); private JButton b34 = new JButton("34"); private JButton b35 = new JButton("35"); private JButton b36 = new JButton("36"); private JButton b37 = new JButton("37"); private JButton b38 = new JButton("38"); private JButton b41 = new JButton("41"); private JButton b42 = new JButton("42"); public Paneeleja() { JPanel p11 = new JPanel(new BorderLayout()); p11.add("North", b111); p11.add("South", t112); JPanel p12 = new JPanel(new BorderLayout()); p12.add("North", l121); p12.add("South", b122); JPanel p1 = new JPanel(new FlowLayout()); p1.add(p11); p1.add(p12); JPanel p2 = new JPanel(new FlowLayout()); p2.add(b21); p2.add(t22); p2.add(b23); JPanel p3 = new JPanel(new GridLayout(2,4)); p3.add(b31); p3.add(b32); p3.add(b33); p3.add(b34); p3.add(b35); p3.add(b36); p3.add(b37); p3.add(b38); JPanel pp1 = new JPanel(new BorderLayout()); pp1.add("North", p1); pp1.add("Center", p2); pp1.add("South", p3); JPanel pp2 = new JPanel(new GridLayout(2,1)); pp2.add(b41); pp2.add(b42); this.setLayout(new BorderLayout()); this.add("Center", pp1); this.add("East", pp2); } public static void main(String args[]) { Paneeleja ikkuna = new Paneeleja(); ikkuna.setTitle("Paneeleja"); ikkuna.pack(); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setVisible(true); } }
Näissä esimerkeissa piirustusalusta toteutetaan luokan JPanel aliluokkana. (Esimerkeissä aliluokka on kirjoitettu samaan käännösyksikköön varsinaisen ohjelman luokan kanssa.)
Oma piirustusalusta perii JPanel-luokalta mm. metodin protected void paintComponent(Graphics g). (JPanel on puolestaan itse perinyt tuon metodin omalta yliluokaltaan JComponent.) Ohjelman laatija korvaa (overrides) tämän perityn metodin sellaisella, joka piirtää komponenttiin halutun sisällön.
Järjestelmä kutsuu paintComponent(Graphics g)-metodia aina, kun näkymä pitää piirtää: ohjelman suorituksen alussa, kun näkymä on ollut peitettynä, kun se palautetaan kuvakkeesta ikkunaksi, ... Kutsuva järjestelmä antaa metodille parametrina Graphics-luokan ilmentymän, joka juuri on ohjelman ikkuna. (Kyseessä on itse asiassa Graphics-luokan jonkin aliluokan ilmentymä, koska Graphics on itse abstrakti!)
Luokassa Graphics on määritelty joukko metodeita sovelman ikkunassa näytettävän kuvan muodostamiseen ja käsittelyyn:
Ikkunan koordinaatit ilmaistaan kuvapisteinä (ns. pikseleinä) siten, että ikkunan vasemman yläkulman koordinaatit ovat (0,0), yläkulman alapuolinen pikseli on (0,1), yläkulman viereinen pikseli on (1,0), jne.
Huom: Piirrellä voidaan itse asiassa melkein minkä vain komponentin pinnalle! JComponent-luokalle luetellaan "tunnettuina aliluokkina" seuraavat: AbstractButton, BasicInternalFrameTitlePane, Box, Box.Filler, JColorChooser, JComboBox, JFileChooser, JInternalFrame, JInternalFrame.JDesktopIcon, JLabel, JLayeredPane, JList, JMenuBar, JOptionPane, JPanel, JPopupMenu, JProgressBar, JRootPane, JScrollBar, JScrollPane, JSeparator, JSlider, JSpinner, JSplitPane, JTabbedPane, JTable, JTableHeader, JTextComponent, JToolBar, JToolTip, JTree, JViewport. Näillä luokillakin on aliluokkia. Esimerkiksi AbstractButton-luokan JButton-aliluokan ilmentymät voivat joutua piirtelyn kohteeksi!
Tekstien kirjoittamista ikkunaan: (Kirjoittelua.java)
import javax.swing.*; import java.awt.Graphics; public class Kirjoittelua extends JFrame { public Kirjoittelua() { this.add(new KirjPaneeli()); } public static void main(String[] args) { Kirjoittelua ikkuna = new Kirjoittelua(); ikkuna.setTitle("Kirjoittelua"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(350, 350); ikkuna.setVisible(true); } } class KirjPaneeli extends JPanel { public void paintComponent(Graphics g) { for (int i=0; i<299; i+=10) g.drawString("Böö "+i, i, i); } }
Luokasta Font löytyy monenlaisia kirjasintyyppejä. Esimerkki yhdestä kirjasintyypistä ja luokan Font käytöstä (Kaunokirjoittelua.java)
import javax.swing.*; import java.awt.Graphics; import java.awt.Font; public class Kaunokirjoittelua extends JFrame { public Kaunokirjoittelua() { add(new KaunoPaneeli()); } public static void main(String[] args) { Kaunokirjoittelua ikkuna = new Kaunokirjoittelua(); ikkuna.setTitle("Kaunokirjoittelua"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(350, 350); ikkuna.setVisible(true); } } class KaunoPaneeli extends JPanel { public void paintComponent(Graphics g) { Font kirjasin = new Font("TimesRoman", Font.BOLD+Font.ITALIC, 14); g.setFont(kirjasin); for (int i=0; i<299; i+=10) g.drawString("Böö "+i, i, i); } }
Laaditaan sitten pikku ohjelma, jolla voi tutkia, milloin ja miten usein paint-metodi suoritetaan. (Painttilaskuri.java)
import javax.swing.*; import java.awt.Graphics; public class Painttilaskuri extends JFrame { public Painttilaskuri() { add(new PainttiPaneeli()); } public static void main(String[] args) { Painttilaskuri ikkuna = new Painttilaskuri(); ikkuna.setTitle("Painttilaskuri"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(300, 100); ikkuna.setVisible(true); } } class PainttiPaneeli extends JPanel { private int lkm=0; public void paintComponent(Graphics g) { g.drawString("Maalauskerta numero: "+lkm, 10,25); ++lkm; } }
Graphics-luokassa on määritelty joukko piirtämisvälineitä. Metodit on määritelty abstrakteina. Graphicsin aliluokat siis itse asiassa toteuttavat nuo välineet omien vaatimustensa mukaisina.
Esimerkiksi janoja kuva-alalle piirretään metodilla
drawLine(alkuX, alkuY, loppuX, loppuY)
Seuraava esimerkkiohjelma arpoo janan paikan (Arpaviiva.java)
import javax.swing.*; import java.awt.Graphics; public class Arpaviiva extends JFrame { public Arpaviiva() { add(new ArpaPaneeli()); } public static void main(String[] args) { Arpaviiva ikkuna = new Arpaviiva(); ikkuna.setTitle("Arpaviiva"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(350, 250); ikkuna.setVisible(true); } } class ArpaPaneeli extends JPanel { public void paintComponent(Graphics g) { System.out.println("maalataan"); int alkux,alkuy, loppux, loppuy; alkux = (int)(Math.random()*300); alkuy = (int)(Math.random()*200); loppux = (int)(Math.random()*300); loppuy = (int)(Math.random()*200); g.drawRect(0,0,299,199); g.drawLine(alkux,alkuy,loppux,loppuy); g.drawString("("+alkux+","+alkuy+"),("+loppux+","+loppuy+")",1,12); } }
Luokassa Color on värienkäsittelyvälineitä. Siellä on myös vakiot mm. seuraaville väreille: black, blue, cyan, darkGray, gray, green, lightGray, magenta, orange, pink, red, white ja yellow.
Muita värejä voi määritellä ns. RGB-lukukolmikkoina, jotka annetaan Color-konstruktorille. Valkoinen on Color(255,255,255), musta Color(0,0,0), puhdas punainen Color(255,0,0), ...
Esimerkki: Kokeillaan RGB-määriteltyjä värejä. Käydään läpi R- ja G-arvoja ja B-komponentti arvotaan joka maalauskerralla. (OmatVarit.java)
import javax.swing.*; import java.awt.Graphics; import java.awt.Color; public class OmatVarit extends JFrame { public OmatVarit() { add(new VariPaneeli()); } public static void main(String[] args) { OmatVarit ikkuna = new OmatVarit(); ikkuna.setTitle("OmatVarit"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(285, 310); ikkuna.setVisible(true); } } class VariPaneeli extends JPanel { public void paintComponent(Graphics kuva) { kuva.drawRect(0,0, 277, 277); setBackground(Color.white); int r,g,b; // RGB värikolmikko b = (int)(Math.random()*256); for (r=0; r<256; r+=3) for (g=0; g<256; g+=3) { Color vari = new Color(255-r,255-g,b); kuva.setColor(vari); kuva.fillRect(10+r,10+g, 3,3); } } }
Esimerkissä Maalaus.java luodaan pari käyttöliittymäelementtiä, asemoidaan niitä ja reagoidaan napin painallukseen:
public class Maalaus extends JFrame implements ActionListener { private JButton nappula = new JButton("Uusi maalaus!"); private VariPaneeli kuva = new VariPaneeli(); public Maalaus() { setLayout(new BorderLayout()); add("Center", kuva); add("South", nappula); nappula.addActionListener(this); kuva.setOpaque(false); // estää "ylimääräisen" buttonin varjon nappula.setOpaque(false); } public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); if (aiheuttaja == nappula) { kuva.repaint(); } } public static void main(String[] args) { Maalaus ikkuna = new Maalaus(); ikkuna.setTitle("Art_o"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(285, 330); ikkuna.setVisible(true); } } class VariPaneeli extends JPanel { public void paintComponent(Graphics kuva) { ... } }VariPaneeli-luokan toteuksella sitten onkin jo minun taiteellinen copyrightini... ;-)
Koska luokka Reagointeja lupaa toteuttaa kaikki mainitut rajapintaluokat, sen on annettava toteutus niiden kaikille metodeille! Metodit vain tulostavat ilmoituksen itsestään siihen ikkunaan, josta sovellus on käynnistetty - tuttuun tapaan metodilla System.out.println(...)! (Reagointeja.java)
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Reagointeja extends JFrame implements ComponentListener, MouseMotionListener, MouseListener, KeyListener, FocusListener { private TextArea alue = new TextArea ("\n Reagoiva alue:\n\n" + " Kokeile erilaisia\n" + " tapahtumia ja seuraa\n" + " systeemin ikkunaa!", 10, 20, TextArea.SCROLLBARS_NONE); private int laskuri = 0; private Label laskuriOtsikko = new Label("", Label.CENTER); public Reagointeja() { alue.setEditable(false); add ("Center", alue); add ("South", laskuriOtsikko); addComponentListener(this); alue.addFocusListener(this); alue.addKeyListener(this); alue.addMouseListener(this); alue.addMouseMotionListener(this); } private void kirjaaTapahtuma(AWTEvent tapahtuma) { laskuri++; laskuriOtsikko.setText("Tapahtumia: " + laskuri + " kpl"); System.out.println(tapahtuma.toString().substring(15)); // ei tulosteta kaikille yhteistä alkua "java.awt.event."! } public void componentMoved(ComponentEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void componentHidden(ComponentEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void componentResized(ComponentEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void componentShown(ComponentEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseDragged(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseMoved(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mousePressed(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseReleased(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseEntered(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseExited(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void mouseClicked(MouseEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void keyPressed(KeyEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void keyReleased(KeyEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void keyTyped(KeyEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void focusGained(FocusEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public void focusLost(FocusEvent tapahtuma) { kirjaaTapahtuma(tapahtuma); } public static void main(String args[]) { Reagointeja ikkuna = new Reagointeja(); ikkuna.setTitle("Tapahtuu ..."); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.setSize(200, 200); ikkuna.setVisible(true); } }
Ja tässä tulee todellinen hyötyohjelma! (HiiriTuliJaMeni.java)
import javax.swing.*; import java.awt.event.*; import java.awt.font.*; // vain fonttia varten import java.awt.*; // " public class HiiriTuliJaMeni extends JFrame implements MouseListener { private JTextArea alue = new JTextArea (1, 11); private int puremia = 0; public HiiriTuliJaMeni() { alue.setEditable(false); add ("Center", alue); alue.setFont(new Font("Serif", Font.BOLD, 20)); alue.setText(" Hiirtä ei ole näkynyt."); alue.addMouseListener(this); // luokka ITSE toteuttaa // kuuntelijan } public void mouseEntered(MouseEvent tapahtuma) { alue.setText(" Hiiri tuli!"); } public void mouseExited(MouseEvent tapahtuma) { alue.setText(" Hiiri meni!"); } public void mouseClicked(MouseEvent tapahtuma) { ++puremia; alue.setText(" Hiiri puri! ("+puremia+". kerta)"); } // KAIKKI luvatut metodit on toteutettava (edes tyhjinä): public void mousePressed(MouseEvent tapahtuma) { } public void mouseReleased(MouseEvent tapahtuma) { } public static void main(String[] args) { HiiriTuliJaMeni ikkuna = new HiiriTuliJaMeni(); ikkuna.setTitle("Hiirielämää"); ikkuna.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); ikkuna.pack(); ikkuna.setVisible(true); } }
Graafisen sovelluksen muuntaminen sovelmaksi on käytännössä (lähes) täysin mekaanista. Tälle kurssille sovelmista mahtuu kuitenkin vain pieni esimerkki. Kurssin vanhan materiaalin sovelmista kertovat osat saattavat olla vieläkin hyödyllisiä.
Edellä nähtiin esimerkkinä graafisesta sovelluksesta (application) luokka Maalaus.java. Sovelmaksi (applet) tuon ohjelman voi muuttaa yksinkertaisesti kopioimalla konstruktorin algoritmi init()-metodin algoritmiksi. Pääohjelma poistetaan: (BMaalaus.java)
import javax.swing.*; import java.awt.Graphics; import java.awt.*; import java.awt.event.*; public class BMaalaus extends JApplet implements ActionListener { private JButton nappula = new JButton("Uusi maalaus!"); private BVariPaneeli kuva = new BVariPaneeli(); public void init() { setLayout(new BorderLayout()); add("Center", kuva); add("South", nappula); nappula.addActionListener(this); kuva.setOpaque(false); // estää "ylimääräisen" buttonin varjon nappula.setOpaque(false); } public void actionPerformed(ActionEvent tapahtuma) { Object aiheuttaja = tapahtuma.getSource(); if (aiheuttaja == nappula) { kuva.repaint(); } } /* Pääohjelma on poistettu. */ } class BVariPaneeli extends JPanel { public void paintComponent(Graphics kuva) { ... } }
Www-sivut toteutetaan ns. html-kielellä, jonka perusidea on määritellä näytettävien sivujen looginen rakenne ns. "tägeillä" (tag), jotka ilmaisevat esimerkiksi kappalerajaa, otsikkoa, sisennettyä luetteloa, yms. (html-kielestä: ks. esimerkiksi Jukka Korpelan Web-julkaisemisen opas).
Java-sovelma liitetään www-sivulle tägillä "<APPLET CODE..." Tällä sivulla näkyvä sovelma on liitetty sivuun kirjoittamalla tämän sivun html-tiedostoon rivit:
<APPLET CODE="BMaalaus.class" WIDTH=258 HEIGHT=280> Selain ei ymmärrä Javaa! </APPLET>Samaan tapaan kuin kaikelle maailmalle annetaan html-tiedoston lukuoikeus, myös sovelman toteuttaville class-tiedostoille on nimenomaan tämä lukuoikeus annettava. Suoritusoikeutta ei tarvita, koska sovelmat suoritetaan käyttäjän oman koneen selaimen Java-tulkilla! Tässä esimerkissä tiedoston BMaalaus.class lisäksi lukuoikeus on pitänyt antaa apuluokalle BVariPaneeli.class. (Sovelmaversiossa on muutettu myös hieman sovelluksen piirtoalaa BVariPaneeli-luokassa.)
Ja "viritelty versio kansainväliseen käyttöön" ...
Takaisin pääsisällysluetteloon.