You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
610 lines
17 KiB
610 lines
17 KiB
/* -------------------------------------------------------------
|
|
KDE Tuberling
|
|
Play ground widget
|
|
mailto:e.bischoff@noos.fr
|
|
------------------------------------------------------------- */
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <tdemessagebox.h>
|
|
#include <tdelocale.h>
|
|
#include <kstandarddirs.h>
|
|
#include <kprinter.h>
|
|
|
|
#include <tqfile.h>
|
|
#include <tqpainter.h>
|
|
#include <tqimage.h>
|
|
#include <tqcursor.h>
|
|
#include <tqdom.h>
|
|
|
|
#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<ToDraw> 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++;
|
|
|
|
}
|
|
}
|