/* ------------------------------------------------------------- KDE Tuberling Play ground widget mailto:e.bischoff@noos.fr ------------------------------------------------------------- */ #include #include #include #include #include #include #include #include #include #include #include "playground.moc" #include "toplevel.h" #define XMARGIN 5 #define YMARGIN 5 // Constructor PlayGround::PlayGround(TopLevel *parent, const char *name, uint selectedGameboard) : TQWidget(parent, name) { topLevel = parent; textsLayout = objectsLayout = 0; textsList = soundsList = 0; draggedCursor = 0; toDraw.setAutoDelete(true); history.setAutoDelete(true); setBackgroundColor(white); TQDomDocument layoutsDocument; bool ok = topLevel->loadLayout(layoutsDocument); if (ok) ok = registerPlayGrounds(layoutsDocument); if (ok) ok = loadPlayGround(layoutsDocument, selectedGameboard); if (!ok) loadFailure(); currentAction = 0; setupGeometry(); } // Destructor PlayGround::~PlayGround() { delete [] textsLayout; delete [] objectsLayout; delete [] textsList; delete [] soundsList; delete draggedCursor; } // Reset the play ground void PlayGround::reset() { toDraw.clear(); history.clear(); currentAction = 0; } // Change the gameboard void PlayGround::change(uint selectedGameboard) { TQDomDocument layoutsDocument; bool ok = topLevel->loadLayout(layoutsDocument); if (ok) ok = loadPlayGround(layoutsDocument, selectedGameboard); if (!ok) loadFailure(); toDraw.clear(); history.clear(); currentAction = 0; setupGeometry(); update(); } // Repaint all the editable area void PlayGround::repaintAll() { TQRect dirtyArea (editableArea.left() - 10, editableArea.top() - 10, editableArea.width() + 20, editableArea.height() + 20); repaint(dirtyArea, false); } // Undo last action // Returns true if everything went fine bool PlayGround::undo() { ToDraw *newObject; Action *undone; int zOrder; if (!(undone = history.at(--currentAction))) return false; zOrder = undone->ZOrderAfter(); if (zOrder != -1) { // Undo an "add" or a "move" action if (!toDraw.remove(zOrder)) return false; } zOrder = undone->ZOrderBefore(); if (zOrder != -1) { // Undo a "delete" or a "move" action newObject = new ToDraw(undone->DrawnBefore()); if (!toDraw.insert(zOrder, newObject)) return false; } return true; } // Redo next action // Returns true if everything went fine bool PlayGround::redo() { ToDraw *newObject; Action *undone; int zOrder; if (!(undone = history.at(currentAction++))) return false; zOrder = undone->ZOrderBefore(); if (zOrder != -1) { // Redo a "delete" or a "move" action if (!toDraw.remove(zOrder)) return false; } zOrder = undone->ZOrderAfter(); if (zOrder != -1) { // Redo an "add" or a "move" action newObject = new ToDraw(undone->DrawnAfter()); if (!toDraw.insert(zOrder, newObject)) return false; } return true; } // Save objects laid down on the editable area bool PlayGround::saveAs(const TQString & name) { FILE *fp; const ToDraw *currentObject; if (!(fp = fopen((const char *) TQFile::encodeName(name), "w"))) return false; fprintf(fp, "%d\n", topLevel->getSelectedGameboard()); for (currentObject = toDraw.first(); currentObject; currentObject = toDraw.next()) currentObject->save(fp); return !fclose(fp); } // Print gameboard's picture bool PlayGround::printPicture(KPrinter &printer) const { TQPainter artist; TQPixmap picture(getPicture()); if (!artist.begin(&printer)) return false; artist.drawPixmap(TQPoint(32, 32), picture); if (!artist.end()) return false; return true; } // Get a pixmap containing the current picture TQPixmap PlayGround::getPicture() const { TQPixmap result(editableArea.size()); TQPainter artist(&result); TQRect transEditableArea(editableArea); transEditableArea.moveBy(-XMARGIN, -YMARGIN); artist.translate(XMARGIN - editableArea.left(), YMARGIN - editableArea.top()); drawGameboard(artist, transEditableArea); return result; } // Draw some text void PlayGround::drawText(TQPainter &artist, TQRect &area, TQString &textId) const { TQString label; label=i18n(textId.latin1()); artist.drawText(area, AlignCenter, label); } // Paint the current picture to the given device void PlayGround::drawGameboard( TQPainter &artist, const TQRect &area ) const { artist.drawPixmap(area.topLeft(), gameboard, area); artist.setPen(white); for (int text = 0; text < texts; text++) drawText(artist, textsLayout[text], textsList[text]); artist.setPen(black); for (TQPtrListIterator currentObject(toDraw); currentObject.current(); ++currentObject) currentObject.current()->draw(artist, area, objectsLayout, &gameboard, &masks); } // Painting event void PlayGround::paintEvent( TQPaintEvent *event ) { TQPoint destination(event->rect().topLeft()), position(destination.x() - XMARGIN, destination.y() - YMARGIN); TQRect area(position, TQSize(event->rect().size())); TQPixmap cache(gameboard.size()); TQPainter artist(&cache); if (destination.x() < XMARGIN) destination.setX(XMARGIN); if (destination.y() < YMARGIN) destination.setY(YMARGIN); area = TQRect(0, 0, gameboard.width(), gameboard.height()).intersect(area); if (area.isEmpty()) return; drawGameboard(artist, area); bitBlt(this, destination, &cache, area, TQt::CopyROP); } // Mouse pressed event void PlayGround::mousePressEvent( TQMouseEvent *event ) { if (draggedCursor) return; TQPoint position(event->x() - XMARGIN, event->y() - YMARGIN); if (!zone(position)) return; int draggedNumber = draggedObject.getNumber(); TQPixmap object(objectsLayout[draggedNumber].size()); TQBitmap shape(objectsLayout[draggedNumber].size()); bitBlt(&object, TQPoint(0, 0), &gameboard, objectsLayout[draggedNumber], TQt::CopyROP); bitBlt(&shape, TQPoint(0, 0), &masks, objectsLayout[draggedNumber], TQt::CopyROP); object.setMask(shape); draggedCursor = new TQCursor(object, position.x(), position.y()); setCursor(*draggedCursor); topLevel->playSound(soundsList[draggedNumber]); } // Mouse released event void PlayGround::mouseReleaseEvent( TQMouseEvent *event ) { // If we are not dragging an object, ignore the event if (!draggedCursor) return; TQCursor arrow; int draggedNumber = draggedObject.getNumber(); TQRect position( event->x() - XMARGIN - draggedCursor->hotSpot().x(), event->y() - YMARGIN - draggedCursor->hotSpot().y(), objectsLayout[draggedNumber].width(), objectsLayout[draggedNumber].height()); TQRect dirtyArea (editableArea.left() - 10, editableArea.top() - 10, editableArea.width() + 20, editableArea.height() + 20); ToDraw *newObject; Action *newAction; // We are not anymore dragging an object delete draggedCursor; draggedCursor = 0; setCursor(arrow); // If we are not moving the object to the editable area if (!dirtyArea.contains(event->pos())) { // ... then register its deletion (if coming from the editable area), and return if (draggedZOrder == -1) return; while (history.count() > currentAction) history.removeLast(); newAction = new Action(&draggedObject, draggedZOrder, 0, -1); history.append(newAction); currentAction++; topLevel->enableUndo(true); return; } // Register that we have one more object to draw newObject = new ToDraw(draggedNumber, position); toDraw.append(newObject); // Forget all subsequent actions in the undo buffer, and register object's addition (or its move) while (history.count() > currentAction) history.removeLast(); newAction = new Action(&draggedObject, draggedZOrder, newObject, toDraw.count()-1); history.append(newAction); currentAction++; topLevel->enableUndo(true); // Repaint the editable area position.moveBy(XMARGIN, YMARGIN); repaint(position, false); } // Register the various playgrounds bool PlayGround::registerPlayGrounds(TQDomDocument &layoutDocument) { TQDomNodeList playGroundsList, menuItemsList, labelsList; TQDomElement playGroundElement, menuItemElement, labelElement; TQDomAttr actionAttribute; playGroundsList = layoutDocument.elementsByTagName("playground"); if (playGroundsList.count() < 1) return false; for (uint i = 0; i < playGroundsList.count(); i++) { playGroundElement = (const TQDomElement &) playGroundsList.item(i).toElement(); menuItemsList = playGroundElement.elementsByTagName("menuitem"); if (menuItemsList.count() != 1) return false; menuItemElement = (const TQDomElement &) menuItemsList.item(0).toElement(); labelsList = menuItemElement.elementsByTagName("label"); if (labelsList.count() != 1) return false; labelElement = (const TQDomElement &) labelsList.item(0).toElement(); actionAttribute = menuItemElement.attributeNode("action"); topLevel->registerGameboard(labelElement.text(), actionAttribute.value().latin1()); } return true; } // Load background and draggable objects masks bool PlayGround::loadPlayGround(TQDomDocument &layoutDocument, uint toLoad) { TQDomNodeList playGroundsList, editableAreasList, categoriesList, objectsList, gameAreasList, maskAreasList, soundNamesList, labelsList; TQDomElement playGroundElement, editableAreaElement, categoryElement, objectElement, gameAreaElement, maskAreaElement, soundNameElement, labelElement; TQDomAttr gameboardAttribute, masksAttribute, leftAttribute, topAttribute, rightAttribute, bottomAttribute, refAttribute; playGroundsList = layoutDocument.elementsByTagName("playground"); if (toLoad >= playGroundsList.count()) return false; playGroundElement = (const TQDomElement &) playGroundsList.item(toLoad).toElement(); gameboardAttribute = playGroundElement.attributeNode("gameboard"); if (!gameboard.load(locate("data", "ktuberling/pics/" + gameboardAttribute.value()))) return false; masksAttribute = playGroundElement.attributeNode("masks"); if (!masks.load(locate("data", "ktuberling/pics/" + masksAttribute.value()))) return false; editableAreasList = playGroundElement.elementsByTagName("editablearea"); if (editableAreasList.count() != 1) return false; editableAreaElement = (const TQDomElement &) editableAreasList.item(0).toElement(); gameAreasList = editableAreaElement.elementsByTagName("position"); if (gameAreasList.count() != 1) return false; gameAreaElement = (const TQDomElement &) gameAreasList.item(0).toElement(); leftAttribute = gameAreaElement.attributeNode("left"); topAttribute = gameAreaElement.attributeNode("top"); rightAttribute = gameAreaElement.attributeNode("right"); bottomAttribute = gameAreaElement.attributeNode("bottom"); editableArea.setCoords (XMARGIN + leftAttribute.value().toInt(), YMARGIN + topAttribute.value().toInt(), XMARGIN + rightAttribute.value().toInt(), YMARGIN + bottomAttribute.value().toInt()); soundNamesList = editableAreaElement.elementsByTagName("sound"); if (soundNamesList.count() != 1) return false; soundNameElement = (const TQDomElement &) soundNamesList.item(0).toElement(); refAttribute = soundNameElement.attributeNode("ref"); editableSound = refAttribute.value(); categoriesList = playGroundElement.elementsByTagName("category"); texts = categoriesList.count(); if (texts < 1) return false; delete[] textsLayout; textsLayout = new TQRect[texts]; delete[] textsList; textsList = new TQString[texts]; for (int text = 0; text < texts; text++) { categoryElement = (const TQDomElement &) categoriesList.item(text).toElement(); gameAreasList = categoryElement.elementsByTagName("position"); if (gameAreasList.count() != 1) return false; gameAreaElement = (const TQDomElement &) gameAreasList.item(0).toElement(); leftAttribute = gameAreaElement.attributeNode("left"); topAttribute = gameAreaElement.attributeNode("top"); rightAttribute = gameAreaElement.attributeNode("right"); bottomAttribute = gameAreaElement.attributeNode("bottom"); textsLayout[text].setCoords (leftAttribute.value().toInt(), topAttribute.value().toInt(), rightAttribute.value().toInt(), bottomAttribute.value().toInt()); labelsList = categoryElement.elementsByTagName("label"); if (labelsList.count() != 1) return false; labelElement = (const TQDomElement &) labelsList.item(0).toElement(); textsList[text] = labelElement.text(); } objectsList = playGroundElement.elementsByTagName("object"); decorations = objectsList.count(); if (decorations < 1) return false; delete[] objectsLayout; objectsLayout = new TQRect[decorations]; delete[] soundsList; soundsList = new TQString[decorations]; for (int decoration = 0; decoration < decorations; decoration++) { objectElement = (const TQDomElement &) objectsList.item(decoration).toElement(); gameAreasList = objectElement.elementsByTagName("position"); if (gameAreasList.count() != 1) return false; gameAreaElement = (const TQDomElement &) gameAreasList.item(0).toElement(); leftAttribute = gameAreaElement.attributeNode("left"); topAttribute = gameAreaElement.attributeNode("top"); rightAttribute = gameAreaElement.attributeNode("right"); bottomAttribute = gameAreaElement.attributeNode("bottom"); objectsLayout[decoration].setCoords (leftAttribute.value().toInt(), topAttribute.value().toInt(), rightAttribute.value().toInt(), bottomAttribute.value().toInt()); soundNamesList = objectElement.elementsByTagName("sound"); if (soundNamesList.count() != 1) return false; soundNameElement = (const TQDomElement &) soundNamesList.item(0).toElement(); refAttribute = soundNameElement.attributeNode("ref"); soundsList[decoration] = refAttribute.value(); } return true; } // Report a load failure void PlayGround::loadFailure() { KMessageBox::error(topLevel, i18n("Fatal error:\n" "Unable to load the pictures, aborting.")); exit(-1); } // Set up play ground's geometry void PlayGround::setupGeometry() { int width = gameboard.width() + 2 * XMARGIN, height = gameboard.height() + 2 * YMARGIN; setFixedWidth(width); setFixedHeight(height); } // In which decorative object are we? // On return, the position is the location of the cursor's hot spot // Returns false if we aren't in any zone bool PlayGround::zone(TQPoint &position) { // Scan all available decorative objects on right side because we may be adding one int draggedNumber; for (draggedNumber = 0; draggedNumber < decorations; draggedNumber++) if (objectsLayout[draggedNumber].contains(position)) { position.setX(position.x() - objectsLayout[draggedNumber].x()); position.setY(position.y() - objectsLayout[draggedNumber].y()); draggedObject.setNumber(draggedNumber); draggedZOrder = -1; return true; } // Scan all decorative objects already layed down on editable are because we may be moving or removing one const ToDraw *currentObject; for (draggedZOrder = toDraw.count()-1; draggedZOrder >= 0; draggedZOrder--) { currentObject = toDraw.at(draggedZOrder); if (!currentObject->getPosition().contains(position)) continue; TQRect toUpdate(currentObject->getPosition()); draggedObject = *currentObject; draggedNumber = draggedObject.getNumber(); TQBitmap shape(objectsLayout[draggedNumber].size()); TQPoint relative(position.x() - toUpdate.x(), position.y() - toUpdate.y()); bitBlt(&shape, TQPoint(0, 0), &masks, objectsLayout[draggedNumber], TQt::CopyROP); if (!shape.convertToImage().pixelIndex(relative.x(), relative.y())) continue; toDraw.remove(draggedZOrder); toUpdate.moveBy(XMARGIN, YMARGIN); repaint(toUpdate, false); position = relative; return true; } // If we are on the gameboard itself, then play "tuberling" sound if (editableArea.contains(position)) topLevel->playSound(editableSound); return false; } // Load objects and lay them down on the editable area bool PlayGround::loadFrom(const TQString &name) { FILE *fp; bool eof = false; ToDraw readObject, *newObject; Action *newAction; if (!(fp = fopen(TQFile::encodeName(name), "r"))) return false; uint newGameboard; int nitems = fscanf(fp, "%u\n", &newGameboard); if (nitems == EOF) { fclose(fp); return false; } topLevel->changeGameboard(newGameboard); for (;;) { if (!readObject.load(fp, decorations, eof)) { fclose(fp); return false; } if (eof) { return !fclose(fp); } newObject = new ToDraw(readObject); toDraw.append(newObject); newAction = new Action(0, -1, newObject, toDraw.count()-1); history.append(newAction); currentAction++; } }