/* Yo Emacs, this -*- C++ -*- Copyright (C) 1999-2001 Jens Hoefkens jens@hoefkens.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. $Id$ */ #include "kbg.h" #include "kbg.moc" #include <tqpainter.h> #include <tqlayout.h> #include <tqgroupbox.h> #include <tqpixmap.h> #include <tqstring.h> #include <tqwhatsthis.h> #include <tqstringlist.h> #include <tqvaluelist.h> #include <tqiconset.h> #include <tqvbox.h> #include <kmenubar.h> #include <ktoolbar.h> #include <kapplication.h> #include <kiconloader.h> #include <kedittoolbar.h> #include <klocale.h> #include <kaction.h> #include <kstdaction.h> #include <kstdgameaction.h> #include <kaboutdata.h> #include <kmessagebox.h> #include <kconfig.h> #include <kcompletion.h> #include <kcompletionbox.h> #include <kpopupmenu.h> #include <kurllabel.h> #include <krun.h> #include <kstatusbar.h> #include <klineedit.h> #include <knuminput.h> #include <kprinter.h> #include "kbgtextview.h" #include "offline/kbgoffline.h" #include "fibs/kbgfibs.h" #include "gnubg/kbggnubg.h" #include "nextgen/kbgng.h" #include "version.h" // == setup ==================================================================== /* * Constructor creates user interface, actions and first engine. */ KBg::KBg() { /* * Initialize menu strings */ engineString[Offline] = i18n("Open Board"); engineString[FIBS ] = i18n("FIBS"); engineString[GNUbg ] = i18n("GNU Backgammon (Experimental)"); engineString[NextGen] = i18n("Next Generation (Experimental)"); helpTopic[FIBSHome][0] = i18n("FIBS Home"); helpTopic[FIBSHome][1] = "http://www.fibs.com/"; helpTopic[RuleHome][0] = i18n("Backgammon Rules"); helpTopic[RuleHome][1] = "http://www.bkgm.com/rules.html"; /* * The main view is shared between the board and a small text window */ panner = new TQSplitter(Qt::Vertical, this, "panner"); board = new KBgBoardSetup(panner, "board"); status = new KBgTextView(panner, "status"); setCentralWidget(panner); /* * Create all actions needed by the application */ newAction = KStdGameAction::gameNew(TQT_TQOBJECT(this), TQT_SLOT(openNew()), actionCollection()); newAction->setEnabled(false); KStdGameAction::print(TQT_TQOBJECT(this), TQT_SLOT(print()), actionCollection()); KStdGameAction::quit(TQT_TQOBJECT(this), TQT_SLOT(close()), actionCollection()); TQStringList list; for (int i = 0; i < MaxEngine; i++) list.append(engineString[i]); engineSet = new KSelectAction(i18n("&Engine"), 0, TQT_TQOBJECT(this), TQT_SLOT(setupEngine()), actionCollection(), "move_engine"); engineSet->setItems(list); // AB: what the heck has this to do with redisplay? perhaps use reload instead? loadAction = KStdAction::redisplay(TQT_TQOBJECT(this), TQT_SLOT(load()), actionCollection(), "move_load"); loadAction->setEnabled(false); undoAction = KStdGameAction::undo(TQT_TQOBJECT(this), TQT_SLOT(undo()), actionCollection()); undoAction->setEnabled(false); redoAction = KStdGameAction::redo(TQT_TQOBJECT(this), TQT_SLOT(redo()), actionCollection()); redoAction->setEnabled(false); rollAction = KStdGameAction::roll(TQT_TQOBJECT(this), TQT_SLOT(roll()), actionCollection()); rollAction->setEnabled(false); endAction = KStdGameAction::endTurn(TQT_TQOBJECT(this), TQT_SLOT(done()), actionCollection()); endAction->setEnabled(false); cubeAction = new KAction(i18n("Double Cube"), TQIconSet(kapp->iconLoader()->loadIcon (PROG_NAME "-double.xpm", KIcon::Toolbar)), 0, TQT_TQOBJECT(this), TQT_SLOT(cube()), actionCollection(), "move_cube"); cubeAction->setEnabled(false); KStdAction::showMenubar(TQT_TQOBJECT(this), TQT_SLOT(toggleMenubar()), actionCollection()); KStdAction::preferences(TQT_TQOBJECT(this), TQT_SLOT(setupDlg()), actionCollection()); KStdAction::saveOptions(TQT_TQOBJECT(this), TQT_SLOT(saveConfig()), actionCollection()); KPopupMenu *p = (new KActionMenu(i18n("&Backgammon on the Web"), actionCollection(), "help_www"))->popupMenu(); (new KAction(helpTopic[FIBSHome][0], 0, TQT_TQOBJECT(this), TQT_SLOT(wwwFIBS()), actionCollection(), "help_www_fibs"))->plug(p); (new KAction(helpTopic[RuleHome][0], 0, TQT_TQOBJECT(this), TQT_SLOT(wwwRule()), actionCollection(), "help_www_rule"))->plug(p); /* * Set up the command line - using actions, otherwise recreating the GUI will delete them * (e.g. using KEditToolbar) */ cmdLabel = new TQLabel(i18n("Command: "), this); new KWidgetAction( cmdLabel, cmdLabel->text(), 0, 0, 0, actionCollection(), "command_label"); cmdLine = new KLineEdit(this, "commandline"); KWidgetAction* actionCmdLine = new KWidgetAction( cmdLine, TQString(), 0, 0, 0, actionCollection(), "command_lineedit"); actionCmdLine->setAutoSized(true); cmdLine->completionObject()->setOrder(KCompletion::Weighted); connect(cmdLine, TQT_SIGNAL(returnPressed(const TQString &)), TQT_TQOBJECT(this), TQT_SLOT(handleCmd(const TQString &))); /* * Done with the actions, create the XML-defined parts of the * user interface */ statusBar(); setupGUI(); cmdLine->setFocus(); /* * Initialize the engine to the default (offline). If the user * prefers a different engine, it will be started later */ for (int i = 0; i < MaxEngine; i++) engine[i] = 0; currEngine = None; engineSet->setCurrentItem(Offline); setupEngine(); /* * Set up configuration handling. * FIXME: support session management */ connect(this, TQT_SIGNAL(readSettings()), board, TQT_SLOT(readConfig())); connect(this, TQT_SIGNAL(saveSettings()), board, TQT_SLOT(saveConfig())); /* * Set up some whatis messages for the online help */ TQWhatsThis::add(status, i18n("This area contains the status messages for the game. " "Most of these messages are sent to you from the current " "engine.")); TQWhatsThis::add(toolBar("cmdToolBar"), i18n("This is the command line. You can type special " "commands related to the current engine in here. " "Most relevant commands are also available " "through the menus.")); TQWhatsThis::add(toolBar("mainToolBar"), i18n("This is the button bar tool bar. It gives " "you easy access to game related commands. " "You can drag the bar to a different location " "within the window.")); TQWhatsThis::add(statusBar(), i18n("This is the status bar. It shows you the currently " "selected engine in the left corner.")); /* * Create and initialize the context menu */ TQPopupMenu* menu = (TQPopupMenu*)factory()->container("popup", this); board->setContextMenu(menu); } /* * Destructor is empty */ KBg::~KBg() {} // == engine handling ========================================================== /* * Set the engine according to the currently selected item in the * engineSet action. Additional engines have to be added to the switch * statement (and only there). */ void KBg::setupEngine() { /* * Get new engine type */ int type = engineSet->currentItem(); /* * Engine doesn't need to be changed? */ if (engine[type]) return; /* * Check with the engine if it can be terminated */ if (currEngine != None && engine[currEngine] && !engine[currEngine]->queryClose()) { engineSet->setCurrentItem(currEngine); return; } /* * Remove the old engine, create a new one, and hook up menu and slots/signals */ TQPopupMenu *commandMenu = (TQPopupMenu *)factory()->container("command_menu", this); TQString s = PROG_NAME; commandMenu->clear(); if (currEngine != None) { delete engine[currEngine]; engine[currEngine] = 0; } switch (currEngine = type) { case Offline: engine[currEngine] = new KBgEngineOffline(this, &s, commandMenu); break; case FIBS: engine[currEngine] = new KBgEngineFIBS(this, &s, commandMenu); break; case GNUbg: engine[currEngine] = new KBgEngineGNU(this, &s, commandMenu); break; case NextGen: engine[currEngine] = new KBgEngineNg(this, &s, commandMenu); break; default: // FIXME: we need some kind of catch here... currEngine = Offline; engine[currEngine] = new KBgEngineOffline(this, &s, commandMenu); break; } statusBar()->message(engineString[currEngine]); KConfig* config = kapp->config(); config->setGroup("global settings"); if (config->readBoolEntry("enable timeout", true)) engine[currEngine]->setCommit(config->readDoubleNumEntry("timeout", 2.5)); newAction->setEnabled(engine[currEngine]->haveNewGame()); // engine -> this connect(engine[currEngine], TQT_SIGNAL(statText(const TQString &)), TQT_TQOBJECT(this), TQT_SLOT(updateCaption(const TQString &))); connect(engine[currEngine], TQT_SIGNAL(infoText(const TQString &)), status, TQT_SLOT(write(const TQString &))); connect(engine[currEngine], TQT_SIGNAL(allowCommand(int, bool)), this, TQT_SLOT(allowCommand(int, bool))); // this -> engine connect(this, TQT_SIGNAL(readSettings()), engine[currEngine], TQT_SLOT(readConfig())); connect(this, TQT_SIGNAL(saveSettings()), engine[currEngine], TQT_SLOT(saveConfig())); // board -> engine connect(board, TQT_SIGNAL(rollDice(const int)), engine[currEngine], TQT_SLOT(rollDice(const int))); connect(board, TQT_SIGNAL(doubleCube(const int)), engine[currEngine], TQT_SLOT(doubleCube(const int))); connect(board, TQT_SIGNAL(currentMove(TQString *)), engine[currEngine], TQT_SLOT(handleMove(TQString *))); // engine -> board connect(engine[currEngine], TQT_SIGNAL(undoMove()), board, TQT_SLOT(undoMove())); connect(engine[currEngine], TQT_SIGNAL(redoMove()), board, TQT_SLOT(redoMove())); connect(engine[currEngine], TQT_SIGNAL(setEditMode(const bool)), board, TQT_SLOT(setEditMode(const bool))); connect(engine[currEngine], TQT_SIGNAL(allowMoving(const bool)), board, TQT_SLOT(allowMoving(const bool))); connect(engine[currEngine], TQT_SIGNAL(getState(KBgtqStatus *)), board, TQT_SLOT(getState(KBgtqStatus *))); connect(engine[currEngine], TQT_SIGNAL(newState(const KBgtqStatus &)), board, TQT_SLOT(setState(const KBgtqStatus &))); // now that all signals are connected, start the engine engine[currEngine]->start(); } // == configuration handing ==================================================== /* * Save all settings that should be saved for the next start. */ void KBg::saveConfig() { KConfig* config = kapp->config(); config->setGroup("global settings"); /* * Save the main window options unless the user has asked not * to do so. */ if (config->readBoolEntry("autosave on exit", true)) { config->setGroup("main window"); config->writeEntry("origin", pos()); config->writeEntry("font", status->font()); config->writeEntry("panner", (double)board->height()/(double)panner->height()); saveMainWindowSettings(config, "main window"); } /* * Save the history */ config->setGroup("command line"); config->writeEntry("history", cmdLine->completionObject()->items()); /* * Save current engine */ config->setGroup("engine settings"); config->writeEntry("last engine", currEngine); /* * Tell other objects to save their settings, too. */ emit saveSettings(); config->sync(); } /* * Read the stored configuration and apply it */ void KBg::readConfig() { KConfig* config = kapp->config(); config->setGroup("global settings"); /* * Restore the main window settings unless the user has asked * not to do so. */ if (config->readBoolEntry("autosave on exit", true)) { config->setGroup("main window"); TQPoint pos, defpos(10, 10); TQFont kappFont = kapp->font(); pos = config->readPointEntry("origin", &defpos); status->setFont(config->readFontEntry("font", &kappFont)); TQValueList<int> l; l.append(tqRound( config->readDoubleNumEntry("panner", 0.75) *panner->height())); l.append(tqRound((1-config->readDoubleNumEntry("panner", 0.75))*panner->height())); panner->setSizes(l); applyMainWindowSettings(config, "main window"); } /* * Restore the history */ config->setGroup("command line"); cmdLine->completionObject()->setItems(config->readListEntry("history")); /* * Tell other objects to read their configurations */ emit readSettings(); /* * Restore last engine */ config->setGroup("engine settings"); engineSet->setCurrentItem((Engines)config->readNumEntry("last engine", Offline)); setupEngine(); } // == configuration ============================================================ /* * Connected to the setup dialog applyButtonPressed signal. Make sure * that all changes are saved. */ void KBg::setupOk() { // global settings KConfig* config = kapp->config(); config->setGroup("global settings"); config->writeEntry("enable timeout", cbt->isChecked()); config->writeEntry("timeout", sbt->value()); config->writeEntry("autosave on exit", cbs->isChecked()); // tell engine about commit timer engine[currEngine]->setCommit(cbt->isChecked() ? sbt->value() : -1); // one time requests if (cbm->isChecked()) KMessageBox::enableAllMessages(); // tell tqchildren to read their changes board->setupOk(); // engines for (int i = 0; i < MaxEngine; i++) engine[i]->setupOk(); // save it all saveConfig(); } /* * Load default values for the user settings */ void KBg::setupDefault() { // timeout cbt->setChecked(true); sbt->setValue(2.5); // messages cbm->setChecked(false); // auto save cbs->setChecked(true); // board board->setupDefault(); // engines for (int i = 0; i < MaxEngine; i++) engine[i]->setupDefault(); } /* * Connected to the setup dialog cancelButtonPressed signal. There * isn't much to do. We tell the board to undo the changes. */ void KBg::setupCancel() { // board board->setupCancel(); // engines for (int i = 0; i < MaxEngine; i++) engine[i]->setupCancel(); } /* * Setup dialog is ready to be deleted. Do it later... */ void KBg::setupDone() { nb->delayedDestruct(); for (int i = 0; i < MaxEngine; i++) if (i != currEngine) engine[i] = 0; } // FIXME make more general... void KBg::startKCM(const TQString &url) { KRun::runCommand(url); } /* * Initialize and display the setup dialog */ void KBg::setupDlg() { /* * Get a new notebook in which all other members can put their * config pages */ nb = new KDialogBase(KDialogBase::IconList, i18n("Configuration"), KDialogBase::Ok|KDialogBase::Cancel|KDialogBase::Default| KDialogBase::Apply|KDialogBase::Help, KDialogBase::Ok, this, "setup", true, true); KConfig* config = kapp->config(); config->setGroup("global settings"); /* * Main Widget */ TQVBox *w = nb->addVBoxPage(i18n("General"), i18n("Here you can configure general settings of %1"). arg(kapp->aboutData()->programName()), kapp->iconLoader()->loadIcon("go", KIcon::Desktop)); /* * Group boxes */ TQGroupBox *gbm = new TQGroupBox(i18n("Messages"), w); TQGroupBox *gbt = new TQGroupBox(i18n("Timer"), w); TQGroupBox *gbs = new TQGroupBox(i18n("Autosave"), w); TQGroupBox *gbe = new TQGroupBox(i18n("Events"), w); /* * Timer box */ TQWhatsThis::add(gbt, i18n("After you finished your moves, they have to be sent to the engine. " "You can either do that manually (in which case you should not enable " "this feature), or you can specify an amount of time that has to pass " "before the move is committed. If you undo a move during the timeout, the " "timeout will be reset and restarted once you finish the move. This is " "very useful if you would like to review the result of your move.")); cbt = new TQCheckBox(i18n("Enable timeout"), gbt); cbt->setChecked(config->readBoolEntry("enable timeout", true)); sbt = new KDoubleNumInput(gbt); sbt->setRange(0.0, 60.0, 0.5); sbt->setLabel(i18n("Move timeout in seconds:")); sbt->setValue(config->readDoubleNumEntry("timeout", 2.5)); connect(cbt, TQT_SIGNAL(toggled(bool)), sbt, TQT_SLOT(setEnabled(bool))); sbt->setEnabled(cbt->isChecked()); TQGridLayout *gl = new TQGridLayout(gbt, 2, 1, 20); gl->addWidget(cbt, 0, 0); gl->addWidget(sbt, 1, 0); /* * Enable messages */ TQWhatsThis::add(gbm, i18n("Check the box to enable all the messages that you have previously " "disabled by choosing the \"Don't show this message again\" option.")); TQGridLayout *glm = new TQGridLayout(gbm, 1, 1, nb->spacingHint()); cbm = new TQCheckBox(i18n("Reenable all messages"), gbm); glm->addWidget(cbm, 0, 0); /* * Save options on exit ? */ TQWhatsThis::add(gbm, i18n("Check the box to automatically save all window positions on program " "exit. They will be restored at next start.")); TQGridLayout *gls = new TQGridLayout(gbs, 1, 1, nb->spacingHint()); cbs = new TQCheckBox(i18n("Save settings on exit"), gbs); cbs->setChecked(config->readBoolEntry("autosave on exit", true)); gls->addWidget(cbs, 0, 0); /* * Event vonfiguration */ TQWhatsThis::add(gbe, i18n("Event notification of %1 is configured as part of the " "system-wide notification process. Click here, and you " "will be able to configure system sounds, etc."). arg(kapp->aboutData()->programName())); TQGridLayout *gle = new TQGridLayout(gbe, 1, 1, nb->spacingHint()); KURLLabel *lab = new KURLLabel("kcmshell kcmnotify", i18n("Klick here to configure the event notification"), gbe); lab->setMaximumSize(lab->tqsizeHint()); gle->addWidget(lab, 0, 0); connect(lab, TQT_SIGNAL(leftClickedURL(const TQString &)), TQT_SLOT(startKCM(const TQString &))); /* * Board settings */ board->getSetupPages(nb); /* * Hack alert: this little trick makes sure that ALL engines * have their settings available in the dialog. */ TQPopupMenu *dummyPopup = new TQPopupMenu(nb); TQString s = PROG_NAME; for (int i = 0; i < MaxEngine; i++) { if (currEngine != i) { switch (i) { case Offline: engine[i] = new KBgEngineOffline(nb, &s, dummyPopup); break; case FIBS: engine[i] = new KBgEngineFIBS(nb, &s, dummyPopup); break; case GNUbg: engine[i] = new KBgEngineGNU(nb, &s, dummyPopup); break; case NextGen: engine[i] = new KBgEngineNg(nb, &s, dummyPopup); break; } connect(this, TQT_SIGNAL(saveSettings()), engine[i], TQT_SLOT(saveConfig())); } engine[i]->getSetupPages(nb); } /* * Connect the signals of nb */ connect(nb, TQT_SIGNAL(okClicked()), this, TQT_SLOT(setupOk())); connect(nb, TQT_SIGNAL(applyClicked()), this, TQT_SLOT(setupOk())); connect(nb, TQT_SIGNAL(cancelClicked()), TQT_TQOBJECT(this), TQT_SLOT(setupCancel())); connect(nb, TQT_SIGNAL(defaultClicked()),this, TQT_SLOT(setupDefault())); connect(nb, TQT_SIGNAL(finished()), TQT_TQOBJECT(this), TQT_SLOT(setupDone())); nb->resize(nb->tqminimumSize()); nb->show(); } // == action slots ============================================================= /* * Tell the board to print itself - restore and save user settings for * the print dialog. */ void KBg::print() { KPrinter *prt = new KPrinter(); KConfig* config = kapp->config(); config->setGroup("printing"); prt->setNumCopies(config->readNumEntry("numcopies", 1)); prt->setOutputFileName(config->readPathEntry("outputfile")); prt->setOutputToFile(config->readBoolEntry("tofile", false)); prt->setPageSize((KPrinter::PageSize) config->readNumEntry("pagesize", KPrinter::A4)); prt->setOrientation((KPrinter::Orientation)config->readNumEntry("orientation", KPrinter::Landscape)); if (prt->setup(this, i18n("Print %1").tqarg(baseCaption))) { TQPainter p; p.begin(prt); board->print(&p); p.end(); config->writeEntry("tofile", prt->outputToFile()); config->writePathEntry("outputfile", prt->outputFileName()); config->writeEntry("pagesize", (int)prt->pageSize()); config->writeEntry("orientation", (int)prt->orientation()); config->writeEntry("numcopies", prt->numCopies()); } delete prt; } /* * Toggle visibility of the menubar - be careful that the menu doesn't * get lost */ void KBg::toggleMenubar() { if (menuBar()->isVisible()) { KMessageBox::information(this, i18n("You can enable the menubar again with the " "right mouse button menu of the board."), i18n("Information"), "conf_menubar_information"); menuBar()->hide(); } else { menuBar()->show(); } } /* * Display a standard dialog for the toolbar content */ void KBg::configureToolbars() { saveMainWindowSettings(KGlobal::config(), "kedittoolbar settings"); // temp group KEditToolbar dlg(actionCollection(), xmlFile(), true, this); connect(&dlg,TQT_SIGNAL(newToolbarConfig()),this,TQT_SLOT(newToolbarConfig())); dlg.exec(); KGlobal::config()->deleteGroup( "kedittoolbar settings" ); // delete temp group } /* * Called when clicking OK or Apply in the toolbar editor */ void KBg::newToolbarConfig() { createGUI(); applyMainWindowSettings(KGlobal::config(), "kedittoolbar settings"); } /* * Help slots */ void KBg::wwwFIBS() {showWWW(FIBSHome);} void KBg::wwwRule() {showWWW(RuleHome);} void KBg::showWWW(int t) { kapp->invokeBrowser(helpTopic[t][1]); } /* * Edit slots */ void KBg::undo() {engine[currEngine]->undo();} void KBg::redo() {engine[currEngine]->redo();} void KBg::roll() {engine[currEngine]->roll();} void KBg::cube() {engine[currEngine]->cube();} void KBg::done() {engine[currEngine]->done();} void KBg::load() {engine[currEngine]->load();} /* * Start a new game with the current engine */ void KBg::openNew() { engine[currEngine]->newGame(); } // == various slots - not for actions ========================================== /* * Check with the engine if it is okay to close the window. * If so, save settings. */ bool KBg::queryClose() { bool ret = engine[currEngine]->queryClose(); if ( ret ) saveConfig(); return ret; } /* * Set the caption of the main window. If the user has requested pip * counts, they are appended, too. */ void KBg::updateCaption(const TQString &s) { baseCaption = s; TQString msg; if (!s.isEmpty()) { msg = s; if (board->getPipCount(US) >= 0) { TQString tmp; tmp.setNum(board->getPipCount(US )); msg += " - " + tmp; tmp.setNum(board->getPipCount(THEM)); msg += "-" + tmp; } } setCaption(msg, false); } /* * Take the string from the commandline, give it to the engine, append * to the history and clear the buffer. */ void KBg::handleCmd(const TQString &s) { if (!s.stripWhiteSpace().isEmpty()) { engine[currEngine]->handleCommand(s); cmdLine->completionObject()->addItem(s); } cmdLine->clear(); cmdLine->completionBox()->close(); } /* * Reflect the availability of commands in the button bar. */ void KBg::allowCommand(int cmd, bool f) { switch (cmd) { case KBgEngine::Undo: undoAction->setEnabled(f); break; case KBgEngine::Redo: redoAction->setEnabled(f); break; case KBgEngine::Roll: rollAction->setEnabled(f); break; case KBgEngine::Cube: cubeAction->setEnabled(f); break; case KBgEngine::Done: endAction->setEnabled(f); break; case KBgEngine::Load: loadAction->setEnabled(f); break; } } /* * Catch the hide envents. That way, the current engine can close its * child windows. */ void KBg::hideEvent(TQHideEvent *e) { KMainWindow::hideEvent(e); engine[currEngine]->hideEvent(); } /* * Catch the show envents. That way, the current engine can open any * previously hidden windows. */ void KBg::showEvent(TQShowEvent *e) { KMainWindow::showEvent(e); engine[currEngine]->showEvent(); } // EOF