changeset 376:40e33ad0d153

Work towards a working editor .. some day.
author Matti Hamalainen <ccr@tnsp.org>
date Wed, 17 Oct 2012 02:27:55 +0300
parents e024ef14f35b
children a41d0db5fa36
files Makefile.gen eddemoobj.cpp eddemoobj.h edgui.cpp edmain.cpp edmain.h edtimeline.cpp edwaveform.cpp edwaveform.h
diffstat 9 files changed, 1077 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile.gen	Wed Oct 17 01:47:49 2012 +0300
+++ b/Makefile.gen	Wed Oct 17 02:27:55 2012 +0300
@@ -362,7 +362,7 @@
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
 
-$(BINPATH)$(DEMO_BIN)$(EXEEXT): $(OBJPATH)$(DEMO_BIN).o $(addprefix $(OBJPATH),$(DEMO_OBJS)) $(DMLIB_A)
+$(BINPATH)$(DEMO_BIN)$(EXEEXT): $(addprefix $(OBJPATH),$(DEMO_OBJS)) $(DMLIB_A)
 	@echo " LINK $+"
 	@$(CC) -o $@ $(filter %.o %.a,$+) $(DM_LDFLAGS) $(SDL_LDFLAGS)
 
@@ -372,7 +372,7 @@
 $(EDITOR_PRO): $(DMLIB)Makefile.gen config.mak $(addprefix $(DMLIB),$(EDITOR_SOURCES)) $(addprefix $(OBJPATH),$(DEMO_OBJS)) $(DMLIB_A)
 	@echo " CREATE $@"
 	@echo > $@
-	@echo "POST_TARGETDEPS = $(filter %.o %.a,$+)" >> $@
+	@echo "QMAKE_LIBS += $(filter-out %dmsimple.o,$(filter %.o %.a,$+))" >> $@
 	@echo "QMAKE_CXXFLAGS += $(DM_CFLAGS) $(SDL_CFLAGS)" >> $@
 	@echo "QMAKE_LIBS += $(DM_LDFLAGS) $(SDL_LDFLAGS)" >> $@
 	@echo "MAKEFILE = $(EDITOR_MAKEFILE)" >> $@
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eddemoobj.cpp	Wed Oct 17 02:27:55 2012 +0300
@@ -0,0 +1,58 @@
+//
+// Map Mask Designer -- Map object and map region classes
+// (C) Copyright 2012 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+//
+#include "eddemoobj.h"
+#include "dmres.h"
+#include <QFileInfo>
+
+//////////////////////////////////////////////////////////////////////////////
+
+DemoObject::DemoObject()
+{
+    lastError = QFile::NoError;
+    changed = 0;
+}
+
+
+// Create a copy of the given mapobject
+DemoObject::DemoObject(DemoObject *obj)
+{
+    lastError = QFile::NoError;
+    changed = 0;
+
+    filename = obj->filename;
+}
+
+
+DemoObject::~DemoObject()
+{
+}
+
+int DemoObject::load(QString mfilename)
+{
+    QByteArray fnba = mfilename.toUtf8();
+    DMResource *res;
+    if ((res = dmf_create_stdio(fnba.data(), "rb")) == NULL)
+        return DMERR_FOPEN;
+
+    int err = dmLoadTimeline(res, &data.tl);
+
+    dmf_close(res);
+    filename = mfilename;
+    return err;
+}
+
+
+int DemoObject::save(QString mfilename)
+{
+    QByteArray fnba = mfilename.toUtf8();
+    DMResource *res;
+    if ((res = dmf_create_stdio(fnba.data(), "wb")) == NULL)
+        return DMERR_FOPEN;
+
+    int err = dmSaveTimeline(res, data.tl);
+
+    dmf_close(res);
+    return err;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eddemoobj.h	Wed Oct 17 02:27:55 2012 +0300
@@ -0,0 +1,30 @@
+//
+// Demo Editor -- Demo state object
+// (C) Copyright 2012 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+//
+#ifndef DEMOOBJECT_H
+#define DEMOOBJECT_H
+
+#include "dmengine.h"
+#include <QFile>
+
+class DemoObject
+{
+public:
+    // Last file I/O status
+    QFile::FileError lastError;
+    QString state;
+    int changed;
+    DMEngineData data;
+    QString filename;
+
+    DemoObject();
+    DemoObject(DemoObject *);
+    ~DemoObject();
+
+    int load(QString filename);
+    int save(QString filename);
+};
+
+
+#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/edgui.cpp	Wed Oct 17 02:27:55 2012 +0300
@@ -0,0 +1,446 @@
+//
+// Demo Editor -- Qt GUI setup parts and callbacks
+// (C) Copyright 2012 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+//
+#include "edmain.h"
+
+#include <QCloseEvent>
+#include <QMenuBar>
+#include <QStatusBar>
+#include <QMenu>
+#include <QProgressBar>
+#include <QFileDialog>
+#include <QHBoxLayout>
+#include <QVBoxLayout>
+
+
+
+void DemoEditor::updateMenuStates()
+{
+    // Set window title based on document filename and changed status
+    QString name;
+
+    if (!demo || demo->filename.isEmpty())
+        name = DOC_DEF_FILENAME;
+    else
+        name = demo->filename;
+
+    if (changed)
+        name = "*" + name;
+    
+    setWindowTitle(name + " - " + QCoreApplication::applicationName());
+
+    // Enable menu items based on states
+#if 0
+    menuActValidate->setEnabled(demo->get);
+    menuActSave->setEnabled(demo->changed || changed);
+    menuActSaveAs->setEnabled(demo->changed || changed);
+#else
+    menuActSave->setEnabled(false);
+    menuActSaveAs->setEnabled(false);
+#endif
+    
+    // Enable undo/redo items and set their texts based on history status
+    int historyLevels = undoHistory.size();
+    QString itemText;
+    bool itemEnabled;
+    
+    if (undoHistoryPos >= 0 && undoHistoryPos < historyLevels)
+    {
+        itemText = " " + undoHistory.at(undoHistoryPos)->state;
+        itemEnabled = true;
+    }
+    else
+    {
+        itemText = "";
+        itemEnabled = false;
+    }
+    
+    menuActRedo->setEnabled(itemEnabled);
+    menuActRedo->setText("&Redo" + itemText);
+
+    if (undoHistoryPos > 0 && historyLevels > 0)
+    {
+        itemText = " " + undoHistory.at(undoHistoryPos - 1)->state;
+        itemEnabled = true;
+    }
+    else
+    {
+        itemText = "";
+        itemEnabled = false;
+    }
+    
+    menuActUndo->setEnabled(itemEnabled);
+    menuActUndo->setText("&Undo" + itemText);
+
+    update();
+}
+
+
+//
+// Show about dialog
+//
+void DemoEditor::actionAboutBox()
+{
+    QMessageBox::about(this,
+    "About "+ QCoreApplication::applicationName(),
+    "<h1>" + QCoreApplication::applicationName() +
+    " v"+ QCoreApplication::applicationVersion() +"</h1><br>\n"
+    "(C) Copyright 2012 Matti 'ccr' H&auml;m&auml;l&auml;inen<br>\n"
+    "<br>\n"
+    "<b>A demo editor TNSP dmlib engine.</b><br>\n");
+}
+
+
+//
+// Show a dialog inquiring the user whether to save the current
+// document if it has been modified since last save/load.
+//
+QMessageBox::StandardButton DemoEditor::showDocumentModifiedDialog()
+{
+    return QMessageBox::question(this,
+        "The document has been modified.",
+        "Do you want to save your changes?",
+        QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save,
+        QMessageBox::Save);
+}
+
+
+//
+// Generic error/warning dialog
+//
+void DemoEditor::showFileErrorDialog(QString operation, int code, QFile::FileError err)
+{
+/*
+    QString msg;
+    
+    QMessageBox::error(this,
+    "A non-critical error occured",
+    operation
+*/
+}
+
+
+void DemoEditor::actionFileNew()
+{
+    bool okToCreate = true;
+
+    if (changed)
+    {
+        okToCreate = false;
+        switch (showDocumentModifiedDialog())
+        {
+            case QMessageBox::Discard:
+                okToCreate = true;
+                break;
+            
+            case QMessageBox::Save:
+                actionFileSave();
+                if (!changed)
+                {
+                    QMessageBox::information(this, 
+                        "Document saved",
+                        "The document was saved as " + demo->filename);
+
+                    okToCreate = true;
+                }
+                break;
+
+            default:
+                break;
+        }
+    }
+    
+    if (okToCreate)
+        createNewFile();
+}
+
+
+void DemoEditor::actionFileOpen()
+{
+    bool okToOpen = true;
+
+    if (changed)
+    {
+        okToOpen = false;
+
+        switch (showDocumentModifiedDialog())
+        {
+            case QMessageBox::Discard:
+                okToOpen = true;
+                break;
+            
+            case QMessageBox::Save:
+                actionFileSave();
+                if (!changed)
+                {
+                    QMessageBox::information(this, 
+                        "Document saved",
+                        "The document was saved as " + demo->filename);
+                    
+                    okToOpen = true;
+                }
+                break;
+
+            default:
+                break;
+        }
+    }
+        
+    if (okToOpen)
+    {
+        QFileDialog fdialog(this);
+
+        fdialog.setAcceptMode(QFileDialog::AcceptOpen);
+        fdialog.setFileMode(QFileDialog::ExistingFile);
+        fdialog.setNameFilter("Demo timeline files (*.demo)");
+        fdialog.setDefaultSuffix("demo");
+
+        if (fdialog.exec())
+            readFromFile(fdialog.selectedFiles()[0]);
+    }
+}
+
+
+void DemoEditor::actionFileSaveAs()
+{
+    if (!demo)
+    {
+        qDebug() << "DemoEditor::actionFileSaveAs(): demo == null";
+        return;
+    }
+
+    QFileDialog fdialog(this);
+
+    fdialog.setAcceptMode(QFileDialog::AcceptSave);
+    fdialog.setFileMode(QFileDialog::AnyFile);
+    fdialog.setNameFilter("Demo files (*.demo)");
+    fdialog.setDefaultSuffix("demo");
+    fdialog.setConfirmOverwrite(true);
+
+    fdialog.selectFile(demo->filename.isEmpty() ? demo->filename : DOC_DEF_FILENAME);
+    
+    if (fdialog.exec())
+        saveToFile(fdialog.selectedFiles()[0]);
+}
+
+
+void DemoEditor::actionFileSave()
+{
+    if (!demo)
+    {
+        qDebug() << "DemoEditor::actionFileSave(): demo == null";
+        return;
+    }
+
+    // If filename has been set, save .. otherwise go to save as
+    if (!demo->filename.isEmpty())
+        saveToFile(demo->filename);
+    else
+        actionFileSaveAs();
+}
+
+
+void DemoEditor::closeEvent(QCloseEvent *event)
+{
+    bool okToClose = true;
+
+    if (changed)
+    {
+        okToClose = false;
+        switch (showDocumentModifiedDialog())
+        {
+            case QMessageBox::Discard:
+                okToClose = true;
+                break;
+            
+            case QMessageBox::Save:
+                actionFileSave();
+                if (!changed)
+                    okToClose = true;
+                break;
+            
+            default:
+                break;
+        }
+    }
+    
+    if (okToClose)
+        event->accept();
+}
+
+
+//
+// Various menu actions
+//
+
+
+//
+// Update statusbar message text
+//
+void DemoEditor::statusMsg(QString message)
+{
+    statusBar()->showMessage(message, 0);
+}
+
+
+//
+// Set active element of an action group based on matching the data
+//
+void DemoEditor::setActionGroupChecked(QActionGroup *group, QVariant data)
+{
+    QList<QAction *> items = group->actions();
+
+    for (int i = 0; i < items.size(); i++)
+    {
+        QAction *act = items.at(i);
+        if (act->data() == data)
+        {
+            act->setChecked(true);
+            return;
+        }
+    }
+}
+
+
+//
+// Helper functions for creating GUI elements
+//
+QAction * DemoEditor::createToolButton(QActionGroup *group, QString name, QIcon icon, QString statustip, QVariant data)
+{
+    QAction *action = new QAction(icon, name, group);
+    
+    action->setStatusTip(statustip + ".");
+    action->setCheckable(true);
+    action->setData(data);
+    
+    return action;
+}
+
+
+QAction * DemoEditor::createMenuAction(QString name, const QKeySequence &shortcut, QString tooltip)
+{
+    QAction *action = new QAction(name, this);
+    
+    if (shortcut != QKeySequence(QKeySequence::UnknownKey))
+        action->setShortcut(shortcut);
+    
+    if (!tooltip.isNull())
+        action->setStatusTip(tooltip + ".");
+
+    return action;
+}
+
+
+QAction * DemoEditor::createMenuGroupAction(QMenu *menu, QActionGroup *group, QString name, const QKeySequence &shortcut, QString tooltip, QVariant data)
+{
+    QAction *action = createMenuAction(name, shortcut, tooltip);
+    
+    action->setCheckable(true);
+    action->setData(data);
+
+    menu->addAction(action);
+    group->addAction(action);
+
+    return action;
+}
+
+
+#define MCONNECT(menu, act, slot) do { connect(act, SIGNAL(triggered()), this, SLOT(slot)); menu->addAction(act); } while(0)
+
+
+//
+// Create GUI elements
+//
+void DemoEditor::createMainGUI()
+{
+    QAction *act;
+    QMenu *fileMenu, *editMenu, *viewMenu, *helpMenu;
+
+    qDebug() << "- Constructing menus";
+
+    //
+    // File menu
+    //
+    fileMenu = menuBar()->addMenu("&File");
+
+    act = createMenuAction("&New", QKeySequence::New, "Create a new demo timeline");
+    MCONNECT(fileMenu, act, actionFileNew());
+
+    menuActOpen = createMenuAction("&Open", QKeySequence::Open, "Open a demo timeline file");
+    MCONNECT(fileMenu, menuActOpen, actionFileOpen());
+
+    menuActSave = createMenuAction("&Save", QKeySequence::Save, "Save demo timeline");
+    MCONNECT(fileMenu, menuActSave, actionFileSave());
+
+    menuActSaveAs = createMenuAction("Save &as", QKeySequence::SaveAs, "Save demo timeline as a new file");
+    MCONNECT(fileMenu, menuActSaveAs, actionFileSaveAs());
+
+    fileMenu->addSeparator();
+
+    QKeySequence qseq(Qt::CTRL + Qt::Key_Q);
+    act = createMenuAction("&Quit", qseq, "Exit application");
+    MCONNECT(fileMenu, act, close());
+
+
+    //
+    // Edit menu
+    //
+    editMenu = menuBar()->addMenu("&Edit");
+
+    menuActUndo = createMenuAction("&Undo",  QKeySequence::Undo, "Undo last change");
+    MCONNECT(editMenu, menuActUndo, performUndo());
+
+    menuActRedo = createMenuAction("&Redo",  QKeySequence::Redo, "Redo last change");
+    MCONNECT(editMenu, menuActRedo, performRedo());
+
+#if 0
+    editMenu->addSeparator();
+
+    menuActCut = createMenuAction("Cu&t",  QKeySequence::Cut, "Cut object");
+    MCONNECT(editMenu, menuActCut, actionCut());
+
+    menuActCopy = createMenuAction("&Copy",  QKeySequence::Copy, "Copy object");
+    MCONNECT(editMenu, menuActCopy, actionCopy());
+
+    menuActPaste = createMenuAction("&Paste", QKeySequence::Paste, "Paste object");
+    MCONNECT(editMenu, menuActPaste, actionPaste());
+
+    menuActDelete = createMenuAction("&Delete", QKeySequence::Delete, "Delete object");
+    MCONNECT(editMenu, menuActDelete, actionDelete());
+#endif
+
+    //
+    // Help menu
+    //
+    helpMenu = menuBar()->addMenu("&Help");
+    act = createMenuAction("About", 0, "Show information about application");
+    MCONNECT(helpMenu, act, actionAboutBox());
+
+
+    qDebug() << "- Constructing toolbars";
+
+    //
+    // Construct the main screen
+    //
+    qDebug() << "- Constructing main screen layout";
+
+    QWidget *sideVBoxContainer = new QWidget();
+    QSizePolicy sideVBoxPolicy(QSizePolicy::Fixed, QSizePolicy::Ignored);
+    sideVBoxPolicy.setHeightForWidth(sideVBoxContainer->sizePolicy().hasHeightForWidth());
+    sideVBoxContainer->setSizePolicy(sideVBoxPolicy);
+
+    QVBoxLayout *sideVBox = new QVBoxLayout(sideVBoxContainer);
+    sideVBox->setSpacing(0);
+    sideVBox->setContentsMargins(0, 0, 0, 0);
+
+//    view = new MapView();
+
+    QWidget *holder = new QWidget();
+    QHBoxLayout *viewSplitter = new QHBoxLayout(holder);
+
+    viewSplitter->addWidget(sideVBoxContainer);
+//    viewSplitter->addWidget(view);
+
+    setCentralWidget(holder);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/edmain.cpp	Wed Oct 17 02:27:55 2012 +0300
@@ -0,0 +1,436 @@
+//
+// Demo Editor -- Main program
+// (C) Copyright 2012 Matti 'ccr' Hämäläinen <ccr@tnsp.org>
+//
+#include <SDL.h>
+#include "dmengine.h"
+#include "edmain.h"
+#include "eddemoobj.h"
+#include <QSettings>
+#include <QGLWidget>
+
+
+int main(int argc, char *argv[])
+{
+    dmVerbosity = 5;
+
+    QApplication app(argc, argv);
+
+    app.setOrganizationName("TNSP");
+    app.setOrganizationDomain("tnsp.org");
+    app.setApplicationName(PROGRAM_NAME);
+    app.setApplicationVersion(PROGRAM_VERSION);
+
+    DemoEditor mainWin;
+
+    mainWin.show();
+          
+    return app.exec();
+}
+
+
+void engineAudioCallback(void *userdata, Uint8 * stream, int len)
+{
+    DMEngineData *engine = (DMEngineData *) userdata;
+
+    if (engine->paused)
+    {
+        memset(stream, 0, len);
+    }
+    else
+#ifdef DM_USE_JSS
+    {
+        if (engine->dev != NULL)
+            jvmRenderAudio(engine->dev, stream,
+                           len / jvmGetSampleSize(engine->dev));
+    }
+#endif
+#ifdef DM_USE_TREMOR
+    if (engine->audioPos + len >= engine->audioRes->rdataSize)
+    {
+        engine->exitFlag = true;
+    }
+    else
+    {
+        memcpy(stream, (Uint8 *) engine->audioRes->rdata + engine->audioPos, len);
+        engine->audioPos += len;
+    }
+#endif
+}
+
+
+int DemoEditor::reopenResources()
+{
+    int err;
+
+    if ((err = dmres_init(&engine.resources, engine.optPackFilename, engine.optDataPath,
+                           engine.optResFlags, engineClassifier)) != DMERR_OK)
+    {
+        dmError("Could not initialize resource manager: %d, %s.\n",
+            err, dmErrorStr(err));
+    }
+    return err;
+}
+
+
+int DemoEditor::loadResources()
+{
+    int err, loaded, total;
+    err = dmres_preload(engine.resources, true, &loaded, &total);
+
+    while ((err = dmres_preload(engine.resources, false, &loaded, &total)) == DMERR_PROGRESS)
+    {
+        // Show a nice progress bar while loading
+        if (total > 0 && (loaded % 2) == 0)
+        {
+/*
+            if ((err = engineShowProgress(loaded, total)) != DMERR_OK)
+                return err;
+*/
+        }
+    }
+    return DMERR_OK;
+}
+
+
+DemoEditor::DemoEditor()
+{
+    int err;
+    initSDL = FALSE;
+
+    resize(1024, 768);
+    setWindowTitle(QCoreApplication::applicationName());
+
+    memset(&engine, 0, sizeof(engine));
+
+    // Pre-initialization
+    if ((err = demoPreInit(&engine)) != DMERR_OK)
+        goto error_exit;
+
+    // Initialize resource subsystem
+    dmPrint(1, "Initializing resources subsystem.\n");
+    if ((err = reopenResources()) != DMERR_OK)
+        goto error_exit;
+
+    // Initialize SDL components
+    dmPrint(1, "Initializing libSDL.\n");
+    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_TIMER) != 0)
+    {
+        dmError("Could not initialize SDL: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+    initSDL = true;
+
+    // Initialize audio parts
+    if (engine.optAfmt.freq == 0 && engine.optAfmt.channels == 0)
+    {
+        // Defaults, if none seem to be set
+        engine.optAfmt.freq = 44100;
+        engine.optAfmt.format = AUDIO_S16SYS;
+        engine.optAfmt.channels = 2;
+        engine.optAfmt.samples = engine.optAfmt.freq / 16;
+    }
+
+#ifdef DM_USE_JSS
+    jssInit();
+
+    switch (engine.optAfmt.format)
+    {
+        case AUDIO_S16SYS:
+            engine.jss_format = JSS_AUDIO_S16;
+            break;
+        case AUDIO_U16SYS:
+            engine.jss_format = JSS_AUDIO_U16;
+            break;
+        case AUDIO_S8:
+            engine.jss_format = JSS_AUDIO_S8;
+            break;
+        case AUDIO_U8:
+            engine.jss_format = JSS_AUDIO_U8;
+            break;
+    }
+
+    dmPrint(1, "Initializing miniJSS mixer with fmt=%d, chn=%d, freq=%d\n",
+            engine.jss_format, engine.optAfmt.channels, engine.optAfmt.freq);
+
+    if ((engine.dev =
+         jvmInit(engine.jss_format, engine.optAfmt.channels,
+                 engine.optAfmt.freq, JMIX_AUTO)) == NULL)
+    {
+        dmError("jvmInit() returned NULL, voi perkele.\n");
+        goto error_exit;
+    }
+
+    if ((engine.plr = jmpInit(engine.dev)) == NULL)
+    {
+        dmError("jmpInit() returned NULL\n");
+        goto error_exit;
+    }
+#endif
+
+    // Initialize SDL audio
+    dmPrint(1, "Trying to init SDL audio with: fmt=%d, chn=%d, freq=%d\n",
+            engine.optAfmt.format, engine.optAfmt.channels,
+            engine.optAfmt.freq);
+
+    engine.optAfmt.callback = engineAudioCallback;
+
+    if (SDL_OpenAudio(&engine.optAfmt, NULL) < 0)
+    {
+        dmError("Couldn't open SDL audio: %s\n", SDL_GetError());
+        goto error_exit;
+    }
+
+    // Initialize SDL video
+    if (engine.demoInitPreVideo != NULL &&
+        (err = engine.demoInitPreVideo(&engine)) != DMERR_OK)
+    {
+        dmError("demoInitPreVideo() failed, %d: %s\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    dmPrint(1, "Initializing SDL video %d x %d x %dbpp, flags=0x%08x\n",
+        engine.optScrWidth, engine.optScrHeight, engine.optBitDepth, engine.optVFlags);
+
+    engine.screen = SDL_CreateRGBSurface(SDL_SWSURFACE, engine.optScrWidth, engine.optScrHeight, engine.optBitDepth, 0, 0, 0, 0);
+    if (engine.screen == NULL)
+    {
+        dmError("Could not allocate video backbuffer surface.\n");
+        goto error_exit;
+    }
+
+    if (engine.demoInitPostVideo != NULL &&
+        (err = engine.demoInitPostVideo(&engine)) != DMERR_OK)
+    {
+        dmError("demoInitPostVideo() failed, %d: %s\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    // Load resources
+    dmPrint(1, "Loading resources, please wait...\n");
+    if ((err = loadResources()) != DMERR_OK)
+    {
+        dmError("Error loading resources, %d: %s.\n", err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    dmPrint(1, "Initializing effects, etc.\n");
+
+    // Final initializations
+    if ((err = engine.demoInit(&engine)) != DMERR_OK)
+    {
+        dmError("Failure in demoInit(), %d: %s\n",
+            err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+    // Initialize effects
+    if ((err = engineInitializeEffects(&engine)) != DMERR_OK)
+    {
+        dmError("Effects initialization failed, %d: %s\n",
+            err, dmErrorStr(err));
+        goto error_exit;
+    }
+
+error_exit:
+
+    // Setup GUI elements
+    demo = NULL;
+    createMainGUI();
+    createNewFile();
+
+    // Finalize init
+    settingsRestore();
+
+//    view->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
+
+    updateMenuStates();
+    statusMsg("Application started.");
+}
+
+
+DemoEditor::~DemoEditor()
+{
+    dmPrint(1, "Shutting down.\n");
+
+    settingsSave();
+//    delete view;
+    delete demo;
+    historyReset();
+
+    if (engine.screen)
+        SDL_FreeSurface(engine.screen);
+
+    SDL_LockAudio();
+    SDL_PauseAudio(1);
+#ifdef DM_USE_JSS
+    jmpClose(engine.plr);
+    jvmClose(engine.dev);
+    jssClose();
+#endif
+    SDL_UnlockAudio();
+
+    dmFreeTimeline(engine.tl);
+    dmFreePreparedTimelineData(engine.ptl);
+    engineShutdownEffects(&engine);
+    dmres_close(engine.resources);
+
+    if (engine.demoShutdown != NULL)
+        engine.demoShutdown(&engine);
+
+    if (initSDL)
+        SDL_Quit();
+
+    if (engine.demoQuit != NULL)
+        engine.demoQuit(&engine);
+}
+
+
+void DemoEditor::rehashFile()
+{
+//    view->setDemoObject(demo);
+    update();
+    historyReset();
+    changed = false;
+    updateMenuStates();
+}
+
+
+void DemoEditor::createNewFile()
+{
+    delete demo;
+    demo = new DemoObject();
+    rehashFile();
+}
+
+
+void DemoEditor::readFromFile(QString filename)
+{
+    DemoObject *tmp = new DemoObject();
+    int ret = tmp->load(filename);
+    if (ret != DMERR_OK)
+    {
+        showFileErrorDialog("Loading demo blob file "+ filename, ret, tmp->lastError);
+        delete tmp;
+    }
+    else
+    {
+        delete demo;
+        demo = tmp;
+        rehashFile();
+    }
+}
+
+
+void DemoEditor::saveToFile(QString filename)
+{
+    int ret = demo->save(filename);
+    if (ret != DMERR_OK)
+    {
+        showFileErrorDialog("Saving demo blob file "+ filename, ret, demo->lastError);
+    }
+
+    updateMenuStates();
+}
+
+
+void DemoEditor::settingsRestore()
+{
+    QSettings s;
+
+    restoreGeometry(s.value("windowGeometry").toByteArray());
+    restoreState(s.value("windowState").toByteArray());
+
+}
+
+
+void DemoEditor::settingsSave()
+{
+    QSettings s;
+
+    s.setValue("windowGeometry", saveGeometry());
+    s.setValue("windowState", saveState());
+
+}
+
+
+//
+// Edit history functionality
+//
+void DemoEditor::historyReset()
+{
+    changed = false;
+    undoHistoryPos = -1;
+    undoHistoryMax = DOC_UNDO_MAX;
+    undoHistory.clear();
+}
+
+
+void DemoEditor::historyPush(QString description)
+{
+    if (!undoHistory.isEmpty() && undoHistory.last()->state == "-")
+    {
+        delete undoHistory.takeLast();
+    }
+    
+    while (undoHistory.size() >= undoHistoryMax)
+    {
+        delete undoHistory.takeFirst();
+    }
+    
+    DemoObject *copy = new DemoObject(demo);
+    copy->state = description;
+    undoHistory.append(copy);
+}
+
+
+void DemoEditor::historyTop()
+{
+    DemoObject *copy = new DemoObject(demo);
+    copy->state = "-";
+    undoHistory.append(copy);
+
+    undoHistoryPos = undoHistory.size() - 1;
+    changed = true;
+    updateMenuStates();
+    update();
+}
+
+
+void DemoEditor::historyPop()
+{
+    if (!undoHistory.isEmpty())
+    {
+        delete undoHistory.takeLast();
+    }
+}
+
+
+void DemoEditor::performRedo()
+{
+    if (undoHistoryPos >= 0 && undoHistoryPos < undoHistory.size() - 1)
+    {
+        undoHistoryPos++;
+        delete demo;
+        demo = new DemoObject(undoHistory.at(undoHistoryPos));
+        changed = true;
+        
+        updateMenuStates();
+        update();
+    }
+}
+
+
+void DemoEditor::performUndo()
+{
+    if (undoHistoryPos > 0 && undoHistory.size() > 1)
+    {
+        undoHistoryPos--;
+        delete demo;
+        demo = new DemoObject(undoHistory.at(undoHistoryPos));
+        changed = true;
+        
+        updateMenuStates();
+        update();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/edmain.h	Wed Oct 17 02:27:55 2012 +0300
@@ -0,0 +1,98 @@
+#ifndef EDMAIN_H
+#define EDMAIN_H
+
+// Program name etc
+#define PROGRAM_NAME      "DMPE Editor"
+#define PROGRAM_VERSION   "0.1"
+
+// Defaults
+#define DOC_DEF_FILENAME  "Untitled"
+#define DOC_UNDO_MAX	  30
+
+#include "eddemoobj.h"
+
+#include <QDebug>
+#include <QFile>
+#include <QApplication>
+#include <QMainWindow>
+#include <QTableView>
+#include <QAction>
+#include <QActionGroup>
+#include <QSlider>
+#include <QMessageBox>
+#include <QCheckBox>
+
+
+class DemoEditor : public QMainWindow
+{
+    Q_OBJECT
+
+public:
+    DemoEditor();
+    ~DemoEditor();
+
+    void settingsRestore();
+    void settingsSave();
+
+
+private slots:
+    void actionFileNew();
+    void actionFileOpen();
+    void actionFileSave();
+    void actionFileSaveAs();
+
+    void actionAboutBox();
+    
+    //void actionCut();
+    //void actionCopy();
+    //void actionPaste();
+    //void actionDelete();
+    
+    void performUndo();
+    void performRedo();
+
+private:
+    bool changed, initSDL;
+    DemoObject *demo;
+    DMEngineData engine;
+
+
+    QTableView *list;
+    QAction *menuActUndo, *menuActRedo, *menuActOpen, *menuActSave, *menuActSaveAs;
+//    QAction *menuActCut, *menuActCopy, *menuActPaste, *menuActDelete;
+
+    QAction * createToolButton(QActionGroup *group, QString name, QIcon icon, QString statustip, QVariant data);
+    QAction * createMenuAction(QString name, const QKeySequence &shortcut, QString tooltip);
+    QAction * createMenuGroupAction(QMenu *, QActionGroup *, QString name, const QKeySequence &shortcut, QString tooltip, QVariant data);
+    void setActionGroupChecked(QActionGroup *group, QVariant data);
+
+
+    void showFileErrorDialog(QString operation, int code, QFile::FileError err);
+    QMessageBox::StandardButton showDocumentModifiedDialog();
+    void statusMsg(QString message);
+    void closeEvent(QCloseEvent *event);
+    void createMainGUI();
+    void updateMenuStates();
+
+    int reopenResources();
+    int loadResources();
+    bool initializeVideo();
+    
+
+    void rehashFile();
+    void createNewFile();
+    void readFromFile(QString filename);
+    void saveToFile(QString filename);
+
+
+    QList<DemoObject *> undoHistory;
+    int undoHistoryPos, undoHistoryMax;
+
+    void historyReset();
+    void historyPush(QString description);
+    void historyTop();
+    void historyPop();
+};
+
+
+#endif // EDMAIN_H
--- a/edtimeline.cpp	Wed Oct 17 01:47:49 2012 +0300
+++ b/edtimeline.cpp	Wed Oct 17 02:27:55 2012 +0300
@@ -59,18 +59,6 @@
     for (int event = 0; event < track->nevents; event++)
     {
         DMTimelineEvent *ev = track->events[event];
-
-        if (ev->startTime >= offs && ev->startTime < twidth ||
-            ev->startTime + ev->duration >= offs)
-        {
-            int ex0 = x0 + (ev->startTime - offs) * scale,
-                ew = ex0 + ev->duration * scale,
-                ex1 = ex0 + ew <= x1 ? ex0 + ew : x1;
-
-            painter.setPen(waveColor);
-            dmFillRect(screen, ex0, y0, ex1, y1, eventColor);
-            //dmDrawTTFText(screen, font, fontcol, ex0, y0, "'%s'", ev->effect->name);
-        }
     }
 
     if (time >= offs * scale && time - offs <= width() * scale)
--- a/edwaveform.cpp	Wed Oct 17 01:47:49 2012 +0300
+++ b/edwaveform.cpp	Wed Oct 17 02:27:55 2012 +0300
@@ -4,25 +4,20 @@
 
 WaveformView::WaveformView(QWidget *parent) : QWidget(parent)
 {
-    track = NULL;
-    time = offs = 0;
+    data = NULL;
+    len = time = offs = 0;
     scale = 1.0f;
 }
 
 
-void WaveformView::setTrack(DMWaveform *mtrack)
+void WaveformView::setWaveform(qint16 *mdata, int mlen)
 {
-    track = mtrack;
+    data = mdata;
+    len = mlen;
     update();
 }
 
 
-DMWaveform * WaveformView::getTrack()
-{
-    return track;
-}
-
-
 void WaveformView::setTime(const int mtime)
 {
     time = mtime;
@@ -61,7 +56,7 @@
     int prevY = 0, prevX = 0;
     for (int xc = 0; xc < width(); xc++)
     {
-        qint16 value = data[(offs + xc) * scale];
+        qint16 value = data[(int) ((offs + xc) * scale)];
         painter.drawLine(prevX, prevY, xc, value);
         prevY = value; prevX = xc;
     }
--- a/edwaveform.h	Wed Oct 17 01:47:49 2012 +0300
+++ b/edwaveform.h	Wed Oct 17 02:27:55 2012 +0300
@@ -10,7 +10,7 @@
 
 public:
     WaveformView(QWidget *parent = 0);
-    void setWaveform(qint16 *mdata, int len);
+    void setWaveform(qint16 *mdata, int mlen);
 
     void setTime(const int mtime);
     void setOffset(const int moffs);