package rup.česky.tvary_0;

import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;

import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

import java.util.Collections;
import java.util.List;
import java.util.LinkedList;
import java.util.ListIterator;


/*******************************************************************************
 * Třída <b><code>AktivníPlátno</code></b> slouží k jednoduchému kreslení
 * na virtuální plátno a připadné následné animaci nakreslených obrázků.
 * </p><p>
 * Třída neposkytuje veřejný konstruktor, protože chce, aby její instance
 * byla jedináček, tj. aby se všechno kreslilo na jedno a to samé plátno.
 * Jediným způsobem, jak získat odkaz na instanci třídy
 * <code>AktivníPlátno</code>,
 * je volaní její statické metody <code>getPlátno()</code>.
 * </p><p>
 * Třída <code>AktivníPlátno</code> funguje jako manažer, který dohlíží
 * na to, aby se po změně zobrazení některého z tvarů všechny ostatní tvary
 * řádně překreslily, aby byly správně zachyceny všechny překryvy
 * a aby se při pohybu jednotlivé obrazce vzájemně neodmazavaly.
 * Aby vše správně fungovalo, je možno použít jeden dvou přístupů:</p>
 * <ul>
 *   <li>Manažer bude obsah plátna překreslovat
 *       <b>v pravidelných intervalech</b>
 *       bez ohledu na to, jestli se na něm udála nějaká změna či ne.
 *       <ul><li>
 *       <b>Výhodou</b> tohoto přístupu je, že se zobrazované objekty
 *       nemusí starat o to, aby se manažer dozvěděl, že se jejich stav změnil.
 *       </li><li>
 *       <b>Neýhodou</b> tohoto přístupu je naopak to, že manažer
 *       spotřebovává na neustálé překreslování jistou část výkonu
 *       procesoru, což může u pomalejších počítačů působit problémy.
 *       <br>&nbsp;</li></ul></li>
 *   <li>Manažer překresluje plátno <b>pouze na výslovné požádání</b>.
 *       <ul><li>
 *       <b>Výhodou</b> tohoto přístupu je úspora spotřebovaného výkonu
 *       počítače v období, kdy se na plátně nic neděje.
 *       </li><li>
 *       <b>Nevýhodou</b> tohoto přístupu je naopak to, že kreslené
 *       objekty musí na každou změnu svého stavu upozornit manažera,
 *       aby věděl, žed má plátno překreslit.
 *   </li>
 * </ul><p>
 * Třída <code>AktivníPlátno</code> požívá druhou z uvedených strategií,
 * tj. <b>překresluje plátno pouze na požádání</b>.
 * </p><p>
 * Obrazec, který chce být zobrazován na plátně, se musí nejprve přihlásit
 * u instance třídy <code>AktivníPlátno</code>, aby jej tato zařadila
 * mezi spravované obrazce (sada metod <code>přidej&hellip;</code>).
 * Přihlásit se však mohou pouze instance tříd, které implementují
 * rozhraní <code>IKreslený</code>.
 * </p><p>
 * Nepřihlášený obrazec nemá šanci býti zobrazen, protože na plátno
 * se může zobrazit pouze za pomoci kreslítka, jež může získat jedině od
 * instance třídy <code>AktivníPlátno</code>, ale ta je poskytuje pouze
 * instancím, které se přihlásily do její správy.
 * </p><p>
 * Obrazec, který již dále nemá byt kreslen, se muže odhlásit zavoláním
 * metody <code>odstraň(IKreslený)</code>.Zavoláním metody
 * <code>odstraňVše()</code> se ze seznamu spravovaných (a tím i z plátna)
 * odstraní všechny vykreslované obrazce.
 * </p><p>
 * Efektivitu vykreslování je možné ovlivnit voláním metody
 * <code>nekresli()</code>, která pozastaví překreslování plátna po nahlášených
 * změnách. Její volání je výhodné např. v situaci, kdy je třeba vykreslit
 * obrazec složený z řady menších obrazců a bylo by nevhodné překreslovat
 * plátno po vykreslení každého z nich.
 * </p><p>
 * Do původního stavu převedeme plátno voláním metody <code>vraťKresli()</code>,
 * která vrátí vykreslování do stavu před posledním voláním metody
 * <code>nekresli()</code>. Nemůžeč se tedy stát, že by se při zavolání metody
 * <code>nekresli()</code> v situaci, kdy je již vykreslování pozastaveno,
 * začalo po následém zavolání <code>vraťKresli()</code> hned vykreslovat.
 * Po dvou voláních <code>vraťKresli()</code> se začne vykreslovat až po
 * dvou zavoláních <code>vraťKresli()</code>.
 * </p><p>
 * Proto plátno pouze žádáme, aby se vrátilo do toho kreslícího stavu,
 * ve kterém bylo v okamžiku, kdy jsme je naposledy žádali o to,
 * aby se přestalo překreslovat. Nemůže se tedy stát, že by se při zavolání
 * metody <code>nekresli()</code> v situaci, kdy je již vykreslování
 * pozastaveno, začalo po následném zavolání <code>vraťKresli()</code> hned
 * vykreslovat.
 * </p><p>
 * Každé zavolání metody <code>nekresli()</code> musí být doplněno
 * odpovídajícím voláním <code>vraťKresli()</code>. Teprve když poslední
 * <code>vraťKresli()</code> odvolá první <code>nekresli()</code>, bude
 * překreslování opět obnoveno.
 * </p>
 *
 * @author     Rudolf Pecinovský
 * @version    2.01, 22.02.2004
 */
public class AktivníPlátno
{
//== KONSTANTNÍ ATRIBUTY TŘÍDY =================================================

    /** Počáteční šířka aktivní plochy plátna v bodech. */
    public static final int ŠÍŘKA_0 = 6;

    /** Počáteční výška aktivní plochy plátna v bodech. */
    public static final int VÝŠKA_0 = 6;

    /** Počáteční barva pozadí plátna. */
    public static final Barva POZADÍ_0 = Barva.KRÉMOVÁ;

    /** Počáteční barva čar mřížky. */
    public static final Barva BARVA_ČAR_0 = Barva.ČERNÁ;

    /** Implicitní rozteč čtvercové sítě. */
    private static final int KROK_0 = 50;

    /** Maximální povolená velikost rozteče čtvercové sítě. */
    private static final int MAX_KROK = 200;

    /** Jediná instance třídy AktivníPlátno */
    private static final AktivníPlátno jedináček = new AktivníPlátno();

    //Při kreslení čar se ptá APosuvný po aktivním plátnu.
    //Proto se mohou čáry kreslit až poté, co bude jedináček inicializován
    static 
    {
        //Připraví a vykreslí prázdné plátno
        jedináček.setRozměr(ŠÍŘKA_0, VÝŠKA_0);
    }



//== PROMĚNNÉ ATRIBUTY TŘÍDY ===================================================
//== KONSTANTNÍ ATRIBUTY INSTANCÍ ==============================================

        /** Aplikační okno animačního plátna. */
        private final JFrame okno;

        /** Instance lokální třídy, která je zřízena proto, aby odstínila
         *  metody svého rodiče JPanel. */
        private final JPanel vlastníPlátno;
        
        
//== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================

    //Z venku neovlivnitelné Atributy pro zobrazeni plátna v aplikačním okně

        /** Vše se kreslí na obraz - ten se snadněji překreslí. */
        private Image obrazPlátna;

        /** Kreslítko získané od obrazu plátna, na nějž se vlastně kreslí. */
        private Kreslítko kreslítko;

        /** Semafor bránící příliš častému překreslování. Překresluje se pouze
         *  je-li ==0. Nesmi byt <0. */
        private int nekreslit = 0;

        /** Příznak toho, ze v době zakázaného kreslení přišel požadavek
         *  na překreslení plátna */
        private boolean kreslit = false;

        /** Příznak toho, že kreslení právě probíhá,
         *  takže vypínání nefunguje. */
        private boolean kreslím = false;

        /** Čáry zobrazující na plántě mřížku. */
        private Čára[] vodorovná,   svislá;

    //Přímo ovlivnitelné atributy

        /** Rozteč čtvercové sítě. */
        private int krok = KROK_0;

        /** Zobrazuje-li se mřížka. */
        private boolean mřížka = true;

        /** Barva pozadí plátna. */
        private Barva barvaPozadí = POZADÍ_0;

        /** Barva čar mřížky. */
        private Barva barvaČar = BARVA_ČAR_0;

        /** Šířka aktivní plochy plátna v bodech. */
        private int šířka = ŠÍŘKA_0;

        /** Výška aktivní plochy plátna v bodech. */
        private int výška = VÝŠKA_0;


        /** Šířka aktivní plochy plátna v bodech. */
        private int šířkaBodů = ŠÍŘKA_0 * krok;

        /** Výška aktivní plochy plátna v bodech. */
        private int výškaBodů = VÝŠKA_0 * krok;

        /** Seznam zobrazovaných předmětů. */
        List<IKreslený> předměty = new LinkedList<IKreslený>();

        /** Název v titulkové liště animačního plátna. */
        private String název  = "Animační plátno";


//== PŘÍSTUPOVÉ METODY VLASTNOSTÍ TŘÍDY ========================================
//== OSTATNÍ METODY TŘÍDY ======================================================

//##############################################################################
//== KONSTRUKTORY A TOVÁRNÍ METODY =============================================

    /***************************************************************************
     * Vytvoří instanci třídy - je volaná pouze jednou.
     */
    private AktivníPlátno()
    {
        okno  = new JFrame();          //Vytvoří nové aplikační okno
        okno.setTitle(název);
        okno.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
        okno.addWindowListener(new WindowAdapter()
        {
            public void windowClosing(WindowEvent evt)
            {
                System.exit(0);
            }
        });
        //Vlastní plátno je definováno jako lokální třída
        vlastníPlátno = new JPanel()
        {
            /** Povinně překrývaná abstraktní metoda třídy JPanel. */
            public void paint( Graphics g )
            {
                synchronized( obrazPlátna )
                {
                    g.drawImage( obrazPlátna, 0, 0, null );
                }
            }

        };//Konec definice třídy vlastníPlátno
        okno.setContentPane(vlastníPlátno);
    }


    /***************************************************************************
     * Jediná metoda umožnující získat odkaz na instanci plátna.
     * Vrací vsak pokaždé odkaz na stejnou instanci,
     * protože instance plátna je jedináček.
     * <p>
     * Pokud instance při volaní metody ještě neexistuje,
     * metoda instanci vytvoří.</p>
     *
     * @return Instance třídy AktivníPlátno
     */
    public static AktivníPlátno getPlátno()
    {
        return jedináček;
    }



//== PŘÍSTUPOVÉ METODY ATRIBUTU INSTANCÍ =======================================

    /***************************************************************************
     * Nastaví rozměr plátna zadáním bodové velikosti políčka a
     * počtu políček ve vodorovném a svislém směru.
     * Při velikosti políčka = 1 se vypíná zobrazování mřížky.
     *
     * @param  krok   Nová bodová velikost políčka
     * @param  šířka  Nový počet políček vodorovně
     * @param  výška  Nový počet políček svisle
     */
    public synchronized void setKrokRozměr( int krok, int šířka, int výška )
    {
        šířkaBodů = šířka * krok;
        výškaBodů = výška * krok;

        Dimension obrazovka = Toolkit.getDefaultToolkit().getScreenSize();
        if( (krok  < 1)  ||
            (šířka < 2)  ||  (obrazovka.width  < šířkaBodů) ||
            (výška < 2)  ||  (obrazovka.height < výškaBodů) )
        {
            throw new IllegalArgumentException(
                "\nŠpatně zadané rozměry: krok=" + krok +
                " bodů, šířka="  + šířka + "(" + šířka*krok +
                " bodů), výška=" + výška + "(" + výška*krok +
                " bodů), obrazovka=" + obrazovka.width  + "×" +
                                       obrazovka.height + " bodů" );
        }
        this.krok  = krok;
        this.šířka = šířka;
        this.výška = výška;

        okno.setResizable( true );
        vlastníPlátno.setPreferredSize( new Dimension( šířkaBodů, výškaBodů ) );
        okno.pack();
        okno.setResizable( false );

        obrazPlátna = vlastníPlátno.createImage( šířkaBodů, výškaBodů );
        kreslítko = new Kreslítko( (Graphics2D)obrazPlátna.getGraphics() );
        kreslítko.setPozadí( barvaPozadí );

        if( krok > 1 )
        {
            svislá    = new Čára[ šířka ];
            vodorovná = new Čára[ výška ];
            for( int i=0, x=krok;   i < šířka;      i++, x+=krok )
                svislá[i]    = new Čára( x, 0, x, výškaBodů, barvaČar );
            for( int i=0, y=krok;   i < výška;   i++, y+=krok )
                vodorovná[i] = new Čára( 0, y, šířkaBodů, y, barvaČar );
        }
        else
        {
            //Uvolnění doposud používaných instancí
            svislá = null;
            vodorovná = null;
            mřížka = false;
        }

        okno.setVisible(true);
        překresli();
        okno.toFront();
    }


    /***************************************************************************
     * Vrátí vzdálenost čar mřížky = bodovou velikost políčka.
     *
     * @return Bodová velikost políčka
     */
     public synchronized int getKrok()
     {
         return krok;
     }


    /***************************************************************************
     * Nastaví vzdálenost čar mřížky = bodovou velikost políčka.
     * Při velikosti políčka = 1 se vypíná zobrazování mřížky.
     *
     * @param velikost  Nová bodová velikost políčka
     */
    public synchronized void setKrok( int velikost )
    {
        setKrokRozměr( velikost, šířka, výška );
    }


    /***************************************************************************
     * Vrátí políčkovou šířku plátna.
     *
     * @return  Aktuální políčková šířka plátna (počet políček vodorovně)
     */
    public synchronized int getŠířka()
    {
        return šířka;
    }


    /***************************************************************************
     * Vrátí bodovou šířku plátna.
     *
     * @return  Aktuální bodová šířka plátna (počet bodů vodorovně)
     */
    public synchronized int getBšířka()
    {
        return šířkaBodů;
    }


    /***************************************************************************
     * Vrátí políčkovou výšku plátna.
     *
     * @return  Aktuální políčková výška plátna (počet políček svisle)
     */
    public synchronized int getVýška()
    {
        return výška;
    }


    /***************************************************************************
     * Vrátí bodovou výšku plátna.
     *
     * @return  Aktuální bodová výška plátna (počet bodů svisle)
     */
    public synchronized int getBVýška()
    {
        return výškaBodů;
    }


    /***************************************************************************
     * Nastaví rozměr plátna zadáním jeho políčkové výsky a šířky.
     *
     * @param  šířka  Nový počet políček vodorovně
     * @param  výška  Nový počet políček svisle
     */
    public synchronized void setRozměr(int šířka, int výška)
    {
        setKrokRozměr( krok, šířka, výška );
    }


    /***************************************************************************
     * Nastaví rozměr plátna zadáním jeho políčkové výsky a šířky.
     *
     * @param  rozměr  Zadávaný rozměr v počtu políček
     */
    public synchronized void setRozměr(Rozměr rozměr)
    {
        setRozměr( rozměr.šířka, rozměr.výška );
    }


    /***************************************************************************
     * Vrátí informaci o tom, je-li zobrazována mřížka.
     *
     * @return Mřížka je zobrazována = true, není zobrazována = false.
     */
    public synchronized boolean getMřížka()
    {
    	return mřížka;
    }


    /***************************************************************************
     * V závislosti na hodntě parametru nastaví nebo potlačí
     * zobrazování čar mřížky.
     *
     * @param zobrazit  Jestli mřížku zobrazovat.
     */
    public synchronized void setMřížka( boolean zobrazit )
    {
        mřížka = zobrazit;
        překresli();
    }


    /***************************************************************************
     * Poskytuje informaci o aktuální viditelnosti okna.
     *
     * @return Je-li okno viditelné, vrací <b>true</b>, jinak vrací <b>false</b>
     */
    public synchronized boolean isViditelné()
    {
        return okno.isVisible();
    }


    /***************************************************************************
     * V závislosti na hodntě parametru nastaví nebo potlačí viditelnost plátna.
     *
     * @param viditelné logická hodnota požadované viditelnost (true=viditelné)
     */
    public synchronized void setViditelné(boolean viditelné)
    {
        boolean změna = (isViditelné() != viditelné);
        if( změna )
        {
            okno.setVisible(viditelné);
            if( viditelné )
            {
                okno.toFront();
                překresli();
            }
        }
    }


    /***************************************************************************
     * Vrátí aktuální barvu pozadí.
     *
     * @return  Nastavena barva pozadí
     */
    public synchronized Barva getBarvaPozadí()
    {
        return barvaPozadí;
    }


    /***************************************************************************
     * Nastaví pro plátno barvu pozadí.
     *
     * @param  barva  Nastavovaná barva pozadí
     */
    public synchronized void setBarvaPozadí(Barva barva)
    {
        barvaPozadí = barva;
        kreslítko.setPozadí( barvaPozadí );
        překresli();
    }


    /***************************************************************************
     * Pomocná metoda pro účely ladění aby bylo možno zkontrolovat,
     * ze na konci metody má semafor stejnou hodnotu, jako měl na počátku.
     *
     * @return  Stav vnitřního semaforu: >0  - nebude se kreslit,<br>
     *                                   ==0 - kreslí se,<br>
     *                                   <0  - chyba
     */
    public synchronized int getNekresli()
    {
        return nekreslit;
    }

    
    /***************************************************************************
     * Nastaví název v titulkové liště okna plátna.
     *
     * @param název  Nastavovaný název
     */
    public void setNázev( String název )
    {
        okno.setTitle( this.název = název );
    }
    
    
    /***************************************************************************
     * Vrátí aktuální název v titulkové liště okna plátna.
     *
     * @return  Aktuální název okna
     */
    public String getNázev()
    {
        return okno.getTitle();
    }


//== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ =================================
//== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY ===============================
//== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================

    /***************************************************************************
     * Převede instanci na řetězec. Používá se především při ladění.
     *
     * @return Řetězcová reprezentace dané instance.
     */
    public String toString()
    {
        return getClass().getName() + "(šířka=" + šířka + ",výška=" + výška +
               ", krok=" + krok + ", pozadí=" + barvaPozadí + ")";
    }



//== NOVĚ ZAVEDENÉ METODY INSTANCÍ =============================================

    /***************************************************************************
     * Vykreslí všechny elementy.
     */
    public synchronized void překresli()
    {
        if( kreslím )   //Právě překresluji - volám nepřímo sám sebe
            return;
        if( (nekreslit == 0)  &&  isViditelné() )   //Mám kreslit a je proč
        {
            kreslím = true;
            synchronized( obrazPlátna )
            {
                kreslítko.vyplňRám( 0, 0, šířkaBodů, výškaBodů, barvaPozadí );
                if( mřížka  &&  (barvaČar != barvaPozadí) )
                {
                    for( int i=0;   i < šířka;   )
                        svislá[i++].nakresli( kreslítko );
                    for( int i=0;   i < výška;   )
                        vodorovná[i++].nakresli( kreslítko ) ;
                }
                ListIterator it = předměty.listIterator();
                while(it.hasNext()) {
                    ((IKreslený)it.next()).nakresli( kreslítko );
                }
            }//synchronized( obrazPlátna )
            vlastníPlátno.repaint();
            kreslit = false;    //Vše, co se mělo překreslit, bylo překresleno
            kreslím = false;
        }
        else
            kreslit = true;     //Nevyhověli jsme žádosti o překreslení
    }


    /***************************************************************************
     * Potlačí překreslování plátna, přesněji zvýší hladinu potlačení
     * překreslování o jedničku. Návratu do stavu před voláním této metody
     * se dosáhne zavoláním metody <code>vraťKresli()</code>.</p>
     * <p>
     * Metody <code>nekresli()</code> a <code>vraťKresli()</code>
     * se tak chovají obdobně jako závorky, mezi nimiž je vykreslování
     * potlačeno.</p>
     */
    public synchronized void nekresli()
    {
        nekreslit++;
    }


    /***************************************************************************
     * Vrátí překreslování do stavu před posledním voláním metody
     * <code>nekresli()</code>. Předcházelo-li proto více volání metody
     * <code>nekresli()</code>, začne se překreslovat až po odpovídajím počtu
     * zavolání metody <code>vraťKresli()</code>.
     *
     * @throws IllegalStateException
     *         Je-li metoda volána aniž by předcházelo odpovídající volání
     *         <code>nekresli()</code>.
     */
    public synchronized void vraťKresli()
    {
        if( nekreslit == 0 )
            throw new IllegalStateException(
                "Vrácení do stavu kreslení musí přecházet zákaz!" );
        nekreslit--;
        if( nekreslit == 0 )
            překresli();
    }


    /***************************************************************************
     * Odstraní zadaný obrazec ze seznamu malovaných.
     * Byl-li obrazec v seznamu, překreslí vlastníPlátno.
     *
     * @param obrazec  Odstraňovaný obrazec
     *
     * @return  true v případě, když obrazec v seznamu byl,
     *          false v případě, když nebylo co odstraňovat
     */
    public synchronized boolean odstraň(IKreslený obrazec)
    {
        boolean ret = předměty.remove(obrazec);
        if(ret) překresli();
        return ret;
    }


    /***************************************************************************
     * Vyčisti plátno, tj. vyprázdní seznam malovaných
     * (odstraní z něj všechny obrazce).
     */
    public synchronized void odstraňVše()
    {
        //nekresli();
        ListIterator it = předměty.listIterator();
        while( it.hasNext() ) {
            it.next();
            it.remove();
        }
        překresli();
        //vraťKresli();
    }


    /***************************************************************************
     * Není-li zadaný obrazec v seznamu malovaných, přidá jej na konec
     * (bude se kreslit jako poslední, tj. na vrchu.
     * Byl-li obrazec opravdu přidán, překreslí plátno.
     *
     * @param  obrazec  Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public synchronized boolean přidej(IKreslený obrazec)
    {
        if( předměty.contains(obrazec) )
        {
            return false;
        }
        else
        {
            předměty.add(obrazec);
            překresli();
            return true;
        }
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * nad zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  současný  Obrazec, který má byt při kreslení pod
     *                    přidávaným obrazcem
     * @param  přidaný   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public synchronized boolean přidejNad(IKreslený současný, IKreslený přidaný )
    {
        boolean nebyl = ! předměty.remove(přidaný);
        int kam = předměty.indexOf( současný );
        if( kam < 0 )
        {
            throw new IllegalArgumentException(
                "Referenční objekt není na plátně zobrazován!" );
        }
        předměty.add( kam+1, přidaný );
        překresli();
        return nebyl;
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * pod zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  současný  Obrazec, který má byt při kreslení nad
     *                   přidávaným obrazcem
     * @param  přidaný   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public synchronized boolean přidejPod(IKreslený současný, IKreslený přidaný)
    {
        boolean nebyl = ! předměty.remove(přidaný);
        int kam = předměty.indexOf( současný );
        if( kam < 0 )
        {
            throw new IllegalArgumentException(
                "Referenční objekt není na plátně zobrazován!" );
        }
        předměty.add( kam, přidaný );
        překresli();
        return nebyl;
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * nad všemi obrazci.
     * Pokud již v seznamu byl, jenom jej přesune do požadované pozice.
     *
     * @param  přidaný   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public synchronized boolean přidejNavrch( IKreslený přidaný)
    {
        boolean nebyl = ! předměty.remove(přidaný);
        předměty.add( přidaný );
        překresli();
        return nebyl;
    }


    /***************************************************************************
     * Přidá obrazec do seznamu malovaných tak, aby byl kreslen
     * pod zadaným obrazcem.
     * Pokud již v seznamu byl, jenom jej přesune do zadané pozice.
     *
     * @param  přidaný   Přidávaný obrazec
     *
     * @return  true  v případě, když byl obrazec opravdu přidán,
     *          false v případě, když již mezi zobrazovanými byl
     *                a pouze se přesunul do jiné urovné
     */
    public synchronized boolean přidejDospod( IKreslený přidaný)
    {
        boolean nebyl = ! předměty.remove(přidaný);
        předměty.add( 0, přidaný );
        překresli();
        return nebyl;
    }


    /***************************************************************************
     * Vrátí pořadí zadaného prvku v seznamu kreslených prvků.
     * Prvky se přitom kreslí v rostoucím pořadí, takže obrazec
     * s větším poradím je kreslen nad obrazcem s menším poradím.
     * Neni-li zadaný obrazec mezi kreslenými, vrátí -1.
     *
     * @param  obrazec  Objekt, na jehož kreslicí pořadí se dotazujeme
     *
     * @return  Pořadí obrazce; prvý kresleny obrazec má pořadí 0.
     *           Neni-li zadaný obrazec mezi kreslenými, vrátí -1.
     */
    public synchronized int pořadí(IKreslený obrazec)
    {
        return předměty.indexOf( obrazec );
    }


    /***************************************************************************
     * Vrátí řetězec s názvy všech spravovaných obrázků.
     *
     * @return  požadovaný řetězec
     */
    public String názvyKreslených()
    {
        int i = 0;
        StringBuffer sb = new StringBuffer();
        ListIterator ii=předměty.listIterator(předměty.size()-1);
        ii.next();
        while( ii.hasPrevious() )
        {
            IKreslený ik = (IKreslený)(ii.previous());
            sb.append(i++).append(". ").append(ik).append('\n');
        }
        return sb.toString();
    }


    /***************************************************************************
     * Vrátí nemodifikovatelný seznam všech spravovaných obrázků.
     *
     * @return  Požadovaný seznam
     */
    public List seznamKreslených()
    {
        return Collections.unmodifiableList( předměty );
    }


    /***************************************************************************
     * Přihlásí posluchače událostí klávesnice.
     */
    public void přihlašKlávesnici( KeyListener posluchač )
    {
        okno.addKeyListener( posluchač );
    }


    /***************************************************************************
     * Odhlásí posluchače klávesnice.
     */
    public void odhlašKlávesnici( KeyListener posluchač )
    {
        okno.removeKeyListener( posluchač );
    }


    /***************************************************************************
     * Přihlásí posluchače událostí myši.
     */
    public void přihlašMyš( MouseListener posluchač )
    {
        okno.addMouseListener( posluchač );
    }



    /***************************************************************************
     * Odhlásí posluchače myši.
     */
    public void odhlašMyš( MouseListener posluchač )
    {
        okno.removeMouseListener( posluchač );
    }



//== SOUKROMÉ A POMOCNÉ METODY =================================================
//== VNOŘENÉ A VNITŘNÍ TŘÍDY ===================================================
//== TESTY A METODA MAIN =======================================================
}

