• Nem Talált Eredményt

A FileOperator osztály reinkarnációja

In document Qt strandkönyv (Pldal 84-89)

11. Adatmentés és –visszatöltés modell használatakor

11.2. A FileOperator osztály reinkarnációja

A fileOperator objektum még sehol nincs persze, sőt, az osztálya sem, azaz, ha ki

szeretnénk próbálni, hogy eddig minden rendben-e, akkor a tagfüggvény törzsének utolsó sorát alakítsuk megjegyzéssé.

11.2. A FileOperator osztály reinkarnációja

Végre hozzákezdünk ahhoz, ami a fejezet valódi témája. Adjunk a projekthez új osztály, FileOperator néven, és ezúttal ne a QObject, hanem a QWidget legyen az ősosztály, mert, ahogy arra bizonyára emlékszünk, a fájl-választó ablakok ősének mindenképp QWidget osztályúnak kell lennie. Az osztályt a főablakból fogjuk példányosítani, és a konstruktorának egyik paramétereként adjuk át a modellünk mutatóját. Azaz az osztály konstruktora ilyesmi lesz:

FileOperator::FileOperator(QStandardItemModel *toDoListModel, QWidget

*parent) :

QWidget(parent)

{ model = toDoListModel;

timer = new QTimer(this);

}

A timer objektum természetesen az automatikus mentés miatt kell majd. Apropó,

automatikus mentés: írjuk már meg gyorsan azt a fránya FileOperator::adjustAutoSave() függvényt, aminek a hívását az imént szégyenszemre megjegyzéssé kellett alakítanunk. A tagfüggvénynek nem lesz más dolga, mint a beállítások beolvasása, és a timer objektum megfelelő felparaméterezése. Az automatikus mentés kivitelezése egy másik tagfüggvény feladata lesz majd, ami ráadásul nem csak tagfüggvény, de slot is lesz, így hozzá tudjuk kötni a timer objektum ki(el?)süléséhez. Íme a tagfüggvényünk:

Qt strandkönyv ǀ 11. Adatmentés és -visszatöltés... ǀ

void FileOperator::adjustAutoSave()

{ if(settings.value("Files/AutoSaveEnabled", "false").toBool()) timer‑>start(settings.value("Files/AutoSaveMinutes").toInt()

*60000);

elsetimer‑>stop();

}

Végezzük el a FileOperator osztály példányosítását a főablakban. Olyan helyen kell megejtenünk, ahol már van modell, de még a restoreApplicationState() tagfüggvény hívása előtt, mert különben nem indul be az automatikus mentés.

Ha eddig minden klappol, nekikezdünk a mentést végző tagfüggvény elkészítésének. Ezúttal a fájlnevet paraméterként kapja meg. A fájl megnyitása és használatba vétele a már megismert módon történik, és a modell mentése közben figyelünk arra, hogy üres teendőjű feladatot – azaz az olyan sorokat, amelyek első oszlopa üres – nem mentünk. Sikeres mentés esetén a beállításokban tároljuk a fájlnevet. A hibaüzeneteket ezúttal külön tagfüggvényben helyezzük el, hiszen mentéskor és megnyitáskor nagyon hasonlóan magyaráztuk el a felhasználónak, hogy baj történt. A hibajelzést végző tagfüggvény paraméterként kapja meg, hogy mikor is (mentéskor vagy megnyitáskor) történt baleset. A mentést végző privát tagfüggvény végül ilyenre sikeredett:

bool FileOperator::performFileSaveOperation(QString fileName) { QFile file(fileName);

bool success = false;

if (file.open(QFile::WriteOnly | QFile::Truncate | QFile::Text)) { QTextStream out(&file);

for(int i = 0; i < model‑>rowCount(); i++){

QString job = model‑>index(i,0).data().toString().

simplified();

if(!job.isEmpty())

out << job << "|" << model‑>index(i,1).data().

toString() << endl;

}

if(!file.error()) success = true;

}if(success){

settings.setValue("Files/LastFileName", fileName);

}else{

fileOperationErrorMessage(tr("Save"));

}return success; //can be "false" too }

Gyorsan megírjuk a fileOperationErrorMessage() privát tagfüggvényt is:

Qt strandkönyv ǀ 11. Adatmentés és -visszatöltés... ǀ

oid FileOperator::fileOperationErrorMessage(QString operation) { QMessageBox mb;

mb.setIcon(QMessageBox::Critical);

mb.setText(operation + " " + tr("not succeeded."));

mb.setInformativeText(tr("Try and do something wise."));

mb.exec();

}

Az előző alkalommal úgy gondolkodtunk, hogy a felhasználó alighanem az előző fájl mellé mentené az újat is, ha meg nem volt előző fájl, akkor valahova a saját cókmókja közé. Ráadásul azt is feltételeztük, hogy hasonló gondolatmenet fut át az agyán a mentett fájl megnyitásakor is: hátha az előző mellől nyitna meg másikat, mert mondjuk az imént mellékattintott. Így aztán mindkét alkalommal ilyen megfontolásból kiindulva állítottuk elő azt az elérési útvonalat, amelyet felhasználva megnyitottuk a fájlválasztó párbeszédablakot. Ha viszont kétszer is kellett, ugye van értelme külön tagfüggvénybe pakolni a fejtegetést? A nagyrabecsült Olvasó gesztusát bólintásként értelmezzük, és már itt is az alapértelmezett elérési útvonalat javasló privát tagfüggvény:

QString FileOperator::suggestDefaultPath() { QString path = QDir::homePath();

QString lastFileName = settings.value("Files/LastFileName", "").

toString();

if(lastFileName != ""){

QFileInfo info(lastFileName);

path = info.absolutePath();

}return path;

}

Minden készen áll ahhoz, hogy megírjuk azt a tagfüggvényt is, amelyik előbb beszerzi a szükséges fájlnevet, és aztán ennek ismeretében végezteti el a mentést. A múltkor jól bevált módszer szerint, ha a felhasználó nem kegyeskedett megadni a fájl kiterjesztését, akkor majd mi megadjuk. Íme:

bool FileOperator::fileSave()

{ QString newFileName = QFileDialog::getSaveFileName(

this,

tr("Save as..."), suggestDefaultPath(),

tr("ToDoList files (*.tdolst);;text files (*.txt);;all files (*)")

);

if(!newFileName.isEmpty()){

QFileInfo info(newFileName);

QString ext = info.suffix();

if(ext != "tdolst" && ext != "txt") newFileName += ".tdolst";

return performFileSaveOperation(newFileName);

}return false; }

Qt strandkönyv ǀ 11. Adatmentés és -visszatöltés... ǀ

Törjük kicsit a fejünket. Ha a felhasználó Ctrl+S billentyűkombinációt nyom, és van fájlnév (ki tudjuk olvasni a beállításokból), akkor csuklás nélkül hívjuk a performFileSaveOpration() tagfüggvényt. Ha nincs fájlné, akkor a fileSave() tagfüggvényt hívjuk, megtudjuk a fájlnevet és ennek ismeretében megint csak a

performFileSaveOpration()-ra lőcsöljük a melót. Ha a felhasználó a Save as... menüpontot választaná, akkor tekintet nélkül arra, hogy van-e fájlnevünk vagy nincs, mindenképp újat kell kérnünk, hiszen a Save as... menüpontnak pont ez a lényege. Végiggondoltunk minden lehetőséget? Talán. Akkor megírhatjuk a két QAction osztályú objektum (mármint a Save és a Save as...) slot-ját. Hol volna érdemes? A főablakban egyszerűbb, a FileOperator osztályban elegánsabb. Mi elegánsak leszünk, és publikus slot-ként a FileOperator-ban helyezzük el a két következő szösszenetet. A függvények elnevezése nagyon hasonló ahhoz, amit automatikus létrehozásukkal kapnánk, de szándékosan nem teljesen ugyanolyan, ugyanis a moc (a Meta Object Compiler) azonnal hőbörögne, hogy látja ő, hogy itt a slot, de hol a signal? (Mi persze tudjuk, hogy egy másik osztályban, de a moc-nak nincs ám annyi esze, hogy megkérdezze tőlünk.) És akkor most mihez kössön, meg micsodát? Mi meg nem bírjuk, ha valaki hőbörög, így inkább picit változtatunk a neveken.

void FileOperator::onActionSaveTriggered()

{ QString fileName = settings.value("Files/LastFileName", "").

toString();

if(!fileName.isEmpty())

performFileSaveOperation(fileName);

elsefileSave();

}

void FileOperator::onActionSaveAsTriggered() { fileSave();

}

Már csak az a dolgunk velük, hogy a főablakban kiadjuk a megfelelő connect utasításokat:

connect(ui‑>actionSave, SIGNAL(triggered()), fileOperator, SLOT(onActionSaveTriggered()));

connect(ui‑>actionSave_as, SIGNAL(triggered()), fileOperator, SLOT(onActionSaveAsTriggered()));

Természetesen nincs sok teteje a dolognak, ha még a fileOperator példányosítása elé írjuk a sorokat, de a példányosítást követően azonnal jöhetnek. Elgondolkodhatunk azon is, hogy közvetlenül a fileSave() tagfüggvényt tesszük slot-tá, és azt kötjük a Save as... menühöz. Nem sok ellenérv van ellene, talán az, hogy egyesek számára a jelenlegi forma áttekinthetőbb. Ez viszont egy igen-igen fontos érv, mindenkit meggyőzhet, kivéve esetleg azokat, akik szerint a másik forma áttekinthetőbb. A hörcsög még nem nyilvánította ki véleményét az ügyben, úgyhogy a meccs még nem lefutott.

Megnyitás következik, pedig ez a könyv nem is a sakkról szól. A megnyitást végző

tagfüggvények természetesen nagyon hasonlítanak majd a mentést végzőkhöz, és ugyanúgy párban járnak. Itt következik mind a kettő:

Qt strandkönyv ǀ 11. Adatmentés és -visszatöltés... ǀ

bool FileOperator::fileOpen()

{ QString newFileName = QFileDialog::getOpenFileName(

this,

tr("Open file..."), suggestDefaultPath(),

tr("ToDoList files (*.tdolst);;text files(*.txt);;all files (*)")

);

if(!newFileName.isEmpty())

return performFileOpenOperation(newFileName);

return false; }

bool FileOperator::performFileOpenOperation(QString fileName) { model‑>clear();

model‑>setHorizontalHeaderLabels(QStringList() << tr("job") <<

tr("category"));

QFile file(fileName);

bool success = false;

if (file.open(QIODevice::ReadOnly | QIODevice::Text)){

QTextStream in(&file);

while (!in.atEnd()){

QStringList sl = in.readLine().split("|");

QStandardItem *item = new QStandardItem(sl.at(0));

model‑>setItem(model‑>rowCount(), 0, item);

item = new QStandardItem(sl.at(1));

model‑>setItem(model‑>rowCount()‑1, 1, item);

}

if(!file.error()) success = true;

}if(success){

settings.setValue("Files/LastFileName", fileName);

QList<QStandardItem* > newRow;

model‑>appendRow(newRow);

}else{

fileOperationErrorMessage(tr("Open"));

}return success; //can be "false" too }

A performFileOpenOperation() tagfüggvényt a fileOpen() tagfüggvény hívja, és még ki? Mert, ha senki, akkor minek külön tagfüggvény? Nos, a másik hely, ahonnan hívni fogják, a program indításakori automatikus fájlbetöltés lesz. Úgyhogy van létjogosultsága a dolognak.

Viszont a fileOpen() tagfüggvény hívása körül van még egy icuri kis probléma.

Alighanem eszünkbe ötlik még, hogy a kényelem mián ModelManager osztály berkeiben megírtuk azt a slot-ot, amelyik az utolsó sor első oszlopában lévő elem megváltozásakor azonnal létrehoz egy üres sort. Hamarosan bajba is keveredtünk vele, olyankor is elő-előkerült egy-egy üres sor, amikor az legkevésbé sem szolgálta óhajainkat. Meg is írtuk a nagy ki-be kapcsolgató connectItemChangedSignalToSlot() tagfüggvényt, amelyről már akkor előrevetítettük,

Qt strandkönyv ǀ 11. Adatmentés és -visszatöltés... ǀ

hogy a fájlbetöltéskor még jó hasznát vesszük majd. Nos, ez az idő – mármint a fájlbetöltés, meg a probléma előjövetele – itt van. Szeretnénk hívni a tagfüggvényt, de ugye a fileOperator objektum nem látja. Kénytelenek leszünk szégyenszemre ezt az egy slot-ot a főablakban megvalósítani? Azt már nem!

Úgy módosítjuk a FileOperator osztály konstruktorát, hogy ne csak a modell, hanem az egész modelManager objektum mutatóját vegye át. Íme:

FileOperator::FileOperator(ModelManager *modelManager, QWidget

*parent) :

QWidget(parent)

{ this‑>modelManager = modelManager;

model = modelManager‑>toDoListModel;

timer = new QTimer(this);

}

A törzs első sora azt teszi lehetővé, hogy a FileOperator osztályon belül is

modelManager néven hivatkozhassunk az objektumunkra. Persze át kell alakítanunk a fileOperator objektumot példányosító sort is a főablak forrásában, sőt, a

ModelManager::connectItemChangedSignalToSlot() tagfüggvényt is publikussá kell tennünk, merthogy eddig privát volt, a szentem. Végre megírható a megnyitást végző slot:

void FileOperator::onActionOpenTriggered()

{ modelManager‑>connectItemChangedSignalToSlot(false);

fileOpen();

modelManager‑>connectItemChangedSignalToSlot(true);

}

És, igen, most sem hagyjuk el a connect utasítást a főablakból:

connect(ui‑>actionOpen, SIGNAL(triggered()),

fileOperator, SLOT(onActionOpenTriggered()));

Akkor ez is megvan. Bueno.

11.3. Utolsó lehetőség a mentésre – a QMessageBox osztály további

In document Qt strandkönyv (Pldal 84-89)