Összetett alkalmazások

Hasonló dokumentumok
Eseményvezérelt alkalmazások fejlesztése I 7. előadás. Összetett grafikus felületű alkalmazások. Giachetta Roberto

Eseményvezérelt alkalmazások fejlesztése I 6. előadás. Összetett alkalmazások megvalósítása. Giachetta Roberto

Szoftvertechnológia 7. előadás. Objektumorientált tervezés: végrehajtás. Giachetta Roberto. Eötvös Loránd Tudományegyetem Informatikai Kar

Programozási technológia II 3. előadás. Objektumorientált tervezés Giachetta Roberto

Szoftvertechnológia 6. előadás. Objektumorientált tervezés: állapotkezelés. Giachetta Roberto. Eötvös Loránd Tudományegyetem Informatikai Kar

2. Beadandó feladat dokumentáció

Szoftvertechnológia 5. előadás. Objektumorientált tervezés: architektúra. Giachetta Roberto. Eötvös Loránd Tudományegyetem Informatikai Kar

2. Beadandó feladat dokumentáció

Programozási technológia 2.

Elemi alkalmazások fejlesztése III.

3. Beadandó feladat dokumentáció

Dinamikus felületű alkalmazások. Stílusok, időzítő, képek

Elemi alkalmazások fejlesztése III.

Eseményvezérelt alkalmazások fejlesztése I 3. előadás. Dinamikus felületű alkalmazások. Giachetta Roberto

3. Beadandó feladat dokumentáció

Elemi alkalmazások fejlesztése III.

Alkalmazások fejlesztése III. Qt 4 /C++ alapú MDI alkalmazás: Számlakészítő program 3/3

ContractTray program Leírás

1. Beadandó feladat dokumentáció

DebitTray program Leírás

ClicXoft programtálca Leírás

Eseménykezelés. Aszinkron kommunikáció

Thermo1 Graph. Felhasználói segédlet

Johanyák Zsolt Csaba: Ugráló gomb oktatási segédlet Copyright 2008 Johanyák Zsolt Csaba

Eseményvezérelt alkalmazások fejlesztése I 11. előadás. Szoftverek tesztelése

3. Beadandó feladat dokumentáció

ServiceTray program Leírás

Grafikus felhasználói felületek. Dr. Szendrei Rudolf Informatikai Kar Eötvös Loránd Tudományegyetem. Programozási technológia I. Dr.

HVK Adminisztrátori használati útmutató

Alkalmazások fejlesztése III. Qt 4 /C++ alapú grafikus alkalmazás - Memóriajáték 1/2

Két csomag elemeiből lehet a felületet elkészíteni: awt: heavy weight komponensek; swing: light weight komponensek (időben később).

Választó lekérdezés létrehozása

ELTE, Informatikai Kar december 12.

9. óra operációs rendszerek. Grafikus operációs rendszer

Webes alkalmazások fejlesztése 4. előadás. Megjelenítés és tartalomkezelés (ASP.NET)

Image Processor BarCode Service. Felhasználói és üzemeltetői kézikönyv

ESEMÉNY VEZÉRELT ALKALMAZÁSOK FEJLESZTÉSE I. Bevezetés. Készítette: Gregorics Tibor

DKÜ ZRT. A Portál rendszer felületének általános bemutatása. Felhasználói útmutató. Támogatott böngészők. Felületek felépítése. Információs kártyák

Webes alkalmazások fejlesztése 4. előadás. Megjelenítés és tartalomkezelés (ASP.NET) Cserép Máté.

Elemi alkalmazások fejlesztése III.

Mobil Partner telepítési és használati útmutató

Programozási technológia

Szoftvertechnológia 8. előadás. Szoftverrendszerek tervezése Giachetta Roberto

Models are not right or wrong; they are more or less useful.

A jobboldalon a pnlright egy Stacked Widget Állítsuk be az első lapot és nevezzük el pnldraw-ra:

A Windows az összetartozó adatokat (fájlokat) mappákban (könyvtárakban) tárolja. A mappák egymásba ágyazottak.

TERC V.I.P. hardverkulcs regisztráció

Windows mappaműveletek

Ablak és ablakműveletek

VARIO Face 2.0 Felhasználói kézikönyv

A CCL program használatbavétele

Felhasználói leírás a DimNAV Server segédprogramhoz ( )

3D-s számítógépes geometria és alakzatrekonstrukció

Outlook Express használata

Grafikus felhasználói felület (GUI) létrehozása A GUI jelentése Egy egyszerű GUI mintaalkalmazás létrehozása

MKOSZ Online Support - Felhasználói

Netlock Kft. által kibocsátott elektronikus aláírás telepítése Windows XP SP3 Internet Explorer 8 böngészőbe

Elemi alkalmazások fejlesztése III

WIFI elérés beállítása Windows XP tanúsítvánnyal

A TERC VIP költségvetés-készítő program telepítése, Interneten keresztül, manuálisan

Mio Technology Limited C510, C710. Gyors használati utasítás a Mio Map v3 programhoz. Magyar

Szoftvertechnológia 8. előadás. Szoftverrendszerek tervezése. Giachetta Roberto. Eötvös Loránd Tudományegyetem Informatikai Kar

Programozási technológia

Adabáziselérés ODBC-n keresztül utasításokkal C#-ban

1. beadandó feladat: egyszerű grafikus felületű alkalmazás. Közös követelmények:

ÉVI ADATSZOLGÁLTATÁSOK JAVÍTÁSA. Készítette: Tóth Péter szeptember 26.

components : IContainer dx : int dy : int tmidőzítő : Timer toolstripseparator1 : ToolStripSeparator tsmikilépés : ToolStripMenuItem

Az operációs rendszer fogalma

Operációs rendszerek. Tanmenet

Miről lesz szó? Setup Project készítése. Tulajdonságok. 1. Készítsünk egy setup project alkalmazást egy már elkészített, lefordított programhoz.

Projektmenedzsment tréning

Win-Tax évi ös verzió

Elemi alkalmazások fejlesztése III.

GráfRajz fejlesztői dokumentáció

Elemi Alkalmazások Fejlesztése Beadandó Feladat Juhász Ádám

Számítógépes alapismeretek 2.

Belépés a GroupWise levelező rendszerbe az Internet felől

Felhasználói segédlet a Scopus adatbázis használatához

Microsoft Excel 2010

Országos Területrendezési Terv térképi mel ékleteinek WMS szolgáltatással történő elérése, Quantum GIS program alkalmazásával Útmutató 2010.

13. Tárgymutató. Windows XP alapokon

A CAPICOM ActiveX komponens telepítésének és használatának leírása Windows 7 operációs rendszer és Internet Explorer 9 verziójú böngésző esetén

Eseményvezérelt alkalmazások fejlesztése II 5. előadás. Windows Forms alkalmazások párhuzamosítása. Giachetta Roberto

Az alapértelmezett felhasználói név az "abc", a jelszó pedig "123". Ez célszerű megváltoztatni a felhasználónevet és a jelszót az első használat.

Operációs rendszerek. Az operációs rendszer feladatai

SMS küldő központ Leírás

Tervezőeszközök, fejlesztőeszközök használata Qt alapú alkalmazásoknál. Saját vezérlő használata tervezőben (worldclocks)

Swing GUI készítése NetBeans IDE segítségével

1.1.1 Dátum és idő függvények

A szerzõrõl... xi Bevezetés... xiii

évi adatszolgáltatások javítása

Programozási technológia I. programból! A Gomb4 megoldásból induljunk ki!

QGIS tanfolyam (ver.2.0)

Felhasználói kézikönyv - Android kliens

CitiDirect BE SM Felhasználói útmutató

OTP Egészségpénztár OTP Nyugdíjpénztár OTP SZÉP Kártya OTP Cafeteria Nyilatkoztató. Készítette: Konyicsák Zoltán

BioAdmin 4.1 könnyű telepítés csak Kliens használatra

Tájékoztató a kollégiumi internet beállításához

Algoritmus terv 3. Fejezet: Folyamatok meghatározása

Átírás:

Összetett alkalmazások

Ablakok A grafikus felületű alkalmazásokban a vezérlőket ablakokra helyezzük ablaknak minősül bármely vezérlő, amely egy QWidget, vagy bármely leszármazottjának példánya, és nincs szülője adottak speciális ablaktípusok is, pl.: üzenőablak (QMessageBox), elsősorban üzenetek közlésére, vagy kérdések feltételére dialógusablak (QDialog), amelynek eredménye van, elfogadható (accept), vagy elutasítható (reject) főablak (QMainWindow), amely számos kiegészítést biztosít összetett ablakok megvalósítására 2

Főablak A főablak (QMainWindow) egy olyan speciális ablaktípus, amely megkönnyíti összetett, speciális vezérlőket tartalmazó ablakok létrehozását, úgymint menüsor (Menu Bar): menüpontok gyűjteménye az ablak tetején státuszsor (Status Bar): állapotkijelző sor az ablak alján eszköztár (Toolbar): ikongyűjteményeket tartalmazó funkciógombok, amely az ablak bármely szélére elhelyezhetőek Az ablakon belül további vezérlőket helyezhetünk el, amelyeket dokkolhatunk az ablak széléhez, vagy középre 3

Főablak menüsor eszköztárak dokkolt vezérlők központi vezérlő státuszsor 4

Akciók A különböző vezérlők sokszor ugyanazon funkciókat biztosítják más formában (ikon, szöveg, gyorsbillentyű) A funkciókat egységesen akcióként (QAction) kezelhetjük, amely rendelkezik felirattal (text), ikonnal (icon), gyorsbillentyűvel (shortcut), segédüzenettel (statustip) lehetőséget ad kijelölésre (checked), valamint billentyűs gyorsnavigálásra (az & karakterrel) kiváltható billentyűzettel vagy egérrel, a kiváltás eseménye: triggered felhelyezhető tetszőleges menüre, illetve eszköztárra 5

Példa // ikon és név megadása, a j billentyűre gyorsnavigál a menüben: QAction newact = new QAction(QIcon("new.png"), tr("ú&j"), this); // a keretrendszer által kirendelt "új" billentyűkombináció: newact->setshortcuts(qkeysequence::new); // QKeySequence(Qt::CTRL + Qt::Key_N) newact->setstatustip(tr("új fájl létrehozása")); // eseménykezelő társítás: connect(newact, SIGNAL(triggered()),this, SLOT(newFile())); // felhelyezés: filemenu->addaction(newact); filetoolbar->addaction(newact); 6

Menü A menüt (QMenu) a főablak menubar tulajdonságán keresztül kezelhetjük, a menühöz felvehetünk almenüket, akciókat és elválasztókat (separator). A menük tetszőlegesen egymásba ágyazhatóak. QMenu filemenu = this->menubar()->addmenu(tr("&fájl")); // új almenü létrehozása filemenu->addaction(newact); // menüpont felvétele filemenu->addspearator(); // elválasztó filemenu->addmenu(tr("&legutóbbi fájlok")); // beágyazott almenü 7

Eszköztár Eszköztárakból (QToolBar) tetszőlegesen sokat vehetünk fel, amelyek alapértelmezetten az ablak tetején jelennek meg. Ikonok sorozatát adják, esetleges elválasztókkal szeparálva. Az eszköztárak alapértelmezés szerint utólag áthelyezhetőek bármely szélére az ablaknak, illetve lehetnek lebegő (floating) állapotban is. QToolBar filebar = this->addtoolbar(tr("fájl")); // új eszköztár felvétele filebar->addaction(newact); // új akció felvétele filebar->addspearator(); // elválasztó 8

Státuszsor és tartalom A státuszsor (QStatusBar) alapvetően státuszüzenetek kiírására szolgál, ugyanakkor bármilyen vezérlő ráhelyezhető üzenetet kiírni a showmessage(<üzenet>) utasítással tudunk, törölni a clearmessage() utasítással pl.: this->statusbar()->showmessage(tr("kész")); Az ablak területére célszerű egy külön vezérlőben elhelyezni a tartalmat, ez a központi vezérlő (centralwidget) Amennyiben több tartalmat helyeznénk az ablakra, lehetőségünk van azokat dokkolni a QDockWidget osztály segítségével, amelyet az adddockwidget(<vezérlő>) művelettel helyezhetünk az ablakra 9

Alkalmazásszintű tulajdonságok A Qt alkalmazásokat minden esetben egy alkalmazás (QApplication) objektum vezérli, amely számos értéket tárol, úgymint: alkalmazás információk (applicationname, orgnizationname, applicationversion), környezeti információk (applicationdirpath, arguments, keyboardmodifiers, clipboard), grafikus környezeti adatok (allwindows, windowicon, palette, stylesheet, font). Az alkalmazás értékeihez bárhonnan, statikus műveletekkel hozzáférhetünk. 10

Példa QApplication::setOrganizationName("MySoft"); QApplication::setApplicationName("MyApp"); // beállítunk némi információt QString executablename = QApplication::arguments()[0]; // lekérjük a programnevet if (QApplication::arguments().size() > 1){ // ha még van ezen felül argumentum QString arg1 = QApplication::arguments()[1]; } 11

Beállítások kezelése Nagyobb alkalmazások olyan alkalmazásszintű beállításokkal rendelkeznek, amelyek elmenthetünk, és újabb futtatáskor betöltenünk. A beállítások eltárolhatóak egyedileg, de használhatjuk a beépített QSettings osztályt, amely egyszerűsíti a beállítások kezelését. A beállítások eltárolásának módja platformonként változik (Linux esetén konfigurációs fájlok, Windows esetén regisztrációs adatbázis), ezt az osztály elfedi, így a programozónak a tárolás módjával nem kell törődnie. A beállítások egy adott alkalmazásra és felhasználóra vonatkoznak. A beállításokba kulcs/érték párokat vehetünk fel a setvalue(<kulcs>,<érték>) utasítással, ahol a kulcs szöveges, az érték tetszőleges QVariant lehet. a value(<kulcs>) utasítás lekérdez a contains(<kulcs>) függvény ellenőrzi a kulcs létezését A QVariant egy általános típus, amely a primitív típusokat tud becsomagolni, és rendelkezik konverziós műveletekkel (toint(), value<típus>()). 12

Erőforrások A főablakon használt akciókat célszerű ellátni ikonokkal, amelyeket az alkalmazáshoz kell, hogy csatoljunk Az alkalmazáshoz használt ikonokat és egyéb nem kód tartalmat lehetőségünk van erőforrásként (resource) csatolni az alkalmazáshoz az erőforrások tartalma belefordul a futtatandó állományba, így nem kell külön másolni őket az erőforrásokat a projekthez tartozó.qrc fájlban nevezhetjük meg az erőforrásként megadott fájlokat a :<elérési útvonal> hivatkozással hívhatjuk be QIcon(":/images/new.png"); // erőforrás elérése 13

Feladat Készítsünk egy Memory kártyajátékot, amelyben két játékos küzd egymás ellen. A játékmezőn kártyapárok találhatóak, és a játékosok feladata ezek megtalálása. A játék különböző kártyacsomagokkal játszható, amelyek könyvtárakból tölthetőek be, minden ilyen könyvtárban található egy name.txt, ami a csomag nevét tartalmazza, és tetszőleges számú kép (ezek a kártyák), valamint egy hátlap (back fájlnévvel). Lehetőségünk van egy beállító ablakban megadni a kiválasztott kártyacsomagot, valamint a játéktábla méretét (csak páros méretű, de legalább 4 kártyából álló lehet), valamint a játékosok neveit. 14

Feladat folytatása Kezdetben minden kártya le van fordítva, a játékosok felváltva lépnek, minden lépésben felfordíthatnak két kártyát. Amennyiben a kártyák egyeznek, úgy felfordítva maradnak és a játékos ismét léphet, különben 1 másodperc múlva visszafordulnak, és a másik játékos következik. A játékot az nyeri, aki több kártyapárt talált meg. Megnyert játékok számát göngyölítve jelenítjük meg, amíg új játékosokat nem állítunk be. A felületen folyamatosan megjelenítjük a játékosok adatait (sikeres, sikertelen lépések száma, megnyert játszmák száma). 15

Elemzés Első kártya felfordítása Lépés «include» «precedes» Új játék «include» «precedes» Második kártya felfordítása Nevek megadása «precedes» Beállítások «include» Felhasználó «include» Táblaméret megadása «include» Kilépés Kártyacsomag megadása 16

Architektúra A játékot kétrétegű (M/V) architektúrában valósítjuk meg. A modell tartalmazza: magát a játékot, amit egy kezelőosztály felügyel (GameManager), valamint hozzá segédosztályként a játékost (Player), a kártyacsomagokat (CardPack). A nézet tartalmazza: a főablakot (MainWindow) menüvel és státuszsorral, a beállítások segédablakát (ConfigurationDialog), a játékfelületet megjelenítő vezérlőt (GameWidget), amely tartalmazza a játékmezővel kapcsolatos tevékenységeket, a felhasználói információkat kiíró vezérlőt (PlayerStatusWidget, ezt előléptetett vezérlővel állítjuk be a felülettervezőben), valamint a képet megjeleníteni tudó egyedi gombot (ImageButton). Egy csomag kártyát erőforrásként csatolunk az alkalmazáshoz (packs.qrc), hogy mindig legyen legalább egy ilyen. 17

Csomag diagram 18

Modell osztályai CardPack - _name :QString - _faces :QVector<QPixmap> - _back :QPixmap + CardPack(QString&) + getname() :QString {query} + cardcount() :int {query} + getfaces() :QVector<QPixmap> {query} + getback() :QPixmap& {query} + getface(int) :QPixmap& {query} -_cardpacks 1..* GameManager - _cardpacks :QVector<CardPack*> - _players :QVector<Player*> - _currentnumcols :int - _currentnumrows :int - _currentcardpackindex :int - _currentplayerindex :int - _timer :QTimer* - _cardfound :QVector<bool> - _foundcards :int - _cardids :QVector<int> - _firstcardindex :int - _secondcardindex :int QObject Player - _name :QString - _hits :int - _misses :int - _victories :int + Player(QString) + newgame() :void + addhit() :void + addmiss() :void + addvictory() :void + getname() :QString {query} + gethits() :int {query} + getmisses() :int {query} + getvictories() :int {query} -_players 2 + allcardpacks() :QVector<CardPack*> {query} + currentpack() :CardPack* {query} + currentplayer() :Player* {query} + firstplayer() :Player* {query} + GameManager() + ~GameManager() - loadpacks(qstring) :void + secondplayer() :Player* {query} - shufflecards() :void «signal» + cardchanged(int, QPixmap&) :void + gameover(qstring) :void + statuschanged(qstring) :void «slot» - hidecards() :void + newgame(int, int) :void + selectcard(int) :void + setcurrentpack(cardpack*) :void + setplayers(qstring, QString) :void 19

Nézet osztályai -configurationdialog ConfigurationDialog - _ui :Ui::ConfigurationDialog* - _cardpacks :QVector<CardPack*> - _cardlabels :QVector<QLabel*> QDialog + ConfigurationDialog(QVector<CardPack*>, QWidget*) + ~ConfigurationDialog() + firstplayername() :QString + secondplayername() :QString + numberofrows() :int + numberofcolumns() :int + selectedcardpack() :CardPack* - loadsettings() :void - savesettings() :void «slot» + setfirstplayername(qstring) :void + setsecondplayername(qstring) :void + setnumberofrows(int) :void + setnumberofcolumns(int) :void + checkvalues() :void + changecardpack(int) :void + setmaxrows() :void + setmaxcols() :void GameWidget - _ui :Ui::GameWidget* - _manager :GameManager* - _buttons :QVector<ImageButton*> - _isconfigured :bool - _configurationdialog :ConfigurationDialog* QWidget + GameWidget(QWidget*) + ~GameWidget() + gamemanager_cardchanged(int, QPixmap&) :void + gamemanager_gameover(qstring) :void «signal» + statuschanged(qstring) :void «slot» + newgame() :void + configuregame() :void + buttonclicked() :void -buttons 4..* - _image :QPixmap QPushButton ImageButton + ImageButton(QWidget*) + pixmap() :QPixmap {query} # paintevent(qpaintevent*) :void «slot» + setpixmap(qpixmap&) :void + clearpixmap() :void 2 -gamewidget QWidget PlayerStatusWidget - _ui :Ui::PlayerStatusWidget* - _player :Player* + PlayerStatusWidget(QWidget*) + ~PlayerStatusWidget() «slot» + setplayer(player*) :void + refreshplayer() :void QMainWindow MainWindow - _newgameaction :QAction* - _exitaction :QAction* - _configureaction :QAction* - _gamemenu :QMenu* - _gamewidget :GameWidget* + MainWindow(QWidget*) + ~MainWindow() # closeevent(qcloseevent*) :void 20

Viselkedés Új játék indításához először a főablakban (MainWindow) kell kiváltanunk (triggered) a megfelelő akciót (newgameaction). Ennek hatására a főablak új játékot indít (newgame) a játék nézetében (GameWidget). A nézet beállítja a játék paramétereit (configuregame). A nézet létrehozza az új játékot (newgame) a modellben (GameManager). A modell megkeveri a kártyákat (shufflecards), majd eseménnyel jelzi az állapot változását (changestatus). 21

Szekvencia diagram MainWindow GameWidget GameManager user newgameaction.triggered() newgame() configuregame() newgame(rows, cols) changestatus(message) shufflecards() changestatus(message) 22

Viselkedés Amennyiben új játékot kezdünk (newgame), a felület aktív lesz, játék végén (gameover) pedig inaktívvá válik. A játék modellje kezdetben egy kártyát sem mutat, de új játék kezdésekor (newgame) az összes kártyát megmutatja, majd automatikusan elrejti őket (hidecards). Kiválasztás (selectcard) hatására előbb egyet, majd kettőt megmutathat (cardchanged). Amennyiben a két kártya egyezik, és minden kártyát felfedtünk, vége a játéknak (gameover). 23

Állapotgép bármikor kiléphetünk, új játékot kezdhetünk, változtathatunk a beállításokon GameManager selectcard(i) [ not foundcards[i] / cardchange(i) one cards revealed selectcard(i) [ not foundcards[i] / cardchange(i); timerstart() all cards hidden two cards revealed timeout / hidecards() [all cards revealed] some cards hidden 24

Megvalósítás MainWindow::MainWindow() { connect(_newgameaction, SIGNAL(triggered()), _gamewidget, SLOT(newGame())); connect(_configureaction, SIGNAL(triggered()), _gamewidget, SLOT(configureGame())); connect(_exitaction, SIGNAL(triggered()), this, SLOT(close())); } connect(_gamewidget, SIGNAL(statusChanged(QString)), this->statusbar(), SLOT(showMessage(QString))); 25

Megvalósítás GameWidget::GameWidget(QWidget *parent) : QWidget(parent), _ui(new Ui::GameWidget) { _ui->setupui(this); _manager = new GameManager(); _configurationdialog = 0; _isconfigured = false; // kezdetben nincs konfigurálva a játék } connect(_manager, SIGNAL(statusChanged(QString)), a logikai réteg szignálja egy this, SIGNAL(statusChanged(QString))); újabb szignált vált ki connect(_manager, SIGNAL(statusChanged(QString)), _ui->firstplayerstatus, SLOT(refreshPlayer())); connect(_manager, SIGNAL(statusChanged(QString)), _ui->secondplayerstatus, SLOT(refreshPlayer())); connect(_manager, SIGNAL(cardChanged(int,QPixmap)), this, SLOT(gameManager_CardChanged(int, QPixmap))); connect(_manager, SIGNAL(gameOver(QString)), this, SLOT(gameManager_GameOver(QString))); 26

Megvalósítás ConfigurationDialog(QVector<CardPack*> cps, QWidget *parent) : QDialog(parent), _ui(new Ui::ConfigurationDialog), _cardpacks(cps) { _ui->setupui(this); foreach(cardpack* pack, _cardpacks) _ui->combocardpack->additem(pack->getname()); connect(_ui->combocardpack, SIGNAL(currentIndexChanged(int)), this, SLOT(changeCardPack(int))); connect(_ui->spinrows, SIGNAL(valueChanged(int)), this, SLOT(setMaxCols())); connect(_ui->spincols, SIGNAL(valueChanged(int)), this, SLOT(setMaxRows())); connect(_ui->buttonok, SIGNAL(clicked()), this, SLOT(checkValues())); connect(_ui->buttoncancel, SIGNAL(clicked()), this, SLOT(reject())); if (_cardpacks.size() > 0){ _ui->combocardpack->setcurrentindex(0); changecardpack(0); } loadsettings(); } 27

Megvalósítás GameManager::GameManager() { loadpacks(qapplication::applicationdirpath() + QDir::separator() + "packs"); _currentplayerindex = 0; } _timer = new QTimer(this); _timer->setinterval(1000); connect(_timer, SIGNAL(timeout()), this, SLOT(hideCards())); void GameManager::newGame(int numrows, int numcols){ statuschanged(trutf8("új játék elindítva, ") + players[currentplayerindex]->getname() szignál kiváltása + trutf8(" következik.")); } } 28