• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

kblackbox

kbbmainwindow.cpp

Go to the documentation of this file.
00001 //
00002 // KBlackBox
00003 //
00004 // A simple game inspired by an emacs module
00005 //
00006 /***************************************************************************
00007  *   Copyright (c) 1999-2000, Robert Cimrman                               *
00008  *   cimrman3@students.zcu.cz                                              *
00009  *                                                                         *
00010  *   Copyright (c) 2007, Nicolas Roffet                                    *
00011  *   nicolas-kde@roffet.com                                                *
00012  *                                                                         *
00013  *                                                                         *
00014  *   This program is free software; you can redistribute it and/or modify  *
00015  *   it under the terms of the GNU General Public License as published by  *
00016  *   the Free Software Foundation; either version 2 of the License, or     *
00017  *   (at your option) any later version.                                   *
00018  *                                                                         *
00019  *   This program is distributed in the hope that it will be useful,       *
00020  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00021  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00022  *   GNU General Public License for more details.                          *
00023  *                                                                         *
00024  *   You should have received a copy of the GNU General Public License     *
00025  *   along with this program; if not, write to the                         *
00026  *   Free Software Foundation, Inc.,                                       *
00027  *   51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA               *
00028  ***************************************************************************/
00029 
00030 #include "kbbmainwindow.h"
00031 
00032 
00033 
00034 #include <QFile>
00035 #include <QHBoxLayout>
00036 #include <QWidget>
00037 
00038 
00039 #include <KActionCollection>
00040 #include <KConfigDialog>
00041 #include <KGameClock>
00042 #include <KGameDifficulty>
00043 #include <KGamePopupItem>
00044 #include <KGlobal>
00045 #include <KLocale>
00046 #include <KMessageBox>
00047 #include <KScoreDialog>
00048 #include <KStandardDirs>
00049 #include <KStandardGameAction>
00050 #include <KStatusBar>
00051 #include <KToggleAction>
00052 
00053 
00054 #include "kbbgamedoc.h"
00055 #include "kbbgraphicsitemtutorialmarker.h"
00056 #include "kbblevelconfigurationwidget.h"
00057 #include "kbbprefs.h"
00058 #include "kbbscalablegraphicwidget.h"
00059 #include "kbbthememanager.h"
00060 #include "kbbtutorial.h"
00061 
00062 
00063 
00064 //
00065 // Constructor / Destructor
00066 //
00067 
00068 KBBMainWindow::KBBMainWindow()
00069 {
00070     m_boardEnabled = false;
00071 
00072     //Read configuration options
00073     m_customBallNumber = KBBPrefs::balls();
00074     m_customColumns = KBBPrefs::columns();
00075     m_customRows = KBBPrefs::rows();
00076 
00077 
00078     // Status bar
00079     statusBar()->insertPermanentItem("", SRUN, 1);
00080     statusBar()->insertPermanentItem(i18n("Time: 00:00"), STIME, 1);
00081     statusBar()->insertPermanentItem(i18n("Size: 00 x 00"), SSIZE);
00082     statusBar()->setItemAlignment(SRUN, Qt::AlignLeft | Qt::AlignVCenter);
00083 
00084 
00085     // Difficulty
00086     KGameDifficulty::init(this, this, SLOT(levelChanged(KGameDifficulty::standardLevel)));
00087     KGameDifficulty::addStandardLevel(KGameDifficulty::VeryEasy);
00088     KGameDifficulty::addStandardLevel(KGameDifficulty::Easy);
00089     KGameDifficulty::addStandardLevel(KGameDifficulty::Medium);
00090     KGameDifficulty::addStandardLevel(KGameDifficulty::Hard);
00091     KGameDifficulty::addStandardLevel(KGameDifficulty::VeryHard);
00092     KGameDifficulty::addStandardLevel(KGameDifficulty::ExtremelyHard);
00093     KGameDifficulty::addStandardLevel(KGameDifficulty::Configurable);
00094 
00095 
00096     // Menu "Game"
00097     KStandardGameAction::gameNew(this, SLOT(newGame()), actionCollection());
00098     m_pauseAction = KStandardGameAction::pause(this, SLOT(pause(bool)), actionCollection());
00099     QAction* tutorial = actionCollection()->addAction("game_tutorial");
00100     tutorial->setText(i18n("Start Tutorial"));
00101     tutorial->setIcon(KIcon("footprint"));
00102     tutorial->setToolTip(i18n("Start tutorial"));
00103     tutorial->setWhatsThis(i18n("<qt>The <b>tutorial</b> is a fast, user friendly and interactive way to learn the rules of the game. Start it if you do not know them!</qt>"));
00104     connect(tutorial, SIGNAL(triggered(bool)), SLOT(startTutorial()));
00105     KStandardGameAction::quit(this, SLOT(close()), actionCollection());
00106     QAction* sandbox = actionCollection()->addAction("game_sandbox");
00107     sandbox->setText(i18n("New Sandbox Game"));
00108     sandbox->setToolTip(i18n("Start a new sandbox game"));
00109     sandbox->setWhatsThis(i18n("<qt><p>In a <b>sandbox game</b>, the solution is displayed at the beginning of the game. This is useful to understand the game principles.</p><p>However: after a while, it is not really fun and you should try to start a real game!</p></qt>"));
00110     connect(sandbox, SIGNAL(triggered(bool)), SLOT(startSandbox()));
00111     KStandardGameAction::highscores(this, SLOT(showHighscores()), actionCollection());
00112 
00113     // Menu "Move"
00114     m_doneAction = actionCollection()->addAction("move_done");
00115     m_doneAction->setText(i18nc("This is the last action of a game to check the result, when the user is done.", "Done!"));
00116     m_doneAction->setWhatsThis(i18n("<qt><ul><li>First, you have to place all the balls on the black box. To guess the right possitions of the balls and see how they interact with laser beams, you should use the lasers that are disposed around the black box.</li><li><b>When you think you are done</b>, you should click here.</li></ul><p>Note that it is possible to click here only if you placed the right number of balls.</p></qt>"));
00117     m_doneAction->setIcon(KIcon("dialog-ok"));
00118     connect(m_doneAction, SIGNAL(triggered(bool)), SLOT(done()));
00119     m_solveAction = KStandardGameAction::solve(this, SLOT(solve()), actionCollection());
00120     m_solveAction->setToolTip(i18n("Give up the game"));
00121     m_solveAction->setWhatsThis(i18n("<qt><p>Choose \"<b>Solve</b>\" if you want to give up the current game. The solution will be displayed.</p><p>If you placed all the balls and do not want to give up, choose \"Done!\".</p></qt>"));
00122 
00123     // Menu "Settings"
00124     KStandardAction::preferences(this, SLOT(settingsDialog()), actionCollection());
00125 
00126 
00127     // Theme manager
00128     QString svgzFile = KBBPrefs::theme();
00129     if (!QFile(svgzFile).exists())
00130         svgzFile = KStandardDirs::locate("appdata", "pics/kblackbox.svgz");
00131     m_themeManager = new KBBThemeManager(svgzFile);
00132     
00133     
00134     // Tutorial widget
00135     m_tutorial = new KBBTutorial(this);
00136 
00137 
00138     // Board
00139     m_gameDoc = new KBBGameDoc(this, m_tutorial);
00140     connect(m_gameDoc, SIGNAL(updateStats()), this, SLOT(updateStats()) );
00141     connect(m_gameDoc, SIGNAL(isRunning(bool)), SLOT(setRunning(bool)));
00142 
00143 
00144     // Game widget
00145     m_gameWidget = new KBBScalableGraphicWidget(m_gameDoc, m_themeManager, m_doneAction);
00146     m_tutorial->setGameWidget(m_gameWidget, new KBBGraphicsItemTutorialMarker(m_gameWidget, m_themeManager, KBBTutorial::COLUMNS, KBBTutorial::ROWS));
00147 
00148 
00149     // Central Widget
00150     m_centralWidget = new QWidget(this);
00151     QHBoxLayout *widgetLayout = new QHBoxLayout();
00152     widgetLayout->setMargin(0);
00153     m_centralWidget->setLayout(widgetLayout);
00154     widgetLayout->addWidget(m_gameWidget);
00155     widgetLayout->addWidget(m_tutorial);
00156     setCentralWidget(m_centralWidget);
00157 
00158 
00159     // Keyboard only
00160     QAction* action = actionCollection()->addAction( "move_down" );
00161     action->setText( i18n("Move Down") );
00162     connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveDown()));
00163     action->setShortcut(Qt::Key_Down);
00164     addAction(action);
00165     
00166     action = actionCollection()->addAction( "move_up" );
00167     action->setText( i18n("Move Up") );
00168     connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveUp()));
00169     action->setShortcut(Qt::Key_Up);
00170     addAction(action);
00171     
00172     action = actionCollection()->addAction( "move_left" );
00173     action->setText( i18n("Move Left") );
00174     connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveLeft()));
00175     action->setShortcut(Qt::Key_Left);
00176     addAction(action);
00177     
00178     action = actionCollection()->addAction( "move_right" );
00179     action->setText( i18n("Move Right") );
00180     connect(action, SIGNAL(triggered(bool) ), m_gameWidget, SLOT(keyboardMoveRight()));
00181     action->setShortcut(Qt::Key_Right);
00182     addAction(action);
00183     
00184     action = actionCollection()->addAction("switch_ball");
00185     action->setText(i18n("Switch Ball or Shoot Laser"));
00186     connect(action, SIGNAL(triggered(bool)), m_gameWidget, SLOT(keyboardEnter()));
00187     action->setShortcut(Qt::Key_Return);
00188     addAction(action);
00189     
00190     action = actionCollection()->addAction("switch_marker");
00191     action->setText(i18n("Switch Marker"));
00192     connect(action, SIGNAL(triggered(bool)), m_gameWidget, SLOT(keyboardSpace()));
00193     action->setShortcut(Qt::Key_Space);
00194     addAction(action);
00195 
00196 
00197     m_gameClock = new KGameClock(this, KGameClock::MinSecOnly);
00198     connect(m_gameClock, SIGNAL(timeChanged(const QString&)), SLOT(updateStats()));
00199     connect(m_gameClock, SIGNAL(timeChanged(const QString&)), m_gameDoc, SLOT(timeChanged()));
00200 
00201 
00202     levelChanged((KGameDifficulty::standardLevel) (KBBPrefs::level()));
00203     KGameDifficulty::setLevel(m_level);
00204 
00205     
00206     setupGUI();
00207 
00208     // start a new game
00209     startGame(false);
00210 }
00211 
00212 
00213 KBBMainWindow::~KBBMainWindow()
00214 {
00215     KBBPrefs::self()->writeConfig();
00216 
00217     delete m_gameWidget;
00218     delete m_themeManager;
00219 }
00220 
00221 
00222 
00223 //
00224 // Public slots
00225 //
00226 
00227 void KBBMainWindow::levelChanged(KGameDifficulty::standardLevel level)
00228 {
00229     switch(level) {
00230         case KGameDifficulty::VeryEasy:
00231             m_ballNumber = 2;
00232             m_columns = 6;
00233             m_rows = 6;
00234             break;
00235         case KGameDifficulty::Easy:
00236         default:
00237             m_ballNumber = 4;
00238             m_columns = 8;
00239             m_rows = 8;
00240             level = KGameDifficulty::Medium;
00241             break;
00242         case KGameDifficulty::Medium:
00243             m_ballNumber = 6;
00244             m_columns = 10;
00245             m_rows = 10;
00246             break;
00247         case KGameDifficulty::Hard:
00248             m_ballNumber = 8;
00249             m_columns = 12;
00250             m_rows = 12;
00251             break;
00252         case KGameDifficulty::VeryHard:
00253             m_ballNumber = 11;
00254             m_columns = 14;
00255             m_rows = 10;
00256             break;
00257         case KGameDifficulty::ExtremelyHard:
00258             m_ballNumber = 15;
00259             m_columns = 20;
00260             m_rows = 12;
00261             break;
00262         case KGameDifficulty::Configurable:
00263             m_gameWidget->popupText(i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Note: You can change<br />the parameters of<br />custom games in the<br />setting dialog."));
00264             break;
00265     }
00266 
00267     m_level = level;
00268     KBBPrefs::setLevel((int)(m_level));
00269     startGame(m_sandboxMode);
00270 }
00271 
00272 
00273 void KBBMainWindow::setRunning(bool r)
00274 {
00275     // Difficulty
00276     KGameDifficulty::setRunning(r);
00277 
00278     // Clock
00279     if (r) {
00280         m_gameClock->resume();
00281         m_gameDoc->timeChanged(); // It's important to end the current seconde before pausing so that the player cannot cheat with pause.
00282     } else
00283         m_gameClock->pause();
00284 
00285     // Pause
00286     m_pauseAction->setEnabled(r);
00287 }
00288 
00289 
00290 void KBBMainWindow::updateStats()
00291 {
00292     int ballsLeftToPlace = m_gameDoc->numberOfBallsToPlace() - m_gameDoc->numberOfBallsPlaced();
00293 
00294     m_doneAction->setEnabled(m_solveAction->isEnabled() && (ballsLeftToPlace==0));
00295 
00296     if (ballsLeftToPlace<0)
00297         m_doneAction->setToolTip(i18np("First, you need to remove 1 ball from the black box.", "First, you need to remove %1 balls from the black box.", -ballsLeftToPlace));
00298     else if (ballsLeftToPlace==0) {
00299         m_doneAction->setToolTip(i18n("To check if you successful guessed the ball positions, click here!"));
00300     } else if (ballsLeftToPlace>0) {
00301         m_doneAction->setToolTip(i18np("You need to place 1 more ball on the black box.", "You need to place %1 more balls on the black box.", ballsLeftToPlace));
00302     }
00303 
00304     if (!m_boardEnabled)
00305         m_doneAction->setToolTip(i18n("Game over."));
00306     if (m_pauseAction->isChecked())
00307         m_doneAction->setToolTip(i18n("Game paused."));
00308 
00309 
00310     // Status bar
00311     if (m_tutorial->isVisible())
00312         statusBar()->changeItem(i18n("Tutorial"), SRUN );
00313     if (!m_tutorial->isVisible()) {
00314         if (m_boardEnabled) {
00315             if (ballsLeftToPlace<0) {
00316                 statusBar()->changeItem((i18np("1 ball too much!", "%1 balls too much!", -ballsLeftToPlace)), SRUN);
00317             } else if (ballsLeftToPlace==0) {
00318                 statusBar()->changeItem(i18n("No more ball to place"), SRUN);
00319             } else if (ballsLeftToPlace>0) {
00320                 statusBar()->changeItem(i18np("1 ball to place", "%1 balls to place", ballsLeftToPlace), SRUN);
00321             }   
00322         } else
00323             statusBar()->changeItem(i18n("Game over"), SRUN );
00324     }
00325 
00326     statusBar()->changeItem(i18n("Time: %1", m_gameClock->timeString()), STIME);
00327 
00328     statusBar()->changeItem( i18n("Size: %1 x %2", m_gameDoc->columns(), m_gameDoc->rows()), SSIZE );
00329     
00330     
00331     // 2. Info Widget
00332     m_gameWidget->setScore(m_gameDoc->score());
00333 }
00334 
00335 
00336 
00337 //
00338 // Private slots
00339 //
00340 
00341 void KBBMainWindow::done()
00342 {
00343     if (m_tutorial->isVisible() && !m_tutorial->maySolve()) {
00344         KMessageBox::sorry(this, i18n("Clicking on \"Done!\" is the normal way to check the positions of the balls at the end of the game. However, it is not possible in the tutorial to end the game before you reached the last step.\nPlease first finish the tutorial."), i18n("Check positions"));
00345     } else {
00346         solving();
00347 
00348         const int score = m_gameDoc->score();
00349         QString s;
00350         if (score <= (m_ballNumber*35)) {
00351             s = i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Your final score is: %1.<br />You did really well!", score);
00352             if (m_sandboxMode)
00353                 s += QString("<br /><br />") + i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "But it does not count<br />because <b>it is the sandbox!</b>");
00354         } else
00355             s = i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Your final score is: %1.<br />I guess you need more practice.", score);
00356 
00357         if ((!m_tutorial->isVisible()) && (!m_sandboxMode) && (KGameDifficulty::level() != KGameDifficulty::Configurable) && (score<KBBGameDoc::SCORE_LOST)) {
00358             KScoreDialog scoreDialog(KScoreDialog::Score | KScoreDialog::Name, this);
00359             scoreDialog.addLocalizedConfigGroupNames(KGameDifficulty::localizedLevelStrings());
00360             scoreDialog.setConfigGroup(KGameDifficulty::localizedLevelString());
00361     
00362             KScoreDialog::FieldInfo scoreInfo;
00363             scoreInfo[KScoreDialog::Score].setNum(score);
00364             if(scoreDialog.addScore(scoreInfo, KScoreDialog::LessIsMore) != 0)
00365                 scoreDialog.exec();
00366         }
00367 
00368         m_gameWidget->popupText(s);
00369     }
00370 }
00371 
00372 
00373 void KBBMainWindow::newGame()
00374 {
00375     if (mayAbortGame())
00376         startGame(false);
00377 }
00378 
00379 
00380 void KBBMainWindow::pause(bool state)
00381 {
00382     if (state) {
00383         m_gameClock->pause();
00384         m_gameWidget->popupText(i18n("Game paused.<br />Press \"%1\" to resume.", m_pauseAction->shortcut().toString(QKeySequence::NativeText)), 0);
00385     } else {
00386         m_gameClock->resume();
00387         m_gameWidget->popupText("");
00388     }
00389     m_solveAction->setEnabled(!state);
00390 
00391     updateStats();
00392     m_gameWidget->setPause(state);
00393 }
00394 
00395 
00396 void KBBMainWindow::settingsChanged()
00397 {
00398     m_customBallNumber = m_levelConfig->balls();
00399     m_customColumns = m_levelConfig->columns();
00400     m_customRows = m_levelConfig->rows();
00401     
00402     if (m_level==KGameDifficulty::Configurable) {
00403         bool mayRestart = true;
00404         if (m_gameDoc->gameReallyStarted())
00405             if (KMessageBox::questionYesNo(this, i18n("Do you want to cancel the current custom game and start a new one with the new parameters?"), QString(), KGuiItem(i18n("Start new game"))) == KMessageBox::No)
00406                 mayRestart = false;
00407 
00408         if (mayRestart)
00409             startGame(m_sandboxMode);
00410     }
00411 }
00412 
00413 
00414 void KBBMainWindow::settingsDialog()
00415 {
00416     if (!KConfigDialog::showDialog("settings")) {
00417         KConfigDialog *dialog = new KConfigDialog(this, "settings", KBBPrefs::self());
00418         m_levelConfig = new KBBLevelConfigurationWidget(dialog, m_customBallNumber, m_customColumns, m_customRows, m_themeManager);
00419         dialog->addPage(m_levelConfig, i18n("Custom Game"), "games-config-custom");
00420         connect(dialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(settingsChanged()));
00421                 dialog->setHelp(QString(), "kblackbox");
00422         dialog->show();
00423     }
00424 }
00425 
00426 
00427 void KBBMainWindow::showHighscores()
00428 {
00429     KScoreDialog scoreDialog(KScoreDialog::Score | KScoreDialog::Name, this);
00430     scoreDialog.addLocalizedConfigGroupNames(KGameDifficulty::localizedLevelStrings());
00431     scoreDialog.setConfigGroup( KGameDifficulty::localizedLevelString() );
00432     scoreDialog.exec();
00433 }
00434 
00435 
00436 void KBBMainWindow::solve()
00437 {
00438     if (m_tutorial->isVisible() && !m_tutorial->maySolve()) {
00439         KMessageBox::sorry(this, i18n("Sorry, you may not give up the tutorial."), i18n("Solve"));
00440     } else {
00441         if (m_gameDoc->numberOfBallsToPlace()==m_gameDoc->numberOfBallsPlaced()) {
00442             if (KMessageBox::warningContinueCancel(this, i18n("You placed all the balls. Great!\nYou should now click on \"Done!\" to end the game and check if you guessed right.\nSo, do you really want to give up this game?"), QString(), KGuiItem(i18n("Give up"))) == KMessageBox::Continue)
00443                 solving();
00444         } else if (KMessageBox::warningContinueCancel(this, i18np("You should place %1 ball!\n", "You should place %1 balls!\n", m_gameDoc->numberOfBallsToPlace()) + i18np("You have placed %1.\n", "You have placed %1.\n", m_gameDoc->numberOfBallsPlaced()) + i18n("Do you really want to give up this game?"), QString(), KGuiItem(i18n("Give up"))) == KMessageBox::Continue)
00445             solving();
00446     }
00447 }
00448 
00449 
00450 void KBBMainWindow::startSandbox()
00451 {
00452     if (mayAbortGame()) {
00453         startGame(true);
00454         m_gameWidget->popupText(i18nc("The text may not be too wide. So please use some HTML-BR-tags to have something more or less as wide as in english. Thanks!", "Note: In the sandbox mode,<br />the solution is already displayed.<br />Have fun!"));
00455     }
00456 }
00457 
00458 
00459 void KBBMainWindow::startTutorial()
00460 {
00461     if (mayAbortGame()) {
00462         m_gameDoc->startTutorial();
00463         m_solveAction->setEnabled(true);
00464         m_pauseAction->setChecked(false);
00465         KGameDifficulty::setEnabled(false);
00466 
00467         // Reset clock but don't start it yet.
00468         m_gameClock->restart();
00469         m_gameClock->pause();
00470 
00471         updateStats();
00472     }
00473 }
00474 
00475 
00476 
00477 //
00478 // Private
00479 //
00480 
00481 bool KBBMainWindow::mayAbortGame()
00482 {
00483     bool mayAbort = true;
00484 
00485     if (m_gameDoc->gameReallyStarted())
00486         mayAbort = ( KMessageBox::warningContinueCancel(0, i18n("This will be the end of the current game!"), QString(), KGuiItem(i18n("Start new game"))) == KMessageBox::Continue );
00487 
00488     return mayAbort;
00489 }
00490 
00491 
00492 void KBBMainWindow::solving()
00493 {
00494     m_boardEnabled = false;
00495     m_solveAction->setEnabled(false);
00496     m_doneAction->setEnabled(false);
00497     m_gameDoc->gameOver();
00498     m_gameWidget->solve(false);
00499     updateStats();
00500 }
00501 
00502 
00503 void KBBMainWindow::startGame(bool sandboxMode)
00504 {
00505     if (m_level==KGameDifficulty::Configurable) {
00506         m_ballNumber = m_customBallNumber;
00507         m_columns = m_customColumns;
00508         m_rows = m_customRows;
00509     }
00510 
00511     m_boardEnabled = true;
00512     m_sandboxMode = sandboxMode;
00513 
00514     m_solveAction->setEnabled(true);
00515     m_pauseAction->setChecked(false);
00516     KGameDifficulty::setEnabled(true);
00517     m_tutorial->hide();
00518     m_gameDoc->newGame(m_ballNumber, m_columns, m_rows);
00519     m_gameWidget->newGame(m_columns, m_rows, m_ballNumber);
00520     if (m_sandboxMode)
00521         m_gameWidget->solve(true);
00522 
00523     // Reset clock but don't start it yet.
00524     m_gameClock->restart();
00525     m_gameClock->pause();
00526 
00527     updateStats();
00528 }
00529 
00530 
00531 
00532 #include "kbbmainwindow.moc"

kblackbox

Skip menu "kblackbox"
  • Main Page
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members

API Reference

Skip menu "API Reference"
  • kblackbox
  • kgoldrunner
  • kmahjongg
  • ksquares
  • libkdegames
  •   highscore
  •   kgame
  •   kggzgames
  •   kggzmod
  •   kggznet
  • libkmahjongg
Generated for API Reference by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal