Qtexteditor SIMPLE SDI Application

الناقل : elmasry | الكاتب الأصلى : ahmed_youssef | المصدر : www.arabteam2000-forum.com

SDI Application
Posted Image


لاحظ إن ال Application عبارة عن
1 – Main Window
2 – MenuBar بتشمل File, Edit, Help menus
3- ToolBar 2
الأول هو fileToolBar وفيه new, open and save
التانى هو editToolBar وفيه cut, copy and paste

4- TextEdit
5- StatusBar


هنحتاج نعمل 3 ملفات لل Application
1- mainwindow.h فيه ال class design
2- mainwindow.cpp فيه ال class implementation
3- main.cpp فيه ال entry point لل Application

mainwindow.h

بداية قبل اى شئ

#ifndef MAINWINDOW_H
 #define MAINWINDOW_H

هنحتاج نعمل include ل QMainWindow
#include <QMainWindow>
عشان نورثها لل mainwindow class
class MainWindow : public QMainWindow


لاحظ إننا زى ماقلنا هنستخدم menu, toolbar, actions
class QAction;
 class QMenu;
 class QTextEdit;


ملحوظة ال actions هى العناصر اللى بتكون ال menus وال toolbars !

هنعمل فى ال class دا Override لل virtual closeEvent method وهى method بت fired كل ميحصل محاولة ل قفل ال window فإحنا هنعمل ليها new definition عشان نستخدم ال
famous message “you wanna quit !? “

ال class هيكون ليه slots خاصة فيه يبقة لازم نعرف الmacro Q_OBJECT!

ال slots هتكون إيه ؟
newFile, save, saveAs, about, documentWasModified
ال newFile هنستخدمها عشان نبدأ فى new file
ال save/save as عشان نحفظ الملف بيهم
ال about هنستخدمها عشان نظهر window بتتكلم عن البرنامج وهناك هنستخدم qtabout الموجودة فى Qapplication!
ال documentWasModified عشان تشير هل الملف اتعدل فيه ولا لأ عشان نستخدم save or not MSG

ندخل فى ميثودز ال class نفسها بقة

1- init()
2- createActions()
3- createMenus()
4- createToolBars()
5- createStatusBar()
6- bool maybeSave()
7- loadFile()
8- bool saveFile()
9- setCurrentFile()
10- QString strippedName()
11- QMainWindow findMainWindow()

احنا ممكن نخلى ال GUI فى ال Constructor بس المشكلة الوحيدة هى إننا عندنا 2 Constructors فمش عايزين نكرر نفسنا فنعمل ميثود لل init window
strippedName بترجع لينا اسم الفيل بدون ال full name


ال fields
QTextEdit *textEdit;
         QString curFile;
         bool isUntitled;
         QMenu *fileMenu;
         QMenu *editMenu;
         QMenu *helpMenu;
         QToolBar *fileToolBar;
         QToolBar *editToolBar;
         QAction *newAct;
         QAction *openAct;
         QAction *saveAct;
         QAction *saveAsAct;
         QAction *closeAct;
         QAction *exitAct;
         QAction *cutAct;
         QAction *copyAct;
         QAction *pasteAct;
         QAction *aboutAct;
         QAction *aboutQtAct
;
وهى عبارة عن الActions/Menus/ToolBars
isUntitled لتشير هل الملف لسه اول مرة ومش اتحفظ بإسم ولا لأ
curFile لتشير للملف الحالى
وبكدا يبقة خلص ال header
الصورة النهائية ليه
#ifndef MAINWINDOW_H
 #define MAINWINDOW_H
 #include <QMainWindow>
 class QAction;
 class QMenu;
 class QTextEdit;
 class MainWindow : public QMainWindow
 {
         Q_OBJECT
 public:
         MainWindow();
         MainWindow(const QString &fileName);
 protected:
         void closeEvent(QCloseEvent *event);
 private slots:
         void newFile();
         void open();
         bool save();
         bool saveAs();
         void about();
         void documentWasModified();
 private:
         void init();
         void createActions();
         void createMenus();
         void createToolBars();
         void createStatusBar();
         void readSettings();
         void writeSettings();
         bool maybeSave();
         void loadFile(const QString &fileName);
         bool saveFile(const QString &fileName);
         void setCurrentFile(const QString &fileName);
         QString strippedName(const QString &fullFileName);
         MainWindow *findMainWindow(const QString &fileName);
         QTextEdit *textEdit;
         QString curFile;
         bool isUntitled;
         QMenu *fileMenu;
         QMenu *editMenu;
         QMenu *helpMenu;
         QToolBar *fileToolBar;
         QToolBar *editToolBar;
         QAction *newAct;
         QAction *openAct;
         QAction *saveAct;
         QAction *saveAsAct;
         QAction *closeAct;
         QAction *exitAct;
         QAction *cutAct;
         QAction *copyAct;
         QAction *pasteAct;
         QAction *aboutAct;
         QAction *aboutQtAct;
 };
 #endif

لاحظ ال images اللى فى ال Application هنستخدمها عن طريق ال resource file

<!DOCTYPE RCC><RCC version="1.0">
 <qresource>
         <file>images/copy.png</file>
         <file>images/cut.png</file>
         <file>images/new.png</file>
         <file>images/open.png</file>
         <file>images/paste.png</file>
         <file>images/save.png</file>
 </qresource>
 </RCC>

ندخل فى ال implementation
هنحتاج نستخدم QtGui عشان ال widgets وال windows وهنحتاج نعمل include لل mainwindow.h
#include <QtGui>
#include "mainwindow.h"

ال init method وهى تعتبر ال entry point اللى هيتم فيها التصميم لل gui وتجهيز ال window
v
oid MainWindow::init()
 {
         setAttribute(Qt::WA_DeleteOnClose);
         isUntitled = true;
         textEdit = new QTextEdit;
         setCentralWidget(textEdit);
         createActions();
         createMenus();
         createToolBars();
         createStatusBar();
         readSettings();
         connect(textEdit->document(), SIGNAL(contentsChanged()),
                         this, SLOT(documentWasModified()));
 }

منطقى إن لما تبدأ البرنامج هيكون ال file الحالى مش ليه إسم فهنخلى ال isUntitle قيمتها true
نعمل QtextEdit Object ونخليه فى سنتر الويندو لأن فوقه ال menu/toolbars وتحته ال statusbar
createActions و createMenus و createToolsBars و createStatusBar بتنشئ لينا ال Actions/Menus/Toolbars/StatusBar

ال Actions
void MainWindow::createActions()
 {
         newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
         newAct->setShortcut(tr("Ctrl+N"));
         newAct->setStatusTip(tr("Create a new file"));
         connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));
         openAct = new QAction(QIcon(":/images/open.png"), tr("&Open..."), this);
         openAct->setShortcut(tr("Ctrl+O"));
         openAct->setStatusTip(tr("Open an existing file"));
         connect(openAct, SIGNAL(triggered()), this, SLOT(open()));
         saveAct = new QAction(QIcon(":/images/save.png"), tr("&Save"), this);
         saveAct->setShortcut(tr("Ctrl+S"));
         saveAct->setStatusTip(tr("Save the document to disk"));
         connect(saveAct, SIGNAL(triggered()), this, SLOT(save()));
         saveAsAct = new QAction(tr("Save &As..."), this);
         saveAsAct->setStatusTip(tr("Save the document under a new name"));
         connect(saveAsAct, SIGNAL(triggered()), this, SLOT(saveAs()));
         closeAct = new QAction(tr("&Close"), this);
         closeAct->setShortcut(tr("Ctrl+W"));
         closeAct->setStatusTip(tr("Close this window"));
         connect(closeAct, SIGNAL(triggered()), this, SLOT(close()));
         exitAct = new QAction(tr("E&xit"), this);
         exitAct->setShortcut(tr("Ctrl+Q"));
         exitAct->setStatusTip(tr("Exit the application"));
         connect(exitAct, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
         cutAct = new QAction(QIcon(":/images/cut.png"), tr("Cu&t"), this);
         cutAct->setShortcut(tr("Ctrl+X"));
         cutAct->setStatusTip(tr("Cut the current selection's contents to the "
                                                         "clipboard"));
         connect(cutAct, SIGNAL(triggered()), textEdit, SLOT(cut()));
         copyAct = new QAction(QIcon(":/images/copy.png"), tr("&Copy"), this);
         copyAct->setShortcut(tr("Ctrl+C"));
         copyAct->setStatusTip(tr("Copy the current selection's contents to the "
                                                          "clipboard"));
         connect(copyAct, SIGNAL(triggered()), textEdit, SLOT(copy()));
         pasteAct = new QAction(QIcon(":/images/paste.png"), tr("&Paste"), this);
         pasteAct->setShortcut(tr("Ctrl+V"));
         pasteAct->setStatusTip(tr("Paste the clipboard's contents into the current "
                                                           "selection"));
         connect(pasteAct, SIGNAL(triggered()), textEdit, SLOT(paste()));
         aboutAct = new QAction(tr("&About"), this);
         aboutAct->setStatusTip(tr("Show the application's About box"));
         connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
         aboutQtAct = new QAction(tr("About &Qt"), this);
         aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
         connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
         cutAct->setEnabled(false);
         copyAct->setEnabled(false);
         connect(textEdit, SIGNAL(copyAvailable(bool)),
                         cutAct, SLOT(setEnabled(bool)));
         connect(textEdit, SIGNAL(copyAvailable(bool)),
                         copyAct, SLOT(setEnabled(bool)));
 }

هشرح ال newAction بس والباقى نفس الفكرة
newAct = new QAction(QIcon(":/images/new.png"), tr("&New"), this);
         newAct->setShortcut(tr("Ctrl+N"));
         newAct->setStatusTip(tr("Create a new file"));
         connect(newAct, SIGNAL(triggered()), this, SLOT(newFile()));

ال Qaction Constructor بياخد 3 parameters ال أول ليشير لل Icon اللى فى الAction والتانى بيشير لل Text الظاهر على ال Action والتالت بيشير لل current ref وهو this
setStatusTip وهى ال text اللى هيظهر فى ال statusBar كل ماتمر على الnew Action
نربط ال triggered SIGNAL مع ال newFile SLOT ب Connect والsender هنا هيكون newAction وال receiver هيكون ال main window
لاحظ
        aboutQtAct = new QAction(tr("About &Qt"), this);
         aboutQtAct->setStatusTip(tr("Show the Qt library's About box"));
         connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));

فى ال Connect هنا ال sender هو aboutQtAction وال receiver هو ال qApp وهو بيقدم slot بإسم aboutQt بتظهر ال about window الإفتراضية

ال Copy/Cut/Paste stuff انت مش ليك دعوة بيهم كل اللى عليك تربط ال Action بال textEdit وتستخدم ال slot المناسبة سواء copy, cut or paste
Keep it simple !
ال setEnabled اعتقد واضحة وهى إنك تقدر ت trigger ال Action او لأ وبتاخد true/false
void MainWindow::about()
 {
        QMessageBox::about(this, tr("About SDI"),
                         tr("The <b>SDI</b> example demonstrates how to write single "
                                "document interface applications using Qt."));
 }


بمجرد ضغطك على ال about action هيظهر messagebox لاحظ إنك تقدر تستخدم HTML tag

إنشاء ال menus

void MainWindow::createMenus()
 {
         fileMenu = menuBar()->addMenu(tr("&File"));
         fileMenu->addAction(newAct);
         fileMenu->addAction(openAct);
         fileMenu->addAction(saveAct);
         fileMenu->addAction(saveAsAct);
         fileMenu->addSeparator();
         fileMenu->addAction(closeAct);
         fileMenu->addAction(exitAct);
         editMenu = menuBar()->addMenu(tr("&Edit"));
         editMenu->addAction(cutAct);
         editMenu->addAction(copyAct);
         editMenu->addAction(pasteAct);
         menuBar()->addSeparator();
         helpMenu = menuBar()->addMenu(tr("&Help"));
         helpMenu->addAction(aboutAct);
         helpMenu->addAction(aboutQtAct);
 }

كل اللى عليك إنك تستخدم addMenu method وتباصى ليها إسم ال Menu اللى هتبقة موجودة فى ال MenuBar
وفى كل menu عملتها تضيف ال Actions الخاصة بيها بإستخدام addAction ولتضيف separator إستخدام addSeparator method
عمل ال ToolBars بسيط جدا
void MainWindow::createToolBars()
 {
         fileToolBar = addToolBar(tr("File"));
         fileToolBar->addAction(newAct);
         fileToolBar->addAction(openAct);
         fileToolBar->addAction(saveAct);
         editToolBar = addToolBar(tr("Edit"));
         editToolBar->addAction(cutAct);
         editToolBar->addAction(copyAct);
         editToolBar->addAction(pasteAct);
}

createStatusBar
void MainWindow::createStatusBar()
 {
         statusBar()->showMessage(tr("Ready"));
 }

maybeSave عشان نهدل save or not
bool MainWindow::maybeSave()
 {
         if (textEdit->document()->isModified()) {
                 QMessageBox::StandardButton ret;
                 ret = QMessageBox::warning(this, tr("SDI"),
                                          tr("The document has been modified.\n"
                                                 "Do you want to save your changes?"),
                                          QMessageBox::Save | QMessageBox::Discard
                                          | QMessageBox::Cancel);
                 if (ret == QMessageBox::Save)
                         return save();
                 else if (ret == QMessageBox::Cancel)
                         return false;
         }
         return true;
 }

وبناء على ال response هنشوف هنستخدم هنستخدم اى رد فعل
loadFile
void MainWindow::loadFile(const QString &fileName)
 {
         QFile file(fileName);
         if (!file.open(QFile::ReadOnly | QFile::Text)) {
                 QMessageBox::warning(this, tr("SDI"),
                                                          tr("Cannot read file %1:\n%2.")
                                                          .arg(fileName)
                                                          .arg(file.errorString()));
                 return;
         }
         QTextStream in(&file);
         QApplication::setOverrideCursor(Qt::WaitCursor);
         textEdit->setPlainText(in.readAll());
         QApplication::restoreOverrideCursor();
         setCurrentFile(fileName);
         statusBar()->showMessage(tr("File loaded"), 2000);
 }


كل الفكرة هى عمل Qfile Object ونختبر هل الملف readOnly او لأ فى حال إنه متوافق معانا هنعمل QtextStream object ونباصى كل اللى فيه -readAll method- ل ال setPlainText الخاصة ب textEdit
لاحظ موضوع ال Qt::WaitCursor عشان نخلى الcursor باين إن فى بروسس شغالة
ال restoreOverrideCursor بنستعيد الcursor بصورته السابقة.
نخلى إسم ال currentFile هو إسم ال Opened File بإستخدام setCurrentFile ونشير فى ال statusBar ان ال File Loaded بإستخدام ال showMessage.
QString MainWindow::strippedName(const QString &fullFileName)
 {
         return QFileInfo(fullFileName).fileName();
 }

للحصول على اسم ال File فقط نستخدم fileName method تحت ال QfileInfo وهى بتدى return ل Qstring بالإسم المختصر لل File


setCurrentFile
void MainWindow::setCurrentFile(const QString &fileName)
 {
         static int sequenceNumber = 1;
         isUntitled = fileName.isEmpty();
         if (isUntitled) {
                 curFile = tr("document%1.txt").arg(sequenceNumber++);
         } else {
                 curFile = QFileInfo(fileName).canonicalFilePath();
         }
         textEdit->document()->setModified(false);
         setWindowModified(false);
         setWindowTitle(tr("%1[*] - %2").arg(strippedName(curFile))
                                                                                .arg(tr("SDI")));
 }



عمل Save لل File
bool MainWindow::saveFile(const QString &fileName)
 {
         QFile file(fileName);
         if (!file.open(QFile::WriteOnly | QFile::Text)) {
                 QMessageBox::warning(this, tr("SDI"),
                                                          tr("Cannot write file %1:\n%2.")
                                                          .arg(fileName)
                                                          .arg(file.errorString()));
                 return false;
         }
         QTextStream out(&file);
         QApplication::setOverrideCursor(Qt::WaitCursor);
         out << textEdit->toPlainText();
         QApplication::restoreOverrideCursor();
         setCurrentFile(fileName);
         statusBar()->showMessage(tr("File saved"), 2000);
         return true;
 }

Save/Save As
bool MainWindow::save()
 {
         if (isUntitled) {
                 return saveAs();
         } else {
                 return saveFile(curFile);
         }

}
 bool MainWindow::saveAs()
 {
         QString fileName = QFileDialog::getSaveFileName(this, tr("Save As"),
                                                                                                         curFile);
         if (fileName.isEmpty())
                 return false;
         return saveFile(fileName);
 }

فى حال لو ال currentFile ليه اسم يعنى isUntitled هتدى false هيتم إستدعاء saveFile method
فى حال لو مش ليه إسم او isUntitled هتدى true هيتم إستدعاء saveAs method وهى هتحفظ ال file ب save file dialog لطيف للمستخدم


بالمناسبة فى حالة ال newFileAction هيتعمل Object جديد من ال mainwindow وهكذا على بعد 40 و 40 من ال x, y اللى بادئ عنهم ال window
 void MainWindow::newFile()
 {
         MainWindow *other = new MainWindow;
         other->move(x() + 40, y() + 40);
         other->show();
 }


واخيرا ال main.cpp وهو ال Entry point للبرنامج
 #include <QApplication>

 #include "mainwindow.h"

 int main(int argc, char *argv[])
 {
         Q_INIT_RESOURCE(sdi);
         QApplication app(argc, argv);
         MainWindow *mainWin = new MainWindow;
         mainWin->show();
         return app.exec();
 }


Q_INIT_RESOURCE بنباصى ليها إسم ملف ال resource المستخدم
كفاية كلام لحد كدا باقى السورس بسيط
المرة الجاية نبقة نعمل MDI Application

ال source من ال QT Examples فى mainwindow/sdi