Indrekis
Indrekis

Categories

  • retrocomputing
  • ibm_pc_compat

Tags

  • retrocomputing
  • IBM PC та сумісні
  • Linux
  • KDE

Колись давно, зимою 2001 року, опинившись ізольованим від зовнішнього світу та з дуже депресивним настроєм, переклав два підручника KDE та один по xlib, але якось так вийшло, що ніде не викладав їх. То вирішив поділитися ними вже як ретро-матеріалами. Викладаю лише з мінімальними правками – ось перший з них.

Існує дещо новіша версія, 1.2, (локальна копія тут, вона вийшла вже пізніше, в 2002. Оригінальний архів не можу знайти, знайшов лише html-файли.

Підручник KDE

Цей підручник крок за кроком навчає створенню простих програм для KDE, пояснюючи кожен рядок коду і приділяючи основну увагу написанню зрозумілого коду а не створенню справжніх програм.

Необхідною умовою для читання даного підручника є певний досвід у програмуванні на C++ (це не підручник по C++), однак попередні знання Qt чи KDE не потрібні. Однак, я рекомендую прочитати також підручник по Qt, що буде дуже корисно для того щоб почати в цьому пристрасному світі.

Вам буде потрібна груднева або пізніша версія KDE (тобто KRASH). Це один з попередніх випусків майбутньої версії 2.0. Якщо ви ще не встановили її то тут ви зайдете інформацію про те, як це зробити. Майте на увазі, попередні CVS - версії не призначені для щоденного використання і необхідна лише розробникам для впевненості в сумісності їх програм з майбутньою технологією.

Сказавши це, я нагадаю що ми за чотири кроки створимо веб-браузер, а шостий приклад – програма закладок, котра взаємодіє з браузером для збереження закладок (bookmark links). Але давайте не будемо випереджувати події і почнемо з початку…

Introduction

Надалі буде використовуватися мова, звична для розробників GUI, наприклад слово widget2. Widget – це все що ви бачите на екрані: кнопка, вікно, рамка скролінгу (scrollbar), меню і т.д. Як ви бачите деякі віджети навіть містять інші віджети в собі. З точки зору розробника можна вважати що кожен спеціалізований віджет наслідує базовий клас віджетів – QWidget.

Далі – розміщення (layout) віджетів, спосіб розташування їх на екрані: один під одним, один біля одного, і т.д.

Я спробую пояснити решту термінів в міру їх появи.

Компіляція KDE програми

Я використовую утіліти autoconf/automake для створення Makefile файлів. Є багато складних скриптів створених в основному від Stephan “coolo” Kulow. Ці скрипти можна скопіювати з стандартних KDE пакетів і після невеликих змін використовувати в роботі над даним підручником.

Виконання “make -f Makefile.dist” створить конфігураційний скрипт і файли Makefile.in в кожній директорії використовуючи відповідний Makefile.am файл. Щоб закінчити процедуру вам буде потрібен пакет autoconf/automake На цьому етапі Stephan трохи чаклує над скриптами щоб добитися певного результату замість того щоб явно писати групи правил в кожному Makefile.am

Коли ви запускаєте “./configure”, генеруються файли Makefile, використовуючи раніше створені Makefile.in як шаблони.

Тепер ви можете насправді виконати “make” щоб побудувати бінарники.

moc

Як ви довідаєтеся пізніше, деякі файли потребують попередньої обробки з використанням мета-компілятора об’єктів – moc. Для використання moc ви повинні скористатися фокусом, який використовує таке означення в файлі Makefile.am:

METASOURCES = AUTO

Скрипт, який генерує фінальний Makefile аналізує їх і генерує правила згідно яких ці файли створюються мета-компілятором об’єктів.

Кінцевий код в Makefile для аналізу мета-текстів програм буде якимось таким:

p3.moc.cpp: $(srcdir)/p3.h
        $(MOC) $(srcdir)/p3.h -o p3.moc.cpp  

Аналогічно до цього, в p6, p7 i p8 необхідно буде використовувати препроцесор для інтерфейсу DCOP цих програм. Це робиться такими командами:

 dcopidl appIface.h > appIface.kidl
 dcopidl2cpp --no-stub appIface.kidl  

Вони створять файл appIface.kidl, в якому буде xml – оголошення інтерфейсу програми, і C++ файл appIface_skel.cpp з скелетом інтерфейсу, готовим до реалізації. Якщо ви програмуєте для CORBA, ця схема скелетів і заглушок (skels and stubs) може вам може видатися знайомою.

Додаткова інформація

Якщо цей підручник годиться хоч на що не будь, то прочитавши його, ви почнете писати ваші власні програми для KDE. Якщо це станеться, можливо, ви, зіштовхнетеся з новими проблемами, не описаними на цих сторінках, тому я наводжу тут деякі коментарі, адреси, URL і т.д. що можливо зможуть допомогти у їх вирішенні.

Ремарка з 2025. На жаль, більшість з лінків неактуальні, частину спробував скерувати а Архів.

Читайте документацію

Qt супроводжується найкращою документацією з тих що мені коли-небудь доводилося бачити. Звертайтеся до неї, якщо хочете довідатися, що робить якийсь клас, або коли ви хочете переконатися чи існує метод який робить саме те що вам потрібно.

В мене є каталог Qt документації в списку закладок konqueror, тому, у випадку потреби я можу швидко отримати доступ до них.

Бібліотеки KDE не дуже відстають і теж супроводжуються величезною кількість документації. Ви можете завантажити її з http://developer.kde.org/documentation/library/2.0-api/classref/.

Якщо ви не маєте можливості завантажити документи або не хочете переписувати великий пакет (package), ви можете створити документацію для себе з вихідних текстів бібліотек kdelibs, скориставшись KDoc. KDoc – генератор документації, що використовує спеціальні теги в коді і може створювати як HTML так і latex, docbook, man і т.д. документи.

Щоб використовувати його, спершу завантажте пакет kdoc з KDE snapshots directory, чи з його домашньої сторінки http://www.ph.unimelb.edu.au/~ssk/kde/kdoc.

Ось дві команди котрі я використовую для генерування документації:

qt2kdoc -u "http://localhost/~antonio/docs/qt2" -z ~/kdeCVS/qt-copy/html \
		-o ~/.kdoc

makekdedoc --srcdir ~/kdeCVS/kdelibs/ --outputdir ~/public_html/docs/kde2 \
	        --url  "http://localhost/~antonio/docs/kde2"

Врахуйте, що вихідні тексти Qt в мене розташовані в $HOME/kdeCVS/qt-copy а KDElibs тексти – в $HOME/kdeCVS/kdelibs. Також в мене є локальний веб-сервер що використовує $HOME/public_html як синонім для http://localhost/~antonio. І, нарешті, є symbolic link з $HOME/public_html/docs/qt2, що вказує на каталог html в текстах Qt.

Виконуючи попередні команди, kdoc генерує більше 11Мб документації в $HOME/public_html/docs/kde2, до якої я легко можу отримати доступ використовуючи konqueror чи kfm і переходячи на http://localhost/~antonio/docs/kde2/<library>.

qt2kdoc – інструмент, який аналізує документацію Qt і генерує індекс, що використовується при генерації документації для KDE за допомогою makekdedoc. Таким чином, kdoc може створювати гіпертекстові посилання з документації по KDE на відповідні класи в документації по Qt.

Контакти

Якщо ви хочете приєднатися до KDE, можете вільно підписатися на ті списки розсилки KDE,які вас найбільше цікавлять.

Якщо ви програміст, то, швидше за все, це kde-devel@kde.org, якщо ви хочете малювати ікони, то можете приєднатися до художньої команди, якщо ж хочете перекласти KDE на іншу мову – можете приєднатися до команди перекладу тощо.

Всі списки розсилки наведені в www.kde.org/contacts.html, тут ви можете довідатися як підписатися.

Також, ви можете переглядати сторінки на developer.kde.org, де міститься вся інформація для розробників під KDE, включаючи підручники, інструкції, документацію (заархівовану таром для завантаження) і т.д.

Наш гуру, Mosfet, має власну сторінку, яку він регулярно поновлює новинами, ви можете перевірити її тут www.mosfet.org.

IDE

Тим, хто шукає IDE, від якого ви не відмовитеся в довільний момент роботи над програмою, хочу дещо порадити.. KDevelop (його ви можете знайти на www.kdevelop.org) може стати в пригоді тим, хто починає створювати програми для KDE.

KDevelop це повноцінне IDE для створення програм (KDE, Qt or console apps), він включає підсвітку синтаксичних конструкцій, візуальний редактор діалогів, вбудований відладчик, шаблони програм, вбудовану підтримку CVS, підтримку перекладу, редактор іконок, і багато інших елементів.

Інструкції по розробці інтерфейсу користувача

Не хочу закінчувати вступ, не згадавши про інструкції по розробці інтерфейсу користувача. Ви можете знайти їх на сайті developer.kde.org.

На цих сторінках я буду ігнорувати UI (user interface) – інструкції на користь простоти Думаю, нема потреби окремо згадувати: кожен програміст, що створює справжні KDE програми, а не прості приклади, мусить повністю їх перечитати, і максимально дотримуватися в майбутньому. Причини цього очевидні.

Не маючи більше що сказати, приступимо до навчання!

p1

p1 є найпростішою Qt-програмою, весь її код в main.cpp:

#include <qapplication.h>
#include <qpushbutton.h>

int main( int argc, char **argv )
{
    QApplication a( argc, argv );

    QPushButton *hello=new QPushButton( "Hello world!", 0 );
    hello->resize( 100, 30 );
 
    QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) );
 
    a.setMainWidget( hello );
    hello->show();
 
    return a.exec();
} 

Давайте подивимося, що робить кожен рядок програми.

    QApplication a( argc, argv );

Ця команда створює об’єкт QApplication, який буде керувати виконанням всієї програми, найбільш загальними параметрами системи і програми (для більшої однорідності системи (“десктопу”)) та ін.

    QPushButton *hello=new QPushButton( "Hello world!", 0 );
    hello->resize( 100, 30 );

Цим ми створюємо об’єкт QPushButton. Це кнопка, яку ви можете натиснути (на відміну від кнопок, що мають стан, таких як QRadioButtons чи QCheckBox).

Вона містить текст “Hello world!”, і не має предків (0 – вказівник NULL). Використовуючи віджет без предка, ми дозволяємо віконному менеджеру керувати цим віджетом як незалежним вікном на робочому столі. Звичайно програми мають лише одне вікно без предків, яке є головним віджетом, але ми детальніше розберемося з цим пізніше.

Також, ми встановлюємо розмір нашого віджета на 100 пікселів ширини і 30 пікселів висоти.

    QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) );

Щоб пояснити цей код, ми мусимо трохи ознайомитися зі внутрішньою стороною механізму сигнал/слот (signal/slot). Я спробую пояснити його тут, але якщо ви не зрозумієте, то вам доведеться звернутися до прекрасного документу Qt: “Introduction to Signals and Slots”.

Кожен клас, що наслідується від QObject (прямо, чи не прямо – наслідуючи його нащадків), може містити сигнали і/або слоти. Об’єкт посилає сигнали при будь-якій зміні свого стану. Наприклад, коли ви натискаєте QPushButton, вона посилає сигнал clicked(), або, коли на QScrollBar відбувається скролінг, генерується сигнал valueChanged(). З іншого боку, слот – звичайний метод, член об’єкта, що може отримувати сигнали.

Об’єкт не знає, чи слот під’єднаний до одного з цих сигналів, і навіть більше, ви можете під’єднувати один сигнал до багатьох слотів чи багато сигналів до одного слота. Це дозволяє по справжньому повторно використовувати готові компоненти в процесі програмування.

Код, що необхідний для виклику слотів, генерується moc одночасно з створенням коду ініціювання мета-об’єктної інформації. Пізніше ми побачимо, як користуватися moc, але оскільки в p1 ми використовуємо лише класи, що вже існують, то ми поки не повинні запускати moc.

Нарешті, наведений вище код приєднує сигнал “clicked()” від об’єкта hello до слоту “quit()” програми, що завершує її виконання.

    a.setMainWidget( hello );
    hello->show();

Цей код робить hello головним віджетом програми (таким, що програма закінчиться, коли його знищити), і показує його на екрані.

    return a.exec();

І, на закінчення, запускається цикл обробки подій і ми передаємо керування програмою користувачу. Кожен раз, коли користувач натисне кнопку hello, цикл обробки подій викличе слот програми quit() і ця функція передасть код завершення системі.

Це ж просто. Правда ? :-)

p2

p2 є аналогом p1, але з використанням бібліотек KDE, весь код також вмістився в main.cpp:

// main.cpp
#include <kapp.h>
#include <klocale.h>
#include <qpushbutton.h>
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv , "p2");
 
    QPushButton *hello=new QPushButton( i18n("Hello World !"), 0 );
    hello->setAutoResize( TRUE );
 
    QObject::connect( hello, SIGNAL(clicked()), &a, SLOT(quit()) );
 
    a.setMainWidget( hello );
    hello->show();
 
    return a.exec();
}

Давайте проаналізуємо її.

    KApplication a( argc, argv , "p2");

Цей код створює об’єкт KApplication. KApplication це клас, що наслідується від QApplication, але додає деякі особливості, специфічні для бібліотек KDE, такі як підтримка локалізації (інтернаціоналізації), конфігурації програм та ін.

    QPushButton *hello=new QPushButton( i18n("Hello World !"), 0 );
    hello->setAutoResize( TRUE );

Відмінність між цим кодом, і відповідним з p1 полягає в тому, що ми пишемо не безпосередньо “Hello World!” a беремо значення, що повертає функція i18n(“Hello World !”). Чому? Тому, що i18n повертає рядок, перекладений на мову, задану користувачем в мовному модулі програми KControl. Цією простою зміною ми забезпечуємо підтримку більш ніж 30 мов, на які перекладено KDE.

Через це ми не можемо більше використовувати фіксований розмір кнопки, бо ми не знаємо довжини перекладеного рядка (і буде погано, якщо ми не покажемо весь текст через замалі розміри кнопки). Встановивши прапорець autoResize, змушуємо кнопку змінювати свій розмір автоматично при кожній зміні вмісту щоб бути впевненим в правильності відображення цілого тексту.

Решта програми така ж як і p1 .

p3

Ми вже створили просту KDE програму в p2, але в більшості випадків, вам напевно буде потрібен дещо складніший інтерфейс ніж проста кнопка :-), тому давайте подивимося як додати стандартний головний віджет.

// main.cpp

#include <kapp.h>
#include "p3.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p3");
 
    MainWindow *window=new MainWindow( "Tutorial - p3" );
    window->resize( 400, 300 );
 
    a.setMainWidget( window );
    window->show();
 
    return a.exec();
} 
// p3.h
#include <ktmainwindow.h>
 
class MainWindow : public KTMainWindow
{
   Q_OBJECT
  public:
 
    MainWindow ( char * name );
 
  public slots:
    void fileOpen();
    void fileSave();
};
// p3.cpp
#include "p3.h"
#include <kfiledialog.h>
#include <kapp.h>
#include <kmenubar.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <qtextview.h>
 
MainWindow::MainWindow ( char * name ) : KTMainWindow ( name )
{
    QPopupMenu *filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Open" ), this, SLOT(fileOpen()) );
    filemenu->insertItem( i18n( "&Save" ), this, SLOT(fileSave()) );
    filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT(quit()) );
    QString about =
            i18n("p3 1.0\n\n"
                 "(C) 1999 Antonio Larrosa Jimenez\n"
                 "larrosa@kde.org\t\tantlarr@arrakis.es\n"
                 "Malaga (Spain)\n\n"
                 "Simple KDE Tutorial\n"
                 "This tutorial comes with ABSOLUTELY NO WARRANTY\n"
                 "This is free software, and you are welcome to redistribute it\n"
                 "under certain conditions\n");
 
    QPopupMenu *helpmenu = helpMenu( about );
    KMenuBar *menu = new KMenuBar( this );
    menu->insertItem( i18n( "&File" ), filemenu );
    menu->insertSeparator();
    menu->insertItem( i18n( "&Help" ), helpmenu );
    setMenu( menu );
 
    QTextView *hello=new QTextView(
       i18n("<H2>Hello World !</H2><BR>This is a simple"
	" window with <I><font size=5><B>R<font color=red"
	" size=5>ich </font><font color=blue size=5>Text"
	"</font></B></I> capabilities<BR>Try to resize"
	" this window, all this is automatic !"), "", this );
    setView( hello );
 
} 
 
void MainWindow::fileOpen()
{
    KURL filename = KFileDialog::getOpenURL( QString::null, "*", this );
    QString msg = QString( i18n("Now this app should open the url %1 .") ).arg(filename.url());
    KMessageBox::information( 0, msg, i18n( "Information" ), 
		"fileOpenInformationDialog" );
}
 
void MainWindow::fileSave()
{
   KURL filename=KFileDialog::getSaveURL( QString::null, "*", this );
}

Цей код виглядає значно складнішим, але насправді це не так ;), давайте подивимося.

Текст main.cpp не дуже відрізняється від p1 чи p2. Єдина відмінність в тому, що ми створюємо об’єкт MainWindow замість QPushButton. Крім цього ми не вказуємо адрес предка як NULL тому, що так відбудеться за замовчуванням.

Також, ми не під’єднуємо тут ніяких сигналів MainWindow не має жодного clicked() сигналу.

class MainWindow : public KTMainWindow
{
   Q_OBJECT

В p3.h ми означаємо клас MainWindow, що наслідується від KTMainWindow, котрий входить в kdelibs і містить всі необхідні методи для створення нормальної KDE програми, з меню, панеллю інструментів, рядком стану та ін.

Відзначу, що ми написали Q_OBJECT в оголошенні класу. Це макрос, який оголошує спеціальні змінні і віртуальні методи, потрібні мета-об’єкту для реалізації механізму слотів і сигналів та інших особливостей, він також повідомляє moc про те, що це QObject і він може містити сигнали і/або слоти.

  public slots:
    void fileOpen();
    void fileSave();

Як ми довідалися в p1, слоти – звичайні методи, а тому вони можуть бути описані як public:, protected: чи private:, подібно до інших методів. Єдина особливість - ми мусимо писати "public slots:", "protected slots:", чи "private slots:", щоб moc знав що це слоти.

Препроцесор потурбується, щоб викинути чи замінити всі ці специфічні теги, дозволивши компілятору не займатися ще й цим.

Тепер перейдемо до справді серйозного в p3.cpp :

    QPopupMenu *filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Open" ), this, SLOT(fileOpen()) );
    filemenu->insertItem( i18n( "&Save" ), this, SLOT(fileSave()) );
    filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT(quit()) );

filemenu є QPopupmenu, тобто, меню, що може містити “пункти”, які користувач може вибирати для виконання заданих дій.

Ми вставляємо ці пункти, викликаючи insertItem. Перший аргумент - текстовий заголовок пункту. Символ & ставиться перед літерою, яку ми хочемо використовувати для швидкого доступу до даної команди. Скориставшись i18n, ми можемо бути впевнені – текст з’явиться мовою, визначеною користувачем.

Коли користувач вибирає пункт, меню генерує сигнал, який може (і мусить!) бути приєднаний до функції яка щось робить. Для зручності, замість використання connect, ми приєднуємо сигнал безпосередньо, коли описуємо пункт меню. Це ми робимо з допомогою другого і третього параметра. Ми приєднуємо пункт Open до слота fileOpen() в цьому ж об’єкті, пункт Save до fileSave(), а Quit до quit(), аналогічно до p1 і p2.

Відзначу, що kapp – зручна функція для доступу до поточного екземпляру KApplication.

    QString about =
            i18n("p3 1.0\n\n"
                 "(C) 1999 Antonio Larrosa Jimenez\n"
                 "mailto:larrosa@kde.org\t\tantlarr@arrakis.es\n"
                 "Malaga (Spain)\n\n"
                 "Simple KDE Tutorial\n"
                 "This tutorial comes with ABSOLUTELY NO WARRANTY\n"
                 "This is free software, and you are welcome to redistribute it\n"
                 "under certain conditions\n");
 
    QPopupMenu *helpmenu = helpMenu( about );

Спочатку ми помістили всі ці рядки в об’єкт QString. Клас QString це рядок загального використання, призначений для роботи з рядками довільної форми. Одна з основних переваг цього методу полягає в можливості автоматичного використання формату Unicode в ваших програмах без додаткових турбот. Він також містить неявний механізм спільного використання (sharing mechanism), що збільшує продуктивність, бо створення копії QString не переносить вміст аж доти, поки одна з копій не зміниться3.

Тепер ми створимо ще одне меню, але використаємо член KTMainWindow – метод helpMenu. Зробивши так, ми отримуємо стандартне меню допомоги, з пунктами для документації, діалогом about і т.д. Діалог about містить весь текст, заданий вище в рядку about.

    KMenuBar *menu = new KMenuBar( this );
    menu->insertItem( i18n( "&File" ), filemenu );
    menu->insertSeparator();
    menu->insertItem( i18n( "&Help" ), helpmenu );
    setMenu( menu );

Готуємося до завершення створення меню. Спочатку створюємо KMenuBar, котрий забезпечує стандартне меню KDE, з єдиним виглядом в різних програмах, з дескриптором для переміщення меню з і в головне вікно і т.д. Меню є потомком головного вікна, тому задаємо this як нащадок.

Тепер ми добавляємо два вже готових QPopupMenus та сепаратор між ними і робимо створене меню поточним для даного вікна.

    QTextView *hello=new QTextView(
       i18n("<H2>Hello World !</H2><BR>This is a simple"
	" window with <I><font size=5><B>R<font color=red"
	" size=5>ich </font><font color=blue size=5>Text"
	"</font></B></I> capabilities<BR>Try to resize"
	" this window, all this is automatic !"), "", this );
    setView( hello );

Я хотів би, щоб ця програма робила щось хороше, але просте, тому давайте включимо в неї віджет QTextView. Це – доволі корисний переглядач тексту у форматі Rich Text, для якого ми можемо задавати тип шрифту та колір, поміж інших стилів. Після створення ми робимо його view для даного вікна.

Але що таке view? View це віджет який користувач може бачити під меню (чи під панеллю інструментів, якщо така присутня). Це документ, відкритий користувачем, чи щось інше, що не є стандартною частиною KTMainWindow (меню, панеллю інструментів, рядком стану)

    KURL filename = KFileDialog::getOpenURL( QString::null, "*", this );

Тепер подивимося на реалізацію слота fileOpen . Цей рядок виводить діалог відкриття файлу, і надає користувачу можливість відкрити файл, що вже існує.

Аргумент tree вказує на директорію, з якої починати пошук (нас це не турбує, тому за замовчуванням беремо поточну), фільтр (щоб вибирати файли лише з тих, що задовольняють заданий регулярний вираз) і об’єкт-предок.

Тепер filename містить url вибраного користувачем файлу.

Майте на увазі, краще надати користувачу можливість відкривати довільні URL а не тільки локальні файли. Для цього ми використали діалог getOpenURL , а він дозволяє користувачам вказувати довільний url. Приклад використання бібліотеки KIO дивіться нижче.

    QString msg=QString( i18n("Now this app should open the url %1 .") ).arg(filename.url());

Ми можемо маніпулювати рядками за допомогою класу QString, зокрема, в цьому випадку ми створюємо повідомлення, яке ми хочемо показати. (Оскільки відкриття файлів не є предметом розгляду даного підручника). Формат аналогічний до sprintf, але ви не турбуєтесь про типи даних, бо вам лише треба використовувати %1, %2, і т.д. і потім , .arg(var1).arg(var2).arg(var3) , і т.д. Ще однією перевагою QString є відсутність необхідності турбуватися про замалий розмір рядка-призначення, бо об’єкт автоматично, знаючи кінцевий розмір рядка, виділить потрібне місце.

Отже, filename.url() це QString що містить url, збережений в об’єкті KURL у вигляді протокол+ім’я машини+шлях+запит+все що завгодно :-).

    KMessageBox::information( 0, msg, i18n( "Information" ), "fileOpenInformationDialog" );

З допомогою класу KMessageBox, і його статичних членів, ми можемо створювати різні діалоги. В цьому прикладі ми використовуємо інформаційний діалог, щоб показати який файл буде відкриватися. Третій параметр – текст заголовку а четвертий – ідентифікатор для збереження файлу конфігурації (часто ~/.kde/share/config/p3rc), якщо користувач хоче або або надає перевагу не бачити більше даний діалог (те, що він вирішив, задається кнопкою checkbox на діалозі).

Між іншим, ми не створили засобу для того, щоб знову дозволити показ діалогу! Це робиться просто викликом KMessageBox::enableAllMessages(), але я думаю, для читача буде хорошою вправою додати до p3 пункт меню, що буде робити цей виклик :-) .

    KURL filename=KFileDialog::getSaveURL( QString::null, "*", this );

Нарешті, в fileSave ми викликаємо аналог getOpenURL, getSaveURL.

Мушу сказати, якщо ми використовуємо getOpenURL то ми мусимо використовувати бібліотеку KIO для доступу до віддалених файлів, якщо цього, звичайно, хоче користувач. Це, в основному, реалізується чимось таким:

    
              QString tmpFile; if( KIO::NetAccess::download( "ftp://ftp.kde.org/myfile.tgz",
      
      tmpFile ) )
    {

	/* Do whatever you want with a
     
      _local_ file stored as
    tmpFile
      */ /* Робите все, що завгодно з _локальним файлом_ збереженим як tmpFile */
      
      KIO::NetAccess::removeTempFile( tmpFile );
    }

Але це виходить за межі даного підручника (звичайно, в майбутньому, я можу розширити його прикладами з libkio).

Добре, я сподіваюся було не дуже складно.

p4

Тепер, коли ми знаємо, як створювати меню, ми напишемо майже справжню програму. Дякуючи праці Lars Knoll, Antti Koivisto, Waldo Bastian і багатьох інших, ми маємо можливість використовувати KHTML-widget, здатний показувати сторінки віджет, який використовує бібліотеку kio, а тому може автоматично завантажувати сторінки з мережі. Подивимося як він працює.

// main.cpp

#include <kapp.h>
#include "p4.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p4" );
 
    MainWindow *window=new MainWindow( "Tutorial - p4" );
    window->resize( 300, 200 );
 
    a.setMainWidget( window );
    window->show();
 
    return a.exec();
} 
// p4.h

#include <ktmainwindow.h>
#include <kurl.h>
#include <kparts/browserextension.h> 
 
class QLineEdit;
class KHTMLPart;
 
class MainWindow : public KTMainWindow
{
  Q_OBJECT
 
  public:
    MainWindow ( char * name );
 
  public slots:
    void changeLocation();
    void openURLRequest(const KURL &url, const KParts::URLArgs & );
 
  private:
    QLineEdit *location;
    KHTMLPart *browser;
 
}; 
// p4.cpp

#include "p4.h"
#include <qvbox.h>
#include <qlineedit.h>
#include <kapp.h>
#include <kmenubar.h>
#include <klocale.h>
#include <khtml_part.h>
 
MainWindow::MainWindow ( char * name ) : KTMainWindow ( name )
{
    QPopupMenu * filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) );
    QString about =
            i18n("p4 1.0\n\n"
                 "(C) 1999 Antonio Larrosa Jimenez\n"
                 "larrosa@kde.org\t\tantlarr@arrakis.es\n"
                 "Malaga (Spain)\n\n"
                 "Simple KDE Tutorial\n"
                 "This tutorial comes with ABSOLUTELY NO WARRANTY \n"
                 "This is free software, and you are welcome to redistribute it\n"
                 "under certain conditions\n");
 
    QPopupMenu *helpmenu = helpMenu(about);
    KMenuBar * menu = new KMenuBar( this );
    menu->insertItem( i18n( "&File" ), filemenu);
    menu->insertSeparator();
    menu->insertItem(i18n("&Help"), helpmenu);
    setMenu( menu );
 
    QVBox * vbox = new QVBox ( this );
 
    location = new QLineEdit ( vbox );
    location->setText( "http://localhost" );
 
    browser=new KHTMLPart( vbox );
    browser->openURL( location->text() );
 
    connect( location , SIGNAL( returnPressed() ),
                this, SLOT( changeLocation() ) );
 
    connect( browser->browserExtension(),
	SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
	this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) )
    setView( vbox );
}                                                                              
void MainWindow::changeLocation()
{
    browser->openURL( location->text() );
}
void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & )
{
    location->setText(url.url());
    changeLocation();
} 

Перша відмінність від p3 є в p4.h. Ми будемо використовувати меню лише для виходу з програми, тому ми забрали два слоти для діалогів Open i Save.

  public slots:
    void changeLocation();
    void openURLRequest(const KURL &url, const KParts::URLArgs & );
 
  private:
    QLineEdit *location;
    KHTMLPart *browser;

Ми вказуємо слоту викликатися changeLocation(), коли користувач хоче завантажити нову сторінку, натиснувши Enter на панелі адреси. Метод openURLRequest це слот, який викликається, коли користувач вибрав гіперпосилання на html сторінці, і сторінка робить запит на відкриття цього url (який передається як параметр). Ми будемо використовувати два віджети, один QLineEdit, а інший – KHTMLPart. QLineEdit реалізує рядок, в якому користувач може редагувати якийсь текст, в даному випадку, html сторінку, що буде відображатися в KHTMLPart.

    QVBox * vbox = new QVBox ( this );

Ми використовуємо QVBox для керування виглядом віджету. Оскільки ми хочемо розташувати QLineEdit і KHTMLWidget один під одним, то це якраз завдання для QVBox. Цей віджет автоматично розташовує своїх дітей вертикально (так само як QHBox розташовує їх горизонтально), і змінює їхню геометрію, коли змінюється розмір QHBox. Майте на увазі, що ви можете використовувати більш складні класи компонування (такі як QGridLayout) для керування геометрією, або навіть робити це власними руками, аналогічно до інших інструментів розробника. Але це4 спрощує код, залишаючись дуже потужним. Скажімо, коли користувач змінює розмір вікна, KHTMLWidget завжди розтягується на максимальну висоту, тоді як QLineEdit завжди зберігає фіксовану висоту, і вони обоє набувають повної ширини головного вікна програми.

    location = new QLineEdit ( vbox );
    location->setText( "http://localhost" );

Ми створюємо об’єкт QLineEdit, адресу, як дитину vbox, і задаємо певний текст для показу по замовчуванню.

Ви можете подорожувати мережею Інтернет з допомогою цієї програми, але оскільки більшість людей тільки тестують її з допомогою локального веб-сервера, давайте встановимо адресу localhost за замочуванням. Майте на увазі, для роботи цієї (і наступної програми) потрібен запущений на вашій машині apache.

    browser=new KHTMLPart( vbox );
    browser->openURL( location->text() );

Цими командами ми створюємо об’єкт браузера як потомок vbox, тому він добавиться під рядком адреси.

Виконуючи openURL ми наказуємо нашому KHTMLPart завантажити url, вказаний в тексті рядка адреси, і відобразити його.

    connect( location , SIGNAL( returnPressed() ),
                this, SLOT( changeLocation() ) );

Коли користувач натискає клавішу Enter на рядку адреси, останній генерує сигнал returnPressed(), який ми цим кодом приєднуємо до слота changeLocation() в цьому об’єкті. Ми можемо викликати connect безпосередньо (без QObject::), тому, що ми зараз в класі, що наслідує QObject (непрямо, бо ми наслідуємо KTMainWindow, котрий походить від QWidget, а той, в свою чергу, від QObject).

    connect( browser->browserExtension(),
          SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
          this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

Тепер ми приєднуємо інший сигнал до відповідного слота. Добре, що цей браузер генерує сигнал openURLRequest(const KURL &, const KParts::URLArgs&), коли хоче відкрити URL (наприклад, тому що користувач клікнув по URL). Фактично, сигнал генерується не власне об’єктом KHTMLPart, а його підоб’єктом browserExtension, і тому ми використовуємо його як джерело сигналу.

Але другий параметр не використовується, тому ми будемо ігнорувати його, бо потреба в ньому виникає лише в дуже специфічних випадках.

    setView( vbox );

Тепер ми встановлюємо vbox widget як view widget цього об’єкта KTMainWindow.

    browser->openURL( location->text() );

Слот changeLocation просто вказує браузеру browser відкрити URL, записаний в рядку адреси.

void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & )
{
    location->setText(url.url());
    changeLocation();
}

Це код, що викликається, коли браузер хоче перейти до нового URL, як ми пам’ятаємо, метод url() з об’єкта KURL повертає повний URL, тому ми записуємо його в рядок адреси. Далі ми викликаємо changeLocation(), щоб встановити URL, збережений в рядку адреси, в html частину.

Подивіться, яку функціональність ви отримали, написавши лише 96 рядків, уявляєте чого ви можете досягнути, написавши 1000 рядків! :-)

p5

p5 це наша перша програма, яка буде взаємодіяти з іншими програмами (в даному випадку з p6). Ось її тексти:

// main.cpp

#include <kapp.h>
#include "p5.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p5" );
 
    MainWindow *mywindow=new MainWindow( "Tutorial - p5" );
    mywindow->resize( 300, 200 );
 
    a.setMainWidget( mywindow );
    mywindow->show();
 
    return a.exec();
}
// p5.h

#include <ktmainwindow.h>
#include <kurl.h>
#include <kparts/browserextension.h>
 
class QLineEdit;
class KHTMLPart;
class QSplitter;
class QPushButton;
 
class MainWindow : public KTMainWindow
{
 Q_OBJECT
public:
 
  MainWindow ( char * titulo );
 
public slots:
  void changeLocation();
  void openURLRequest(const KURL &url, const KParts::URLArgs & );
  void bookLocation();
 
private:
 
  QLineEdit *location;
  KHTMLPart *browser;
  QPushButton *bookmark;
  QSplitter *split;
};
// p5.cpp

#include "p5.h"
#include <qvbox.h>
#include <qlineedit.h>
#include <qpushbutton.h>
#include <qsplitter.h>
#include <dcopclient.h>
#include <kapp.h>
#include <kmenubar.h>
#include <klocale.h>
#include <khtml_part.h>
#include <kdebug.h>
 
MainWindow::MainWindow ( char * name ) : KTMainWindow ( name )
{
    QPopupMenu * filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) );
    QString about =
            i18n("p5 1.0\n\n"
                 "(C) 1999 Antonio Larrosa Jimenez\n"
                 "larrosa@kde.org\t\tantlarr@arrakis.es\n"
                 "Malaga (Spain)\n\n"
                 "Simple KDE Tutorial\n"
                 "This tutorial comes with ABSOLUTELY NO WARRANTY \n"
                 "This is free software, and you are welcome to redistribute it\n"
                 "under certain conditions\n");
 
    QPopupMenu *helpmenu = helpMenu(about);
    KMenuBar * menu = new KMenuBar( this );
    menu->insertItem( i18n( "&File" ), filemenu);
    menu->insertSeparator();
    menu->insertItem(i18n("&Help"), helpmenu);
    setMenu(menu);
 
    QVBox * vbox = new QVBox ( this );
 
    location = new QLineEdit ( vbox );
    location->setText( "http://localhost" );
 
    connect( location , SIGNAL( returnPressed() ),
                this, SLOT( changeLocation() ) );
 
    split = new QSplitter ( vbox );
    split->setOpaqueResize();
 
    bookmark = new QPushButton ( i18n("Add to Bookmarks"), split );
 
    connect( bookmark, SIGNAL( clicked() ), this, SLOT( bookLocation() ) );
 
    browser=new KHTMLPart( split );
    browser->openURL( location->text() );
 
    connect( browser->browserExtension(),
	SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
	this, SLOT( openURLRequest(const KURL &, consKParts::URLArgs & ) ) );           	     
    setView( vbox );
 
    DCOPClient *client = kapp->dcopClient();
    client->attach();
}
 
 
void MainWindow::changeLocation()
{
    browser->openURL( location->text() );
}

void MainWindow::openURLRequest(const KURL &url, const KParts::URLArgs & )
{
    location->setText( url.url() );
    changeLocation();
}
 
void MainWindow::bookLocation()
{
    DCOPClient *client=kapp->dcopClient();
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << location->text();
    if (!client->send("p6-*", "bookmarkList", "add(QString)", params))
       kdDebug() << "Error with DCOP\n";
} 

Ця програма і p6 – прості приклади нової технології для desktop communication, DCOP. DCOP заснований на ICE IPC/RPC механізмі, що прозоро використовує локальні сокети Unix або TCP/IP сокети.

p6 буде програмою, котра сортує закладки (bookmarks), передані їй p5 на зберігання. Подивимося, як просто ми можемо це зробити:

public slots:
  void changeLocation();
  void openURLRequest(const KURL &url, const KParts::URLArgs & );
  void bookLocation();
 
private:
 
  QLineEdit *location;
  KHTMLPart *browser;
  QPushButton *bookmark;
  QSplitter *split;

Ми скористаємося новим слотом, який буде викликатися коли користувач захоче зберегти адресу в bookmark-програмі. Також, нам будуть потрібні декілька нових віджетів.

    split = new QSplitter ( vbox );
    split->setOpaqueResize();

Ми хочемо показати велику кнопку на стороні html browser widget. Замість робити її фіксованого розміру, ми дозволимо користувачу змінювати розміри цієї кнопки. Організуємо це з допомогою QSplitter, віджету, який поширює зміну свого розміру на своїх дітей.

Ми вказуємо split непрозоро змінювати розмір потомків, для їхнього перемальовування в процесі зміни розміру користувачем, замість того, щоб перемальовувати по закінченню.

    bookmark = new QPushButton ( i18n("Add to Bookmarks"), split );
 
    connect( bookmark, SIGNAL( clicked() ), this, SLOT( bookLocation() ) );

Тепер ми можемо створити нову QPushButton з локалізованим текстом.

Сигнал clicked() приєднаний до нашого слота bookLocation().

    browser=new KHTMLWidget( split );

browser тепер теж потомок split, разом з клавішею bookmark. Як було сказано раніше, це дозволяє split керувати розмірами обох віджетів.

    DCOPClient *client = kapp->dcopClient();
    client->attach();

Ми вже знайомі з макросом kapp, який використовується для посилання на поточний екземпляр KApplication, цим шляхом ми можемо отримати об’єкт DCOPClient і наказати йому приєднати (attach) цю програму до сервера DCOP, який мусить виконуватися цій ж машині (dcopserver фундаментальна частина десктопу, а тому вона завжди запущена).

    DCOPClient *client=kapp->dcopClient();
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);

Цей слот викликається, коли користувач натискає кнопку, щоб додати поточний URL до закладок. Спочатку ми отримуємо об’єкт DCOPClient цієї програми, а потім ініціюємо об’єкт QDataStream.

QDataStream це клас, призначений для перетворення в послідовну форму (серіалізацію) даних довільного типу. Він повністю незалежний від конкретної машини (порядку байтів, CPU, і т.д.). Отже ви можете створити потік (stream) на big-endian системі, і зчитувати його без всяких проблем на little-endian системі.

    stream << location->text();

Оскільки потік може містити довільні дані, використаємо його, щоб зберегти аргументи функції, яку ми викличемо в p6. Помістимо в потік текст адреси.

    if (!client->send("p6-*", "bookmarkList", "add(QString)", params))
       kdDebug() << "Error with DCOP\n";

Нарешті, посилаємо виклик DCOP до p6.

Перший аргумент – ім’я програми, до якої потрібно приєднатися. Другий – назва в програмі об’єкта, яким ми хочемо скористатися, а третій – ім’я того члена об’єкта, якого ми хочемо викликати. params це потік в якому містяться параметри, що мають бути передані цьому методу.

Іншими словами, програма p6 містить викликаний об’єкт bookmarkList, а цей об’єкт має метод add, який має параметр типу QString. Отже, ця проста лінійка викликає даний метод з p6, з переданими параметрами. Оскільки нам не потрібен код повернення з p6, ми зразу продовжуємо виконання p5, поки p6 додає URL до списку закладок. Симпатично, правда ? ;-)

Зверніть увагу, ми додали -* до імені програми, тому що p6 отримає в сервера dcopserverote ім’я у вигляді “p6-<PID>” (наприклад “p6-12532”). Скориставшись зірочкою, ми передаємо сигнал всім програмам, які задовольняють першим літерам. Якщо створювана вами програма повинна посилати сигнали тільки комусь одному, значить ви обов’язково мусите знати PID програми, з якою ви хочете зв’язуватися. Тоді можна просто послати сигнал використавши “appname-PID”.

Ми перевіряємо код повернення на випадок, якщо dcopserver не може виконати виклик. В цьому випадку ми звертаємося до відладочної підпрограми KDE, щоб програма повідомила, що щось не так. Оскільки це повідомлення призначене лише для розробників, нема потреби викликати тут i18n.

kdDebug() повертає потік, який можна використати для виведення відладочних рядків. Є кілька рівнів журналів (logs): kdDebug, kdWarning, kdError і kdFatal. Ними ви можете вказати серйозність повідомлення. Є інші функції відладки, які використовують код території (area number) для кожного повідомлення, яке вказує місце виникнення проблем. Скориставшись механізмом територій (area), ви можна додати, або видалити їх (areas) щоб відладжувати певні частини програми.

Це все щодо p5. Ви мусите запускати p5 і p6 одночасно, щоб дати їм працювати в команді, і спостерігати як вони взаємодіють..

p6

p6 дуже проста програма, яка просто показує список закладок.

// main.cpp

#include <kapp.h>
#include "p6.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p6" );
 
    MainList *mylist=new MainList;
    mylist->resize( 300, 200 );
 
    a.setMainWidget( mylist );
    mylist->show();
 
    return a.exec();
}
// p6Iface.h

#ifndef __P6IFACE_H__
#define __P6IFACE_H__
 
#include <dcopobject.h>
#include <qstring.h>
 
class p6Iface : virtual public DCOPObject
{
    K_DCOP
public:
 
k_dcop:
    virtual void add( QString s ) = 0;
 
};
 
#endif
// p6.h

#include "p6Iface.h"
#include <qlistview.h>
 
class MainList : public QListView, virtual public p6Iface
{
 Q_OBJECT
 
public:
 
 MainList();
 
 void add ( QString s );
 
};
// p6.cpp

#include "p6.h"
#include <klocale.h>
#include <kapp.h>
#include <dcopclient.h>
 
MainList::MainList() : QListView ( 0L, "Bookmarks" ), 
			DCOPObject ( "bookmarkList" )
{
   addColumn( i18n("My Bookmarks") );
 
   DCOPClient *client=kapp->dcopClient();
   client->attach();
   client->registerAs("p6");
};
 
void MainList::add( QString s )
{
   insertItem ( new QListViewItem ( this , s ) );
}; 

Замість використовувати KTMainWindow, надавши перевагу зрозумілості коду над розробкою інтерфейсу користувача, скористаємося безпосередньо елементом QListView.

QListView дозволяє вам створити віджет з списком пунктів (звичайно, з кількома стовпчиками, щоб показати різні властивості кожного елемента). Крім того, можна (і швидко) також створити дерево елементів для кращої організації інформації. В цьому маленькому прикладі ми використовуємо тільки один стовпчик списку з можливостями сортування (власне тому ми і не використовуємо QListBox, бо він буде пристосовувати наші потреби до свого простого API).

Незначна відмінність від попередніх прикладів полягає в тому, що ми маємо новий файл з іменем p6Iface.h. Давайте подивимося, що він таке та для чого він.

class p6Iface : virtual public DCOPObject
{
    K_DCOP
public:
 
k_dcop:
    virtual void add( QString s ) = 0;
 
};

Цим файлом ми визначаємо інтерфейс, який p6 експортує для використання іншими програмами. Записавши спеціальний тег k_dcop (він буде видалений препроцесором перед передачею компілятору), ми описуємо члени об’єкта, які інші зможуть викликати скориставшись DCOP, метод add в даному випадку.

Врахуйте, що ми означили цю функцію чисто віртуальною, а тому вона не реалізується в класі p6Iface. Фокус тут в можливості скористатися інструментом dcopidl, який транслює наш h-подібний опис інтерфейсу на загальну Мову опису інтерфейсів (Interface Definition Language). Це дозволяє програмам, таким як dcopidl2cpp, створювати для нас скелети реалізації, які використовуються безпосередньо DCOPObject для визначення методів, наявних в цьому об’єкті.

class MainList : public QListView, virtual public p6Iface

Дивлячись на p6.h, бачимо що наш головний віджет наслідує QListView (який буде власне демонструвати список на X-екрані ), але також наслідує тільки що означений клас p6Iface. Цим шляхом, як і хотілося, ми можемо реалізовувати методи, доступні віддалено(remotely accesible) в звичайних класах.

MainList::MainList() : QListView ( 0L, "Bookmarks" ), 
			DCOPObject ( "bookmarkList" )

Оскільки ми не реалізували жодного конструктора в p6Iface, то ми викликаємо тут конструктор (успадкований) DCOPObject. Майте на увазі, параметром конструктора є ім’я, яке дає знати DCOP про цей об’єкт, тому віддалені виклики одного з його методів будуть використовувати bookmarkList як ім’я об’єкта.

   addColumn( i18n("My Bookmarks") );

Ми додаємо стовпчик (це той мінімум, який треба зробити, щоб QListView можна було використовувати).

   DCOPClient *client=kapp->dcopClient();
   client->attach();
   client->registerAs("p6");

Тепер ця програма приєднана до сервера DCOP, як ми зробили в p5, але іншим шляхом. Оскільки p5 не отримує ніяких викликів, ми зареєструвалися анонімно (anonymous). В даному випадку ми хочемо надати можливість іншим програмам приєднуватися до нас, тому ми повинні зареєструватися з певним іменем, що власне registerAs і робить.

Звичайно, це ім’я інші повинні використовувати, роблячи виклики DCOP.

   insertItem ( new QListViewItem ( this , s ) );

Нарешті, метод add, отримавши параметр QString, конструює QListViewItem з ним, і додає пункт до QListView.

Можливо вас дивує, чому не можна просто помістити QString в QListView. Причина проста: QListView може містити значно складніші класи ніж просто рядок, наприклад, ви можете мати декілька стовпчиків, в частині з яких містяться об’єкти QPixmaps (малюнки), і т.д.

Scripting

Одна з багатьох можливостей DCOP полягає в тому, що ми можемо просто керувати будь-якою програмою, користуючись цим інтерфейсом. Як приклад цього напишемо скрипт на Python, який буде додавати URL до списку закладок.

Щоб використовувати його вам буде потрібна бібліотека xmlrpc для Python. Її можна знайти тут http://www.pythonware.com/downloads/xmlrpc-0.9.8–990621.zip, розархівуйте її в каталозі бібліотек python (звичайно це /usr/lib/python1.5). Також вам буде потрібно запустити kxmlrpcd – демон xmlrpc, який служить мостом між протоколами xmlrpc і DCOP.

Скрипт, який ми використовуємо такий:

#!/usr/bin/python
# addurl.py

from xmlrpclib import *
import os
 
rc = open(os.environ['HOME'] + '/.kxmlrpcd', 'r')
config = string.split(rc.read(), ',')
port = config[0]
auth = config[1]
 
server = Server("http://localhost:" + port +"/p6")
 
server.bookmarkList.add(auth, "http://www.kde.org")  

Я вдячний Kurt Granroth за створення цього скрипта, бо мої знання Python ніякі.

У будь-якому випадку, ми використовуємо адресу localhost коли приєднуємося до сервера. Просто змінивши цей рядок (і маючи відповідні права доступу), ми можемо приєднатися до сервера на віддаленому комп’ютері (навіть з іншим CPU і операційною системою), і контролювати з скрипта віддаленою програмою.

Ви можете навіть керувати програмою KDE скриптами оболонки bash!

Ми закінчили вивчення p6. Подивимося що там наступне.

p7

На цьому кроці ми готові зробити програму з p5 кориснішою, вивчивши декілька інших стандартних класів KDE, необхідних майже в кожній програмі.

// main.cpp

#include <kapp.h>
#include "p7.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p7" );
 
    MainWindow *mywindow=new MainWindow( "Tutorial - p7" );
    mywindow->resize( 300, 200 );
 
    a.setMainWidget( mywindow );
    mywindow->show();
 
    return a.exec();
}  
// p7Iface.h

#include <dcopobject.h>
 
class p7Iface : virtual public DCOPObject
{
   K_DCOP
 
k_dcop:
   virtual void setURL( QString s )=0;
 
}; 
// p7.h

#include "p7Iface.h"
#include <ktmainwindow.h>
#include <kurl.h>
#include <kparts/browserextension.h>
#include <qvaluestack.h>
 
class QLineEdit;
class KHTMLPart;
 
class MainWindow : public KTMainWindow, virtual public p7Iface
{
  Q_OBJECT
  public:
    MainWindow ( char * titulo );
 
    virtual void setURL ( QString url );
 
  public slots:
    void fileSetDefaultPage();
    void changeLocation();
    void bookLocation();
    void gotoPreviousPage();
    void openURLRequest(const KURL &url, const KParts::URLArgs & );
 
  private:
    QLineEdit *location;
    KHTMLPart *browser;
    QValueStack <QString> history;
}; 
// p7.cpp

#include "p7.h"
#include <qvbox.h>
#include <qlineedit.h>
#include <dcopclient.h>
#include <kfiledialog.h>
#include <kapp.h>
#include <kmenubar.h>
#include <ktoolbar.h>
#include <klocale.h>
#include <khtml_part.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kiconloader.h>
 
 
#define TOOLBAR_ID_ADDBOOKMARK 1
#define TOOLBAR_ID_BACK 2
#define TOOLBAR_ID_QUIT 3
 
MainWindow::MainWindow ( char * name ) : KTMainWindow ( name ), 
					  DCOPObject ( "browser" )
{
    QPopupMenu * filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Set default page" ), 
				this, SLOT( fileSetDefaultPage() ) );
    filemenu->insertItem( i18n( "&Quit" ), kapp, SLOT( quit() ) );
    QString about =
            i18n("p7 1.0\n\n"
                 "(C) 1999 Antonio Larrosa Jimenez\n"
                 "larrosa@kde.org\t\tantlarr@arrakis.es\n"
                 "Malaga (Spain)\n\n"
                 "Simple KDE Tutorial\n"
                 "This tutorial comes with ABSOLUTELY NO WARRANTY \n"
                 "This is free software, and you are welcome to redistribute it\n"
                 "under certain conditions\n");
 
    QPopupMenu *helpmenu = helpMenu(about);
    KMenuBar * menu = new KMenuBar( this );
    menu->insertItem( i18n( "&File" ), filemenu);
    menu->insertSeparator();
    menu->insertItem(i18n( "&Help" ), helpmenu);
    setMenu(menu);
 
    KToolBar *toolbar=new KToolBar(this)
    toolbar->insertButton(BarIcon("reload"), TOOLBAR_ID_ADDBOOKMARK,
                          SIGNAL(clicked(int)),this,SLOT(bookLocation()),TRUE,
                          i18n("Add to Bookmarks"));
    toolbar->insertButton(BarIcon("back"), TOOLBAR_ID_BACK,
                          SIGNAL(clicked(int)),this,SLOT(gotoPreviousPage()),
                          FALSE, i18n("Back to previous page"));
    toolbar->insertButton(BarIcon("exit"), TOOLBAR_ID_QUIT,
                          SIGNAL(clicked(int)),kapp,SLOT(quit()),TRUE,
                          i18n("Quit the application")); 
    addToolBar(toolbar);
 
    QVBox * vbox = new QVBox ( this );
 
    location = new QLineEdit ( vbox );
 
    KConfig *config=kapp->config();
    config->setGroup("Settings");
    location->setText( config->readEntry( "defaultPage", 
				"http://localhost") );
 
    connect( location , SIGNAL( returnPressed() ),
                this, SLOT( changeLocation() ) );
 
    browser=new KHTMLPart( vbox );
    browser->openURL( location->text() );
 
    connect( browser->browserExtension(),
	SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
	this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );          
    setView( vbox );
 
    DCOPClient *client = kapp->dcopClient();
    client->attach();
    client->registerAs("p7");
}
 
 
void MainWindow::changeLocation()
{
    history.push( browser->url() );
    toolBar()->setItemEnabled( TOOLBAR_ID_BACK, TRUE);
    browser->openURL( location->text() );
}
 
void MainWindow::setURL( QString url )
{
    location->setText( url );
    changeLocation();
}
 
void MainWindow::openURLRequest( const KURL &url, const KParts::URLArgs & )
{
    setURL( url.u() );            
}                                                      

void MainWindow::gotoPreviousPage()
{
    location->setText( history.pop() );
    if (history.isEmpty()) toolBar()->setItemEnabled( TOOLBAR_ID_BACK, FALSE);
    browser->openURL( location->text() );
}
 
void MainWindow::bookLocation()
{
    DCOPClient *client=kapp->dcopClient();
    QByteArray params;
    QDataStream stream(params, IO_WriteOnly);
    stream << location->text();
    if (!client->send("p8-*", "bookmarkList", "add(QString)", params))
       kdDebug << "Error with DCOP\n";
}
 
void MainWindow::fileSetDefaultPage()
{
    KConfig *config=kapp->config();
 
    config->setGroup("Settings");
    config->writeEntry( "defaultPage", browser->url().url() );
}

Перше, що ми зробимо, так це додамо DCOP інтерфейс до p7 для використання p8 (наступного покоління списку закладок :-) ). Робимо це абсолютно так само, як і в p6, тому я не буду додатково коментувати.

#define TOOLBAR_ID_ADDBOOKMARK 1
#define TOOLBAR_ID_BACK 2
#define TOOLBAR_ID_QUIT 3

Добре, тепер подивимося на початок p7.cpp. Тут ми описали певні ідентифікатори щоб визначати кнопки панелі інструментів. Це не завжди потрібно, і ви можете створювати панелі інструментів, дозволивши відповідному класу прив’язувати стандартні ідентифікатори. Але оскільки ми хочемо дозволяти/забороняти кнопки на панелі, то більш очевидно буде робити це таким способом.

MainWindow::MainWindow ( char * name ) : KTMainWindow ( name ), 
					  DCOPObject ( "browser" )
{
    QPopupMenu * filemenu = new QPopupMenu;
    filemenu->insertItem( i18n( "&Set default page" ), 
				this, SLOT( fileSetDefaultPage() ) );

Клас MainWindow також тепер наслідується від p7Iface, тому нам потрібно викликати DCOPObject конструктор. Назвемо цей об’єкт browser. Тепер в меню File є ще один пункт, який буде встановлювати поточну сторінку такою, що буде завантажуватися по замовчуванню при запуску програми. Це продемонструє нам, як зберігати змінні конфігурації.

    KToolBar *toolbar=new KToolBar(this);

KToolBar – клас, який реалізує панель інструментів під KDE. Скориставшись ним ви автоматично отримуєте стандартний вигляд панелі інструментів, ідентифікатор для переміщення її в і з вікна, та інше.

    toolbar->insertButton(BarIcon("reload"), TOOLBAR_ID_ADDBOOKMARK,
                          SIGNAL(clicked(int)),this,SLOT(bookLocation()),TRUE,
                          i18n("Add to Bookmarks"));
    toolbar->insertButton(BarIcon("back"), TOOLBAR_ID_BACK,
                          SIGNAL(clicked(int)),this,SLOT(gotoPreviousPage()),
                          FALSE, i18n("Back to previous page"));
    toolbar->insertButton(BarIcon("exit"), TOOLBAR_ID_QUIT,
                          SIGNAL(clicked(int)),kapp,SLOT(quit()),TRUE,
                          i18n("Quit the application")); 

Тепер ми додамо три кнопки, перша замість великої (і, якщо дозволите, жахливої) кнопки, якою ми користувалися в p5. Друга – для повернення на попередню сторінку, тому нам буде потрібен засіб для керування історією відвіданих сторінок. Нарешті, третя кнопка потрібна для виходу з програми. (Ремарка: Інструкції по створенню інтерфейсу користувача не рекомендують розташовувати клавішу для виходу з програми на панелі інструментів, але, все-таки, це не справжня програма, тому ми проігноруємо цю серйозну пораду).

BarIcon("reload") функція, яка автоматично знаходить іконку reload (де б вона не була, якщо вона на стандартному місці для ікон, наприклад $KDEDIR/share/toolbar або $KDEDIR/share/apps/<appname>/toolbar) і повертає правильний об’єкт QPixmap, що містить її.

Наступний параметр це ідентифікатор, який ми в майбутньому будемо використовувати, щоб звертатися до цієї кнопки, потім йде сигнал, який ми хочемо приєднати і об’єкт, який буде отримувати його, а наступним є слот, який буде викликаний. Зауважте, що в більшості випадків вам не потрібно турбуватися про клавіші панелі управління, тому ви можете просто використовувати 0 в ролі ідентифікатора для кожної кнопки, якщо вам не потрібно прив’язати унікальний ідентифікатор кожній з них. Виняток з цього виникає лише тоді, коли ви хочете дозволити/заборонити клавішу, так, як ми хочемо зробити з кнопкою back.

    addToolBar(toolbar);

Ми закінчили формування панелі інструментів, тому ми можемо добавити її до головного віджету з допомогою addToolBar. addToolBar не наслідує ту ж термінологю, що setMenu, бо ви не обмежені просто використанням панелі інструментів (як ви мусите робити з меню), тому ви не встановлюєте а додаєте її.

    KConfig *config=kapp->config();
    config->setGroup("Settings");
    location->setText( config->readEntry( "defaultPage", 
				"http://localhost") );

Ми вже знаємо, що таке kapp, але раніше ми не використовували метод config(). Цей метод повертає активний об’єкт KConfig для програми. KConfig використовується для збереження конфігураційних змінних програми. Він автоматично зберігає ключі в конфігураційному файлі програми і відновлює їх коли програма запускається знову.

Маючи об’єкт конфігурації, ми робимо активною для роботи з нею групу Settings. Групи використовуються для кращого структурування конфігураційного файла.

Нарешті, замість використання по замовчуванню адреси “http://localhost” , ми дивимося в конфігурацію програми. defaultPage це ім’я потрібного нам ключа, а “http://localhost” – значення, яке повертається за замовчуванням, якщо в файлі конфігурації не збережено ніякого значення для defaultPage.

Ви також можете читати дійсні числові значення методом KConfig::readNumEntry, булеві значення з допомогою KConfig::readBoolEntry, і т.д.

    DCOPClient *client = kapp->dcopClient();
    client->attach();
    client->registerAs("p7");

Нарешті, ми приєднуємо цю програму до dcop сервера і реєструємо її як p7, бо хочемо дозволити p8 викликати її для встановлення сторінки5.

void MainWindow::setURL( QString url )
{
    location->setText( url );
    changeLocation();
}

Цей член тепер використовується для загального встановлення переданого URL. Зараз ця функція може викликатися інтерфейсом DCOP, або коли користувач вибрав гіперпосилання. Ми спочатку встановлюємо текст адреси (location) щоб показати URL, а потім викликаємо функцію changeLocation.

void MainWindow::changeLocation()
{
    history.push( browser->url() );
    toolBar()->setItemEnabled( TOOLBAR_ID_BACK, TRUE);
    browser->openURL( location->text() );
}

changeLocation (яка також приєднана до сигналу enterPressed() в об’єкті object) використовується як центральне місце завантаження нової сторінки, URL якої вже присутній в рядку адреси. Робимо таким чином, щоб реалізувати можливості історії в p7.

Пам’ятайте, що ми означили об’єкт history в p7.h з типом QValueStack<QString>. QValueStack – клас Qt, що реалізує стек, використовуючи шаблонний аргумент як тип даних його елементів. Різниця між QValueStack і QStack в тому, що перший зберігає введене значення, роблячи копію кожного елемента (для цього має бути присутній конструктор копіювання), в той час коли другий просто зберігає вказівники на додані об’єкти, а тому ви мусите турбуватися про те, щоб не знищити ці елементи поки вони залишаються в стеку.

Оскільки клас QString розділяє дані між всіма копіями, аж поки котрась з них не змінюється, то втрати продуктивності при створенні копій URL незначні.

Тепер ми маємо задану за замовчуванням панель інструментів (якщо потрібно щось більше, ми можемо отримати інші, передавши ідентифікатор як параметр для toolBar), і дозволяємо кнопку back, щоб користувач міг її натиснути.

Нарешті ми можемо відкрити новий URL в browser widget, взявши URL з панелі адреси.

void MainWindow::gotoPreviousPage()
{
    location->setText( history.pop() );

Коли користувач хоче повернутися на попередню сторінку, ми просто беремо доданий останнім елемент з стеку історії (з виштовхуванням, бо це все ж таки стек:-)) і розташовуємо його на панелі адреси. Рядок видаляється зі стеку, тому, коли ми наступний раз викличемо pop, ми отримаємо попередній URL, і так далі, поки стек не залишиться порожнім.

    if (history.isEmpty()) toolBar()->setItemEnabled( TOOLBAR_ID_BACK, FALSE);
    browser->openURL( location->text() );
}

Якщо стек порожній, краще заборонити кнопку back. Як тільки ми добавимо інший елемент, кнопка буде знову дозволена в changeLocation(). Тепер ми відкриваємо виштовхнуте URL в браузері.

void MainWindow::fileSetDefaultPage()
{
    KConfig *config=kapp->config();
 
    config->setGroup("Settings");
    config->writeEntry( "defaultPage", browser->url() );
}

Нарешті, ми реалізуємо метод, приєднаний до пункту меню Set default page. Цей метод зберігає поточний URL в конфігураційному файлі цієї програми.

Спочатку ми отримуємо об’єкт KConfig і вибираємо групу Settings як ми робили в конструкторі. Потім записуємо новий пункт за допомогою writeEntry.

Перший параметр – ім’я, а другий значення, яке ми встановили. Зауважте, що, завдяки перевантаженні функцій-членів ми можемо використовувати writeEntry з різними типами даних в другому параметрі (int, bool, float, QString, QStringList, QFont тощо).

Ми наближаємося до кінця підручника, але спершу внесемо невеликі зміни до p8 і дозволимо їй взаємодіяти з p7, для того щоб встановлювати поточну сторінку коли ви натиснули на URL в списку закладок (інакше немає змісту мати список закладок:-)).

p8

p8 це вдосконалена версія p6 з можливістю передавання URL, що міститься в вибраному зі списку пункті, програмі p7. Цим ми надаємо p7 можливість відкривати улюблені URL.

// main.cpp

#include <kapp.h>
#include "p8.h"
 
int main( int argc, char **argv )
{
    KApplication a( argc, argv, "p8" );
 
    MainList *mylist=new MainList;
    mylist->resize( 300, 200 );
 
    a.setMainWidget( mylist );
    mylist->show();
 
    return a.exec();
}
// p8Iface.h

#include <dcopobject.h>
#include <qstring.h>
 
class p8Iface : virtual public DCOPObject
{
    K_DCOP

k_dcop:
    virtual void add( QString s ) = 0;
 
};
// p8.h

#include "p8Iface.h"
#include <qlistview.h>
 
class MainList : public QListView, virtual public p8Iface
{
 Q_OBJECT
 
public:
 
 MainList();
 
 void add ( QString url );
public slots:
 
 void setURLInBrowser ( QListViewItem *item );
 
};
// p8.cpp

#include "p8.h"
#include <klocale.h>
#include <kapp.h>
#include <dcopclient.h>
#include <kdebug.h>
 
MainList::MainList() : QListView ( 0L, "Bookmarks" ),
                        DCOPObject ( "bookmarkList" )
{
  addColumn( i18n("My Bookmarks") );
  connect( this, SIGNAL(clicked(QListViewItem *)), 
		this, SLOT(setURLInBrowser(QListViewItem *)));
 
  DCOPClient *client=kapp->dcopClient();
  client->attach();
  client->registerAs("p8");
}
 
void MainList::add( QString url )
{
  insertItem ( new QListViewItem ( this , url ) );
}
 
void MainList::setURLInBrowser( QListViewItem *item )
{
  if (item==0L) return;
  DCOPClient *client=kapp->dcopClient();
  QByteArray params;
  QDataStream stream(params, IO_WriteOnly);
  stream << item->text(0);
  if (!client->send("p7-*", "browser", "setURL(QString)", params))
    kdDebug << "Error with DCOP\n";
}

main.cpp, p8Iface.h дуже подібні на відповідні файли з p6, тому я не буду їх коментувати.

  connect( this, SIGNAL(clicked(QListViewItem *)), 
		this, SLOT(setURLInBrowser(QListViewItem *)));

Ми додали новий слот, setURLInBrowser(QListViewItem *), який передає URL з QListViewItem в браузер (p7).

Це оригінальний приклад того, що ви навіть можете приєднати сигнал об’єкта до слота в цьому ж самому об’єкті. Звичайно, можливо це не найкращий шлях добитися потрібного результату, але, принаймі воно зрозуміло і підходить для використання в підручниках на зразок цього.

На випадок якщо ви не пам’ятаєте ще з p6, QListViewItem – клас, який представляє елемент з QListView. Коли ви натискаєте на нього, об’єкт QListView генерує сигнал, параметром якого є той QListViewItem, на який натиснув користувач.

void MainList::setURLInBrowser( QListViewItem *item )
{
  if (item==0L) return;

В цьому слоті ми, перш за все, перевіряємо, чи користувач вибрав елемент. Якщо користувач натиснув на порожній ділянці, віджет також генерує сигнал clicked (користувач все ж таки натиснув на ньому), але з параметром NULL. У цьому випадку ми просто не робимо нічого.

  DCOPClient *client=kapp->dcopClient();
  QByteArray params;
  QDataStream stream(params, IO_WriteOnly);
  stream << item->text(0);

Якщо ми маємо послати URL, ми отримуємо об’єкт DCOPCLient так само, як і в p5 чи p7, щоб відправити повідомлення DCOP і потім підготувати потік даних.

Використавши item->text(0) ми отримуємо текст з першого стовпчика вибраного елемента (перший стовпчик – єдиний, створений нами), і зберігаємо його в потоці.

  if (!client->send("p7-*", "browser", "setURL(QString)", params))
    kdebugError( "Error with DCOP");
}

Нарешті, ми посилаємо DCOP виклик до програми p7, об’єкта browser і методу setURL(QString) з параметром params.

Тепер ми вже майже досягли кінця підручника, але спершу давайте проробимо деякі модифікації щоб спростити код, використавши найновіші доступні технології.

p9

Зараз ми спостимо p7, максимально розширивши можливості настроювання під потреби користувача, використавши новий проект XMLGUI для побудови інтерфейсу користувача.

// main.cpp

#include <kapp.h>
#include <kcmdlineargs.h>
#include <kaboutdata.h>
#include "p9.h"

int main( int argc, char **argv )
{
  KAboutData *aboutdata = new KAboutData("p9", "KDE Tutorial - P9", "1.0",
      "Step 9 of a simple tutorial", KAboutData::License_GPL,
      "(C) 2000, Antonio Larrosa Jimenez","",
      "http://www.arrakis.es/~rlarrosa/tutorial.html");
  aboutdata->addAuthor("Antonio Larrosa Jimenez",
      "Original Developer/Mantainer","larrosa@kde.org",
      "http://www.arrakis.es/~rlarrosa/index.html");

  KCmdLineArgs::init(argc, argv, aboutdata);
    
  KApplication a;

  MainWindow *mywindow=new MainWindow( "Tutorial - p9" );
  mywindow->resize( 300, 200 );

  a.setMainWidget( mywindow );
  mywindow->show();

  return a.exec();
}
// p9Iface.h

#include <dcopobject.h>
 
class p9Iface : virtual public DCOPObject
{
   K_DCOP
 
k_dcop:
   virtual void setURL( QString s )=0;
 
}; 
// p9.h

#include "p9Iface.h"
#include 
#include 
#include 
#include 
 
class QLineEdit;
class KHTMLPart;

class MainWindow : public KTMainWindow, virtual public p9Iface
{
  Q_OBJECT
public:
    MainWindow ( char * titulo );

    virtual void setURL ( QString url );

public slots:
    void fileSetDefaultPage();
    void changeLocation();
    void bookLocation();
    void gotoPreviousPage();
    void openURLRequest(const KURL &url, const KParts::URLArgs & );

private:
    QLineEdit *location;
    KHTMLPart *browser;
    QValueStack  history;
};
// p9.cpp

#include "p9.h"
#include <qvbox.h>
#include <qlineedit.h>
#include <dcopclient.h>
#include <kfiledialog.h>
#include <kapp.h>
#include <klocale.h>
#include <khtml_part.h>
#include <kdebug.h>
#include <kconfig.h>
#include <kstdaction.h>

MainWindow::MainWindow ( char * name ) : KTMainWindow ( name ),
DCOPObject ( "browser" )
{
    
  KStdAction::quit(this, SLOT(close()), actionCollection());

  (void)new KAction(i18n("&Set default page"), "gohome", 0, this,
		    SLOT(fileSetDefaultPage()), actionCollection(), "set_default_page");

  (void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this,
		    SLOT(bookLocation()), actionCollection(), "add_to_bookmarks");

  (void)new KAction(i18n("Back to previous page"), "back", 0, this,
		    SLOT(gotoPreviousPage()), actionCollection(), "back");

  actionCollection()->action("back")->setEnabled(false);

  createGUI("p9ui.rc");

  QVBox * vbox = new QVBox ( this );

  location = new QLineEdit ( vbox );

  KConfig *config=kapp->config();
  config->setGroup("Settings");
  location->setText( config->readEntry( "defaultPage", "http://localhost") );

  connect( location , SIGNAL( returnPressed() ),
      this, SLOT( changeLocation() ) );

  browser=new KHTMLPart( vbox );
  browser->openURL( location->text() );

  connect( browser->browserExtension(),
      SIGNAL( openURLRequest( const KURL &, const KParts::URLArgs & ) ),
      this, SLOT( openURLRequest(const KURL &, const KParts::URLArgs & ) ) );

  setView( vbox );

  DCOPClient *client = kapp->dcopClient();
  client->attach();
  client->registerAs("p7");
}


void MainWindow::changeLocation()
{
  history.push( browser->url().url() );
  actionCollection()->action("back")->setEnabled(true);
  browser->openURL( location->text() );
}

void MainWindow::setURL( QString url )
{
  location->setText( url );
  changeLocation();
}

void MainWindow::openURLRequest( const KURL &url, const KParts::URLArgs & )
{
  setURL( url.url() );
}

void MainWindow::gotoPreviousPage()
{
  location->setText( history.pop() );
  if (history.isEmpty()) actionCollection()->action("back")->setEnabled(false);
  browser->openURL( location->text() );
}

void MainWindow::bookLocation()
{
  DCOPClient *client=kapp->dcopClient();
  QByteArray params;
  QDataStream stream(params, IO_WriteOnly);
  stream << location->text();
  if (!client->send("p8-*", "bookmarkList", "add(QString)", params))
    kdDebug << "Error with DCOP";
}

void MainWindow::fileSetDefaultPage()
{
  KConfig *config=kapp->config();

  config->setGroup("Settings");
  config->writeEntry( "defaultPage", browser->url().url() );
}     
  KAboutData *aboutdata = new KAboutData("p9", I18N_NOOP("KDE Tutorial - P9"), "1.0",
    I18N_NOOP("Step 9 of a simple tutorial"), KAboutData::License_GPL,
    "(C) 2000, Antonio Larrosa Jimenez","",
    "http://www.arrakis.es/~rlarrosa/tutorial.html");

Коли ви вирішили що ми більше не станемо змінювати main.cpp, тут відбулася ще одна зміна, котра генерує діалог about автоматично.

Клас KAboutData використовується для збереження даних для діалогу about (це було очевидно, правда ?:-)). Спершу ми передаємо внутрішнє ім’я програми, потім справжнє (те що буде відображатися) ім’я, версію, короткий опис, ліцензію, the copyright notice, довільний текст (порожній у даному випадку), і домашню сторінку програми.

Зверніть увагу, що використано макрос I18N_NOOP замість функції i18n, ми зробили так, бо ми не можемо використовувати i18n до створення об’єкту KApplication, отже записуємо I18N_NOOP, який “помічає” текст для наступного перекладу.

  aboutdata->addAuthor("Antonio Larrosa Jimenez",
    "Original Developer/Mantainer","larrosa@kde.org",
    "http://www.arrakis.es/~rlarrosa/personal.html");

Тепер ми додаємо інформацію про одного автора (ми можемо додати інформацію про стількох авторів, скількох потрібно). Спершу ім’я (ні, не помічайте його для перекладу ;-)), потім їх функції при створенні програми, а далі – його власна домашня сторінка.

  KCmdLineArgs::init(argc, argv, aboutdata);

  KApplication a;   

Тепер ми можемо ініціювати аргументи командного рядка передавши їх і об’єкт aboutdata статичному методу KCmdLineArgs::init.

Зверніть увагу: зараз ми можемо використовувати аргументи за замовчуванням для конструктора KApplication, бо він отримає всю інформацію з об’єкта aboutdata.

Ok, тепер ми можемо поглянути на p9.cpp:

#define TOOLBAR_ID_ADDBOOKMARK 1
#define TOOLBAR_ID_BACK 2
#define TOOLBAR_ID_QUIT 3

Спершу зауважимо, що цих рядків більше не існує. Ми хочемо використати інший метод, який значно більш інтуїтивний і тому дозволяє нам спростити код.

Ми будемо використовувати об’єкти KAction. Це радикальна нова і потужна можливість яка дозволить нам спостити створення і підтримку інтерфейсу користувача.

Ми створимо об’єкт KAction для кожної дії, яку може здійснити користувач. Є два види дій (actions), стандартні (такі як відкриття і збереження файла, відкидання змін, вихід, інше) і спеціальні (custom) (такі, що специфічні для кожної програми) .

  KStdAction::quit(this, SLOT(close()), actionCollection());

Спершу ми створюємо стандартну дію “quit” (вихід), яка приєднана до слота close() об’єкта this. Параметр actionCollection() це лише об’єкт, що колекціонує всі дії програми.

  (void)new KAction(i18n("&Set default page"), "gohome", 0, this,
       SLOT(fileSetDefaultPage()), actionCollection(), "set_default_page")

Тепер ми створимо нову дію. Перший параметр – текст, який буде показано коли ця дія знаходиться в меню, другий параметр це ім’я іконки, яка буде відображатися коли ця дія знаходитиметься в меню або на панелі інструментів, зверніть увагу на те, що ми вказуємо лише ім’я ікони, а не її саму. Це дозволяє бібліотекам робити з нею деякі приємні речі, такі як використання і підбирання правильного розміру іконок для меню та інструментальних панелей. Третій параметр – кнопка швидкого доступу (accelerator), яку можна натиснути для активації дії (в нашому випадку ми не маємо акселератора). Тепер, ми встановлюємо об’єкт і слот, які будуть викликатися коли дія активується, а також об’єкт actionCollection().

Нарешті, ми встановлюємо ім’я для дії (ім’я має визначати дію, а тому має бути унікальним).

Зверніть увагу на те, що ми не зберігаємо дію в якійсь змінній, бо нею можна керувати за допомогою об’єкта, повернутого actionCollection().

  (void)new KAction(i18n("Add to Bookmarks"), "reload", 0, this,
		    SLOT(bookLocation()), actionCollection(), "add_to_bookmarks");

  (void)new KAction(i18n("Back to previous page"), "back", 0, this,
		    SLOT(gotoPreviousPage()), actionCollection(), "back");

Ми створюємо ще декілька дій: для додавання поточної сторінки до закладок, і для повернення на попередню сторінку.

  actionCollection()->action("back")->setEnabled(false);

Цим викликом ми забороняємо кнопку “back”. Спершу ми отримуємо колекцію дій, і запитуємо про дію з іменем “back”, а потім викликаємо для цієї дії setEnabled(false) щоб заборонити її.

Основною перевагою такого підходу є відсутність необхідності керувати елементами меню і панелями інструментів окремо, ми просто забороняємо дію, і відповідні елементи меню і іконки панелі інструментів відключаються (стають забороненими) автоматично. Більше не потрібно мільйони означень для кожного ID меню, і кожного ID панелі інструментів, класно, правда ? ;-)

  createGUI("p9ui.rc");

Викликавши createGUI, ми справді поміщаємо дії в меню і на панель інструментів.

Тепер перевіримо вміст p9ui.rc:

<!DOCTYPE kpartgui>
<kpartgui name="p9">
<MenuBar>
  <Menu name="file"><text>&File</text>
    <Action name="set_default_page"/>
  </Menu>
</MenuBar>
<ToolBar fullWidth="true" name="mainToolBar">
  <Action name="add_to_bookmarks"/>
  <Action name="back"/>
  <Separator/>
  <Action name="file_quit"/>
</ToolBar>
</kpartgui>   

Це файл XML, який містить опис GUI для p9.

<kpartgui name="p9">

Це вказує, що p9 є програмою, для якої ми створюємо GUI.

<MenuBar>
  <Menu name="file"><text>&File</text>
    <Action name="set_default_page"/>
  </Menu>
</MenuBar>

Спершу ми означуємо меню в секції MenuBar. Для кожного меню ми маємо одну секцію Menu з її ім’ям (в даному випадку ми маємо лише меню file, бо меню допомоги (help) додається автоматично), і в кожній секції меню ми поміщаємо дії, які мають з’являтися в цьому меню.

<ToolBar fullWidth="true" name="mainToolBar">
  <Action name="add_to_bookmarks"/>
  <Action name="back"/>
  <Separator/>
  <Action name="file_quit"/>
</ToolBar>

Тепер, ми визначили панель інструментів (фактично “mainToolBar” бо ми можемо мати декілька панелей інструментів). Ми просто записуємо дії, які ми хочемо бачити на панелі інструментів з певними (необов’язковими) сепараторами.

Крім того, той самий синтаксис для сепараторів може бути використаний, щоб вставити сепаратори в меню.

Повертаючись до p9.cpp, зазначимо, що ми реєструємо його так само як “p7” на dcopserver. Ми робимо так, просто тому, щоб використовувати p8 разом з p9 як ми це робили з p7.

Як цікавий додаток, порівняємо кількість рядків в p7.cpp і p9.cpp:

  wc p7/p7.cpp p9/p9.cpp
     124     324    3454 p7/p7.cpp
     102     238    2687 p9/p9.cpp   

В p9.cpp ми маємо на 22 рядки менше ніж в p7.cpp!!!

Забравши ці 17.7 процентів вихідних текстів, ми надали користувачу можливість перенастроювати інтерфейс (він тільки мусить змінити XML файл і перезапустити програму, не перекомпільовуючи її !).

Якщо ви хочете довідатися більше про проект KXMLGUI, не забудьте прочитати прекрасний підручник, написаний Kurt Granroth. Його ви можете знайти тут http://developer.kde.org/documentation/tutorials/xmlui/preface.html.

Це останній крок в даному підручнику. Я сподіваюся ви багато чому навчилися, і вирішили почати ваші KDE програми. В цьому випадку дивіться на sugerences в Передмові за інструкціями, як отримати вирішення ваших майбутніх проблем при програмуванні, і за певними ідеями по створенню ваших програм

Дякую за увагу,

Antonio Larrosa

Виноски

  1. Використовую ту адресу автора, що була в оригінальному документі. Сайт автора все ще є тут, але активності там немає дуже давно. Зате там знайшовся такий лінк: “Round table conferences about Relativity and Quantum Theory” 

  2. Не маючи впевнений в правильності перекладу технічних термінів я залишаю їх англійський варіант - прим. перекл. 

  3. Сучасна нотатка: автор так описує COW – Copy-on-Write. 

  4. Використання готового компонування – прим. перекл. 

  5. Для відображення – прим. перекл.