From ced4f0dbf8ce06d78a08677aacf37468bde9b0cd Mon Sep 17 00:00:00 2001
From: vesal <vesal@jyu.fi>
Date: Fri, 24 Mar 2023 11:29:55 +0200
Subject: [PATCH] Tietue ja sen avulla dialogit

---
 .../live21/src/fxKerho/JasenDialogView.fxml   |   2 +-
 luennot/live22/kelmit/harrastukset.dat        |   1 +
 .../fxKerho/HarrastusDialogController.java    | 188 ------------------
 .../src/fxKerho/HarrastusDialogView.fxml      |  65 ------
 .../src/fxKerho/KerhoGUIController.java       |  13 +-
 ...oller.java => TietueDialogController.java} | 110 +++++-----
 ...nDialogView.fxml => TietueDialogView.fxml} |  14 +-
 luennot/live22/src/kanta/Tietue.java          | 116 +++++++++++
 luennot/live22/src/kerho/Harrastus.java       |   9 +-
 luennot/live22/src/kerho/Jasen.java           |   8 +-
 10 files changed, 202 insertions(+), 324 deletions(-)
 delete mode 100644 luennot/live22/src/fxKerho/HarrastusDialogController.java
 delete mode 100644 luennot/live22/src/fxKerho/HarrastusDialogView.fxml
 rename luennot/live22/src/fxKerho/{JasenDialogController.java => TietueDialogController.java} (52%)
 rename luennot/live22/src/fxKerho/{JasenDialogView.fxml => TietueDialogView.fxml} (85%)
 create mode 100644 luennot/live22/src/kanta/Tietue.java

diff --git a/luennot/live21/src/fxKerho/JasenDialogView.fxml b/luennot/live21/src/fxKerho/JasenDialogView.fxml
index 7b97af5..93c8a65 100644
--- a/luennot/live21/src/fxKerho/JasenDialogView.fxml
+++ b/luennot/live21/src/fxKerho/JasenDialogView.fxml
@@ -7,7 +7,7 @@
 <?import javafx.scene.text.*?>
 <?import javafx.scene.layout.BorderPane?>
 
-<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxKerho.JasenDialogController">
+<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxKerho.TietueDialogController">
    <bottom>
       <VBox BorderPane.alignment="CENTER">
          <children>
diff --git a/luennot/live22/kelmit/harrastukset.dat b/luennot/live22/kelmit/harrastukset.dat
index 3bd59d7..a4a7ce0 100644
--- a/luennot/live22/kelmit/harrastukset.dat
+++ b/luennot/live22/kelmit/harrastukset.dat
@@ -17,3 +17,4 @@
 17|8|Pitsin nypläys|1951|26
 18|8|Pitsin nypläys|1963|19
 19|1|Uinti|2023|1
+20|2|Uinti|2022|2
diff --git a/luennot/live22/src/fxKerho/HarrastusDialogController.java b/luennot/live22/src/fxKerho/HarrastusDialogController.java
deleted file mode 100644
index 68a6a9d..0000000
--- a/luennot/live22/src/fxKerho/HarrastusDialogController.java
+++ /dev/null
@@ -1,188 +0,0 @@
-package fxKerho;
-
-import java.net.URL;
-import java.util.ResourceBundle;
-
-import fi.jyu.mit.fxgui.Dialogs;
-import fi.jyu.mit.fxgui.ModalController;
-import fi.jyu.mit.fxgui.ModalControllerInterface;
-import fi.jyu.mit.ohj2.Mjonot;
-import javafx.fxml.Initializable;
-import javafx.stage.Stage;
-import kerho.Harrastus;
-import javafx.fxml.FXML;
-import javafx.scene.control.TextField;
-import javafx.scene.Node;
-import javafx.scene.control.Label;
-import javafx.scene.layout.GridPane;
-import javafx.scene.control.ScrollPane;
-
-/**
- * Harrastuksen käsittely
- * @author vesal
- * @version 26.3.2022
- */
-public class HarrastusDialogController implements ModalControllerInterface<Harrastus>, Initializable {
-
-    @FXML TextField editNimi;
-    @FXML TextField editHetu;
-    @FXML TextField editKatuosoite;
-    @FXML TextField editPostinumero;
-    @FXML Label labelVirhe;
-    @FXML GridPane gridHarrastus;
-    @FXML ScrollPane panelHarrastus;
-
-
-    @Override
-    public void initialize(URL arg0, ResourceBundle arg1) {
-        alusta();
-    }
-
-    @Override
-    public Harrastus getResult() {
-        return harrastusKohdalla;
-    }
-
-    @Override
-    public void handleShown() {
-        kentta = Math.max(apuharrastus.ekaKentta(), Math.min(kentta, apuharrastus.getKenttia()-1)); 
-        edits[kentta].requestFocus(); 
-    }
-
-    @Override
-    public void setDefault(Harrastus oletus) {
-        this.harrastusKohdalla = oletus;
-        naytaHarrastus(edits, harrastusKohdalla);
-    }
-
-    @FXML private void handleOK() {
-        if ( harrastusKohdalla != null && harrastusKohdalla.anna(apuharrastus.ekaKentta()).trim().equals("") ) {
-            naytaVirhe("Nimi ei saa olla tyhjä");
-            return;
-        }
-        ModalController.closeStage(labelVirhe);
-    }
-
-    @FXML private void handleCancel() {
-        harrastusKohdalla = null;
-        ModalController.closeStage(labelVirhe);
-    }
-
-    // =========================================================================
-    
-    private Harrastus harrastusKohdalla;
-    private TextField[] edits;
-    private static Harrastus apuharrastus = new Harrastus();
-    private int kentta = 0;  // mikä kenttä aktivoidaan kun dialogi aukaistaan
-    
-    
-    /**
-     * Palautetaan komponentin id:stä saatava luku
-     * @param obj tutkittava komponentti
-     * @param oletus mikä arvo jos id ei ole kunnollinen
-     * @return komponentin id lukuna 
-     */
-    public static int getFieldId(Object obj, int oletus) {
-        if ( !( obj instanceof Node)) return oletus;
-        Node node = (Node)obj;
-        return Mjonot.erotaInt(node.getId().substring(1),oletus);
-    }
- 
-    /**
-     * Luodaan GridPaneen harrastuksen tiedot
-     * @param gridHarrastus mihin tiedot luodaan
-     * @return luodut tekstikentät
-     */
-    public static TextField[] luoKentat(GridPane gridHarrastus) {
-        gridHarrastus.getChildren().clear();
-        TextField[] edits = new TextField[apuharrastus.getKenttia()];
-        
-        for (int i=0, k = apuharrastus.ekaKentta(); k < apuharrastus.getKenttia(); k++, i++) {
-            Label label = new Label(apuharrastus.getKysymys(k));
-            gridHarrastus.add(label, 0, i);
-            TextField edit = new TextField();
-            edits[k] = edit;
-            edit.setId("e"+k);
-            gridHarrastus.add(edit, 1, i);
-        }
-        return edits;
-    }
-    
-    
-    private void alusta() {
-        edits = luoKentat(gridHarrastus);
-        for (TextField edit : edits)
-            if ( edit != null )
-                edit.setOnKeyReleased( e -> kasitteleMuutosHarrastukseen((TextField)(e.getSource())));
-        panelHarrastus.setFitToHeight(true);
-    }
-
-    
-    private void setKentta(int kentta) {
-       this.kentta = kentta; 
-    }
-    
-    
-    /**
-     * Käsitellään harrastukseen tullut muutos
-     * @param edit muuttunut kenttä
-     */
-    private void kasitteleMuutosHarrastukseen(TextField edit) {
-        if (harrastusKohdalla == null) return;
-        String s = edit.getText();
-        int k = getFieldId(edit,apuharrastus.ekaKentta());
-        String virhe = harrastusKohdalla.aseta(k, s);
-        
-        if (virhe != null) {
-            Dialogs.setToolTipText(edit,virhe); 
-            edit.getStyleClass().add("virhe");
-            naytaVirhe(virhe);
-        } else {
-            Dialogs.setToolTipText(edit,""); 
-            edit.getStyleClass().removeAll("virhe");
-            naytaVirhe(virhe);
-        }
-    }
-    
-    
-    /**
-     * Näytetään harrastuksen tiedot TextField komponentteihin
-     * @param edits taulukko TextFieldeistä johon näytetään
-     * @param harrastus näytettävä harrastus
-     */
-    public static void naytaHarrastus(TextField[] edits, Harrastus harrastus) {
-        if (harrastus == null) return;
-        for (int k = harrastus.ekaKentta(); k < harrastus.getKenttia(); k++) {
-            edits[k].setText(harrastus.anna(k));
-        }
-    }
-    
-  
-    private void naytaVirhe(String virhe) {
-        if ( virhe == null || virhe.isEmpty() ) {
-            labelVirhe.setText("");
-            labelVirhe.getStyleClass().removeAll("virhe");
-            return;
-        }
-        labelVirhe.setText(virhe);
-        labelVirhe.getStyleClass().add("virhe");
-    }
-    
-    
-    /**
-     * Luodaan harrastuksen kysymisdialogi ja palautetaan sama tietue muutettuna tai null
-     * TODO: korjattava toimimaan
-     * @param modalityStage mille ollaan modaalisia, null = sovellukselle
-     * @param oletus mitä dataan näytetään oletuksena
-     * @param kentta mikä kenttä saa fokuksen kun näytetään
-     * @return null jos painetaan Cancel, muuten täytetty tietue
-     */
-    public static Harrastus kysyHarrastus(Stage modalityStage, Harrastus oletus, int kentta) {
-        return ModalController.<Harrastus, HarrastusDialogController>showModal(
-                  HarrastusDialogController.class.getResource("HarrastusDialogView.fxml"), 
-                  "Harrastus", 
-                  modalityStage, oletus,
-                  ctrl -> ctrl.setKentta(kentta));
-    }
-
-}
diff --git a/luennot/live22/src/fxKerho/HarrastusDialogView.fxml b/luennot/live22/src/fxKerho/HarrastusDialogView.fxml
deleted file mode 100644
index 8f8d87a..0000000
--- a/luennot/live22/src/fxKerho/HarrastusDialogView.fxml
+++ /dev/null
@@ -1,65 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<?import javafx.geometry.Insets?>
-<?import javafx.scene.control.Button?>
-<?import javafx.scene.control.ButtonBar?>
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.control.ScrollPane?>
-<?import javafx.scene.control.TextField?>
-<?import javafx.scene.layout.BorderPane?>
-<?import javafx.scene.layout.ColumnConstraints?>
-<?import javafx.scene.layout.GridPane?>
-<?import javafx.scene.layout.HBox?>
-<?import javafx.scene.layout.RowConstraints?>
-<?import javafx.scene.layout.VBox?>
-
-<BorderPane xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxKerho.HarrastusDialogController">
-   <bottom>
-      <VBox BorderPane.alignment="CENTER">
-         <children>
-            <Label fx:id="labelVirhe" maxWidth="1000.0" />
-            <HBox />
-            <ButtonBar prefHeight="40.0" prefWidth="200.0">
-              <buttons>
-                  <Button defaultButton="true" mnemonicParsing="false" onAction="#handleOK" text="OK" />
-                  <Button cancelButton="true" mnemonicParsing="false" onAction="#handleCancel" text="Cancel" />
-              </buttons>
-               <padding>
-                  <Insets right="10.0" />
-               </padding>
-            </ButtonBar>
-         </children>
-      </VBox>
-   </bottom>
-   <center>
-      <ScrollPane fx:id="panelHarrastus" fitToWidth="true" pannable="true" BorderPane.alignment="CENTER">
-         <content>
-            <GridPane fx:id="gridHarrastus" hgap="10.0">
-               <columnConstraints>
-                  <ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="NEVER" minWidth="10.0" />
-                  <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
-               </columnConstraints>
-               <rowConstraints>
-                  <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
-                  <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
-                  <RowConstraints minHeight="10.0" vgrow="SOMETIMES" />
-               </rowConstraints>
-               <children>
-                  <Label text="ala" />
-                  <Label text="aloitusvuosi" GridPane.rowIndex="1" />
-                  <Label text="h/vko" GridPane.rowIndex="2" />
-                  <TextField text="kalastus" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" />
-                  <TextField text="1955" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
-                  <TextField text="20" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" />
-               </children>
-               <opaqueInsets>
-                  <Insets left="10.0" top="10.0" />
-               </opaqueInsets>
-               <padding>
-                  <Insets left="10.0" right="10.0" top="10.0" />
-               </padding>
-            </GridPane>
-         </content>
-      </ScrollPane>
-   </center>
-</BorderPane>
diff --git a/luennot/live22/src/fxKerho/KerhoGUIController.java b/luennot/live22/src/fxKerho/KerhoGUIController.java
index de86aec..2d8e517 100644
--- a/luennot/live22/src/fxKerho/KerhoGUIController.java
+++ b/luennot/live22/src/fxKerho/KerhoGUIController.java
@@ -1,6 +1,6 @@
 package fxKerho;
 
-import static fxKerho.JasenDialogController.getFieldId; 
+import static fxKerho.TietueDialogController.getFieldId; 
 import java.awt.Desktop;
 import java.io.IOException;
 import java.io.PrintStream;
@@ -152,14 +152,13 @@ public class KerhoGUIController implements Initializable {
         chooserJasenet.clear();
         chooserJasenet.addSelectionListener(e -> naytaJasen());
         
-        edits = JasenDialogController.luoKentat(gridJasen);      
+        edits = TietueDialogController.luoKentat(gridJasen, new Jasen());      
         for (TextField edit: edits)   
             if ( edit != null ) {   
                 edit.setEditable(false);   
                 edit.setOnMouseClicked(e -> { if ( e.getClickCount() > 1 ) muokkaa(getFieldId(edit,kentta)); });  
                 edit.focusedProperty().addListener((a,o,n) -> kentta = getFieldId(edit,kentta)); 
             } 
-
     }
 
     
@@ -244,7 +243,7 @@ public class KerhoGUIController implements Initializable {
         Jasen jasen = chooserJasenet.getSelectedObject();
         if (jasen == null) return;
 
-        JasenDialogController.naytaJasen(edits, jasen); 
+        TietueDialogController.naytaTietue(edits, jasen); 
         naytaHarrastukset(jasen);
     }
 
@@ -274,7 +273,7 @@ public class KerhoGUIController implements Initializable {
         } catch (CloneNotSupportedException e) {
             // Ei voi tapahtua
         }
-        jasen = JasenDialogController.kysyJasen(null, jasen, k);
+        jasen = TietueDialogController.kysyTietue(null, jasen, k);
         if (jasen == null) return;
         try {
             kerho.korvaaTaiLisaa(jasen);
@@ -321,7 +320,7 @@ public class KerhoGUIController implements Initializable {
      */
     protected void uusiJasen() {
         Jasen uusi = new Jasen();
-        uusi = JasenDialogController.kysyJasen(null, uusi, uusi.ekaKentta()); 
+        uusi = TietueDialogController.kysyTietue(null, uusi, uusi.ekaKentta()); 
         if (uusi == null) return;
         uusi.rekisteroi();
         try {
@@ -342,7 +341,7 @@ public class KerhoGUIController implements Initializable {
         if ( jasenKohdalla == null ) return;
 
         Harrastus uusi = new Harrastus(jasenKohdalla.getTunnusNro());
-        uusi = HarrastusDialogController.kysyHarrastus(null, uusi, 0);
+        uusi = TietueDialogController.kysyTietue(null, uusi, 0);
         if ( uusi == null ) return;
         uusi.rekisteroi();
         try {
diff --git a/luennot/live22/src/fxKerho/JasenDialogController.java b/luennot/live22/src/fxKerho/TietueDialogController.java
similarity index 52%
rename from luennot/live22/src/fxKerho/JasenDialogController.java
rename to luennot/live22/src/fxKerho/TietueDialogController.java
index 5ff7474..5e4bea3 100644
--- a/luennot/live22/src/fxKerho/JasenDialogController.java
+++ b/luennot/live22/src/fxKerho/TietueDialogController.java
@@ -9,7 +9,7 @@ import fi.jyu.mit.fxgui.ModalControllerInterface;
 import fi.jyu.mit.ohj2.Mjonot;
 import javafx.fxml.Initializable;
 import javafx.stage.Stage;
-import kerho.Jasen;
+import kanta.Tietue;
 import javafx.fxml.FXML;
 import javafx.scene.Node;
 import javafx.scene.control.Label;
@@ -21,41 +21,44 @@ import javafx.scene.layout.GridPane;
  * Jäsenelle oma kontrolleri
  * @author vesal
  * @version 20.3.2023
+ * @param <TYPE> Minkä tyyppisiä olioita käsitellään
  *
  */
-public class JasenDialogController implements ModalControllerInterface<Jasen>, Initializable {
+public class TietueDialogController<TYPE extends Tietue> implements ModalControllerInterface<TYPE>,Initializable  {
 
     @FXML private Label labelVirhe;
-    @FXML private GridPane gridJasen;
-    @FXML ScrollPane panelJasen;
+    @FXML private GridPane gridTietue;
+    @FXML ScrollPane panelTietue;
 
     @Override
     public void initialize(URL arg0, ResourceBundle arg1) {
-        alusta();
+        // alusta();
+        // Ei voi täällä vielä alustaa kun ei ole tietueKohdale olemassa, siirretty setDefault
     }
 
     @Override
-    public Jasen getResult() {
-        return jasenKohdalla;
+    public TYPE getResult() {
+        return tietueKohdalla;
     }
 
     @Override
-    public void setDefault(Jasen oletus) {
-        jasenKohdalla = oletus;
-        naytaJasen(edits, jasenKohdalla);    
+    public void setDefault(TYPE oletus) {
+        tietueKohdalla = oletus;
+        alusta();
+        naytaTietue(edits, tietueKohdalla);    
     }
 
     
     @Override
     public void handleShown() {
-        kentta = Math.max(apujasen.ekaKentta(), Math.min(kentta, apujasen.getKenttia()-1)); 
+        kentta = Math.max(tietueKohdalla.ekaKentta(), Math.min(kentta, tietueKohdalla.getKenttia()-1)); 
         edits[kentta].requestFocus(); 
     }
 
 
     @FXML private void handleOK() {
-        if ( jasenKohdalla != null && jasenKohdalla.getNimi().trim().equals("") ) {
-            naytaVirhe("Nimi ei saa olla tyhjä");
+        if ( tietueKohdalla != null && tietueKohdalla.anna(tietueKohdalla.ekaKentta()).trim().equals("") ) {
+            naytaVirhe("ei saa olla tyhjä");
             return;
         }
         
@@ -64,45 +67,45 @@ public class JasenDialogController implements ModalControllerInterface<Jasen>, I
 
     
     @FXML private void handleCancel() {
-        jasenKohdalla = null;
+        tietueKohdalla = null;
         ModalController.closeStage(labelVirhe);
     }
 
     // =========================================================================================
     
-    private Jasen jasenKohdalla;
+    private TYPE tietueKohdalla;
     private TextField[] edits;
-    private static Jasen apujasen = new Jasen();
-    private int kentta;
+    private int kentta = 0;  // mikä kenttä aktivoidaan kun dialogi aukaistaan
 
     
     /**
-     * Luodaan GridPaneen jäsenen tiedot
-     * @param gridJasen mihin tiedot luodaan
+     * Luodaan GridPaneen tietueen tiedot
+     * @param gridTietue mihin tiedot luodaan
+     * @param aputietue malli josta tiedot otetaan
      * @return luodut tekstikentät
      */
-    public static TextField[] luoKentat(GridPane gridJasen) {
-        gridJasen.getChildren().clear();
-        TextField[] edits = new TextField[apujasen.getKenttia()];
+    public static<TYPE extends Tietue> TextField[] luoKentat(GridPane gridTietue, TYPE aputietue) {
+        gridTietue.getChildren().clear();
+        TextField[] edits = new TextField[aputietue.getKenttia()];
         
-        for (int i=0, k = apujasen.ekaKentta(); k < apujasen.getKenttia(); k++, i++) {
-            Label label = new Label(apujasen.getKysymys(k));
-            gridJasen.add(label, 0, i);
+        for (int i=0, k = aputietue.ekaKentta(); k < aputietue.getKenttia(); k++, i++) {
+            Label label = new Label(aputietue.getKysymys(k));
+            gridTietue.add(label, 0, i);
             TextField edit = new TextField();
             edits[k] = edit;
             edit.setId("e"+k);
-            gridJasen.add(edit, 1, i);
+            gridTietue.add(edit, 1, i);
         }
         return edits;
     }
     
     
     private void alusta() {
-        edits = luoKentat(gridJasen);
+        edits = luoKentat(gridTietue, tietueKohdalla);
         for (TextField edit : edits)
             if ( edit != null )
-                edit.setOnKeyReleased( e -> kasitteleMuutosJaseneen((TextField)(e.getSource())));
-        panelJasen.setFitToHeight(true);         
+                edit.setOnKeyReleased( e -> kasitteleMuutosTietueeseen((TextField)(e.getSource())));
+        panelTietue.setFitToHeight(true);         
     }
     
 
@@ -137,37 +140,36 @@ public class JasenDialogController implements ModalControllerInterface<Jasen>, I
     
     
     /**
-     * Käsitellään jäseneen tullut muutos
+     * Käsitellään tietueeseen tullut muutos
      * @param edit muuttunut kenttä
      */
-    private void kasitteleMuutosJaseneen(TextField edit) {
-        if (jasenKohdalla == null) return;
+    private void kasitteleMuutosTietueeseen(TextField edit) {
+        if (tietueKohdalla == null) return;
         String s = edit.getText();
-        int k = getFieldId(edit,apujasen.ekaKentta());
-        String virhe = null;
-        virhe = jasenKohdalla.aseta(k, s);
-        if (virhe == null) {
-            Dialogs.setToolTipText(edit,"");
+        int k = getFieldId(edit,tietueKohdalla.ekaKentta());
+        String virhe = tietueKohdalla.aseta(k, s);
+        
+        if (virhe != null) {
+            Dialogs.setToolTipText(edit,virhe); 
+            edit.getStyleClass().add("virhe");
             naytaVirhe(virhe);
-            edit.getStyleClass().removeAll("virhe");
         } else {
-            Dialogs.setToolTipText(edit,virhe);
+            Dialogs.setToolTipText(edit,""); 
+            edit.getStyleClass().removeAll("virhe");
             naytaVirhe(virhe);
-            edit.getStyleClass().add("virhe");
         }
     }
     
     
-    
     /**
-     * Näytetään jäsenen tiedot TextField komponentteihin
+     * Näytetään tietueen tiedot TextField komponentteihin
      * @param edits taulukko jossa tekstikenttiä
-     * @param jasen näytettävä jäsen
+     * @param tietue näytettävä tietue
      */
-    public static void naytaJasen(TextField[] edits, Jasen jasen) {
-        if (jasen == null) return;
-        for (int k = jasen.ekaKentta(); k < jasen.getKenttia(); k++) {
-            edits[k].setText(jasen.anna(k));
+    public static void naytaTietue(TextField[] edits, Tietue tietue) {
+        if (tietue == null) return;
+        for (int k = tietue.ekaKentta(); k < tietue.getKenttia(); k++) {
+            edits[k].setText(tietue.anna(k));
         }
     }
   
@@ -180,12 +182,12 @@ public class JasenDialogController implements ModalControllerInterface<Jasen>, I
      * @param kentta mikä kenttä saa fokuksen kun näytetään
      * @return null jos painetaan Cancel, muuten täytetty tietue
      */
-    public static Jasen kysyJasen(Stage modalityStage, Jasen oletus, int kentta) {
-        return ModalController.<Jasen, JasenDialogController>showModal(
-                KerhoGUIController.class.getResource("JasenDialogView.fxml"), 
-                "Jäsen", 
-                modalityStage, 
-                oletus,  
-                ctrl -> ctrl.setKentta(kentta));
+    public static<TYPE extends Tietue> TYPE kysyTietue(Stage modalityStage, TYPE oletus, int kentta) {
+        return ModalController.<TYPE, TietueDialogController<TYPE>>showModal(
+                TietueDialogController.class.getResource("TietueDialogView.fxml"),
+                "Kerho",
+                modalityStage, oletus,
+                ctrl -> ctrl.setKentta(kentta) 
+                );
     }
 }
diff --git a/luennot/live22/src/fxKerho/JasenDialogView.fxml b/luennot/live22/src/fxKerho/TietueDialogView.fxml
similarity index 85%
rename from luennot/live22/src/fxKerho/JasenDialogView.fxml
rename to luennot/live22/src/fxKerho/TietueDialogView.fxml
index 156708e..a7c548f 100644
--- a/luennot/live22/src/fxKerho/JasenDialogView.fxml
+++ b/luennot/live22/src/fxKerho/TietueDialogView.fxml
@@ -7,7 +7,7 @@
 <?import javafx.scene.text.*?>
 <?import javafx.scene.layout.BorderPane?>
 
-<BorderPane stylesheets="@kerho.css"  xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxKerho.JasenDialogController">
+<BorderPane stylesheets="@kerho.css"  xmlns="http://javafx.com/javafx/19" xmlns:fx="http://javafx.com/fxml/1" fx:controller="fxKerho.TietueDialogController">
    <bottom>
       <VBox BorderPane.alignment="CENTER">
          <children>
@@ -26,9 +26,9 @@
       </VBox>
    </bottom>
    <center>
-      <ScrollPane fx:id="panelJasen" fitToWidth="true" pannable="true" BorderPane.alignment="CENTER">
+      <ScrollPane fx:id="panelTietue" fitToWidth="true" pannable="true" BorderPane.alignment="CENTER">
          <content>
-            <GridPane fx:id="gridJasen" hgap="10.0">
+            <GridPane fx:id="gridTietue" hgap="10.0">
                <columnConstraints>
                   <ColumnConstraints fillWidth="false" halignment="RIGHT" hgrow="NEVER" minWidth="10.0" />
                   <ColumnConstraints hgrow="ALWAYS" minWidth="10.0" />
@@ -60,10 +60,10 @@
                   <Label text="jäsenmaksu" GridPane.rowIndex="9" />
                   <Label text="maksettu maksu" GridPane.rowIndex="10" />
                   <Label text="lisätietoja" GridPane.rowIndex="11" />
-                  <TextField fx:id="editNimi"        text="Ankka Aku" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" />
-                  <TextField fx:id="editHetu"        text="010245-123U" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
-                  <TextField fx:id="editKatuosoite"  text="Paratiisitie 13" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" />
-                  <TextField fx:id="editPostinumero" text="12345" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3" />
+                  <TextField text="Ankka Aku" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" />
+                  <TextField text="010245-123U" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1" />
+                  <TextField text="Paratiisitie 13" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="2" />
+                  <TextField text="12345" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="3" />
                   <TextField text="ANKKALINNA" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="4" />
                   <TextField text="12-12345" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="5" />
                   <TextField text="2" GridPane.columnIndex="1" GridPane.hgrow="ALWAYS" GridPane.rowIndex="6" />
diff --git a/luennot/live22/src/kanta/Tietue.java b/luennot/live22/src/kanta/Tietue.java
new file mode 100644
index 0000000..0a4b49c
--- /dev/null
+++ b/luennot/live22/src/kanta/Tietue.java
@@ -0,0 +1,116 @@
+package kanta;
+
+
+/**
+ * Rajapinta tietueelle johon voidaan taulukon avulla rakentaa 
+ * "attribuutit".
+ * @author vesal
+ * @version Mar 23, 2012
+ * @example
+ */
+public interface Tietue {
+
+    
+    /**
+     * @return tietueen kenttien lukumäärä
+     * @example
+     * <pre name="test">
+     *   #import kerho.Harrastus;
+     *   Harrastus har = new Harrastus();
+     *   har.getKenttia() === 5;
+     * </pre>
+     */
+    public abstract int getKenttia();
+
+
+    /**
+     * @return ensimmäinen käyttäjän syötettävän kentän indeksi
+     * @example
+     * <pre name="test">
+     *   Harrastus har = new Harrastus();
+     *   har.ekaKentta() === 2;
+     * </pre>
+     */
+    public abstract int ekaKentta();
+
+
+    /**
+     * @param k minkä kentän kysymys halutaan
+     * @return valitun kentän kysymysteksti
+     * @example
+     * <pre name="test">
+     *   Harrastus har = new Harrastus();
+     *   har.getKysymys(2) === "ala";
+     * </pre>
+     */
+    public abstract String getKysymys(int k);
+
+
+    /**
+     * @param k Minkä kentän sisältö halutaan
+     * @return valitun kentän sisältö
+     * @example
+     * <pre name="test">
+     *   Harrastus har = new Harrastus();
+     *   har.parse("   2   |  10  |   Kalastus  | 1949 | 22 t ");
+     *   har.anna(0) === "2";   
+     *   har.anna(1) === "10";   
+     *   har.anna(2) === "Kalastus";   
+     *   har.anna(3) === "1949";   
+     *   har.anna(4) === "22";   
+     * </pre>
+     */
+    public abstract String anna(int k);
+
+    
+    /**
+     * Asetetaan valitun kentän sisältö.  Mikäli asettaminen onnistuu,
+     * palautetaan null, muutoin virheteksti.
+     * @param k minkä kentän sisältö asetetaan
+     * @param s asetettava sisältö merkkijonona
+     * @return null jos ok, muuten virheteksti
+     * @example
+     * <pre name="test">
+     *   Harrastus har = new Harrastus();
+     *   har.aseta(3,"kissa") === "aloitusvuosi: Ei kokonaisluku (kissa)";
+     *   har.aseta(3,"1940")  === null;
+     *   har.aseta(4,"kissa") === "h/vko: Ei kokonaisluku (kissa)";
+     *   har.aseta(4,"20")    === null;
+     * </pre>
+     */
+    public abstract String aseta(int k, String s);
+
+
+    /**
+     * Tehdään identtinen klooni tietueesta
+     * @return kloonattu tietue
+     * @throws CloneNotSupportedException jos kloonausta ei tueta
+     * @example
+     * <pre name="test">
+     * #THROWS CloneNotSupportedException 
+     *   Harrastus har = new Harrastus();
+     *   har.parse("   2   |  10  |   Kalastus  | 1949 | 22 t ");
+     *   Object kopio = har.clone();
+     *   kopio.toString() === har.toString();
+     *   har.parse("   1   |  11  |   Uinti  | 1949 | 22 t ");
+     *   kopio.toString().equals(har.toString()) === false;
+     *   kopio instanceof Harrastus === true;
+     * </pre>
+     */
+    public abstract Tietue clone() throws CloneNotSupportedException;
+
+
+    /**
+     * Palauttaa tietueen tiedot merkkijonona jonka voi tallentaa tiedostoon.
+     * @return tietue tolppaeroteltuna merkkijonona 
+     * @example
+     * <pre name="test">
+     *   Harrastus harrastus = new Harrastus();
+     *   harrastus.parse("   2   |  10  |   Kalastus  | 1949 | 22 t ");
+     *   harrastus.toString()    =R= "2\\|10\\|Kalastus\\|1949\\|22.*";
+     * </pre>
+     */
+    @Override
+    public abstract String toString();
+
+}
diff --git a/luennot/live22/src/kerho/Harrastus.java b/luennot/live22/src/kerho/Harrastus.java
index 4e37dd8..b088323 100644
--- a/luennot/live22/src/kerho/Harrastus.java
+++ b/luennot/live22/src/kerho/Harrastus.java
@@ -3,6 +3,8 @@ package kerho;
 import java.io.*;
 
 import fi.jyu.mit.ohj2.Mjonot;
+import kanta.Tietue;
+
 import static kanta.HetuTarkistus.rand;
 
 /**
@@ -11,7 +13,7 @@ import static kanta.HetuTarkistus.rand;
  * @author Vesa Lappalainen
  * @version 1.0, 22.02.2003
  */
-public class Harrastus implements Cloneable {
+public class Harrastus implements Cloneable, Tietue {
     private int tunnusNro;
     private int jasenNro;
     private String ala = "";
@@ -41,6 +43,7 @@ public class Harrastus implements Cloneable {
     /**
      * @return harrastukse kenttien lukumäärä
      */
+    @Override
     public int getKenttia() {
         return 5;
     }
@@ -49,6 +52,7 @@ public class Harrastus implements Cloneable {
     /**
      * @return ensimmäinen käyttäjän syötettävän kentän indeksi
      */
+    @Override
     public int ekaKentta() {
         return 2;
     }
@@ -58,6 +62,7 @@ public class Harrastus implements Cloneable {
      * @param k minkä kentän kysymys halutaan
      * @return valitun kentän kysymysteksti
      */
+    @Override
     public String getKysymys(int k) {
         switch (k) {
             case 0:
@@ -91,6 +96,7 @@ public class Harrastus implements Cloneable {
      *   
      * </pre>
      */
+    @Override
     public String anna(int k) {
         switch (k) {
             case 0:
@@ -124,6 +130,7 @@ public class Harrastus implements Cloneable {
      *   har.aseta(4,"20")    === null;
      * </pre>
      */
+    @Override
     public String aseta(int k, String s) {
         String st = s.trim();
         StringBuffer sb = new StringBuffer(st);
diff --git a/luennot/live22/src/kerho/Jasen.java b/luennot/live22/src/kerho/Jasen.java
index 234a042..182b3a9 100644
--- a/luennot/live22/src/kerho/Jasen.java
+++ b/luennot/live22/src/kerho/Jasen.java
@@ -4,6 +4,7 @@ import java.io.*;
 
 import fi.jyu.mit.ohj2.Mjonot;
 import kanta.HetuTarkistus;
+import kanta.Tietue;
 
 import static kanta.HetuTarkistus.*;
 
@@ -13,7 +14,7 @@ import static kanta.HetuTarkistus.*;
  * @author Vesa Lappalainen
  * @version 1.0, 22.02.2003
  */
-public class Jasen implements Cloneable {
+public class Jasen implements Cloneable, Tietue {
     private int        tunnusNro;
     private String     nimi           = "";
     private String     hetu           = "";
@@ -33,6 +34,7 @@ public class Jasen implements Cloneable {
     /**
      * @return montako kenttaa
      */
+    @Override
     public int getKenttia() {
         return 13;
     }
@@ -41,6 +43,7 @@ public class Jasen implements Cloneable {
      * Eka kenttä joka on mielekäs kysyttäväksi
      * @return ekan kentän indeksi
      */
+    @Override
     public int ekaKentta() {
         return 1;
     }
@@ -51,6 +54,7 @@ public class Jasen implements Cloneable {
      * @param k kuinka monennen kentän kysymys palautetaan (0-alkuinen)
      * @return k:netta kenttää vastaava kysymys
      */
+    @Override
     public String getKysymys(int k) {
         switch ( k ) {
         case 0: return "Tunnus nro";
@@ -90,6 +94,7 @@ public class Jasen implements Cloneable {
      * @param k monenenko kentän sisältö palautetaan
      * @return kentän sisältö merkkijonona
      */
+    @Override
     public String anna(int k) {
         switch ( k ) {
         case 0: return "" + tunnusNro;
@@ -128,6 +133,7 @@ public class Jasen implements Cloneable {
     *   jasen.aseta(9,"1940") === null;
     * </pre>
     */
+    @Override
     public String aseta(int k, String jono) {
         String tjono = jono.trim();
         StringBuffer sb = new StringBuffer(tjono);
-- 
GitLab