diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fcd67e4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +*.o +*.a +*.lib +*.ncb +*.pdb +*.suo +*.idb +*.user +*.sdf +*.opensdf +moc_*.cpp +qrc_*.cpp +ui_*.h +.*.swp +.DS_Store +*~ +/editor/Makefile* +/editor/editor.vcproj +/editor/editor.sln +/editor/debug/ +/editor/release/ +/editor/editor/ +/editor/editor.app/ +/example_bass/example_bass +/example_bass/Debug Client/ +/example_bass/Debug/ +/example_bass/Release Client/ +/example_bass/Release/ +/example_bass/x64/ +/example_bass/include/ +/example_bass/lib/ +/lib/Debug Client/ +/lib/Debug/ +/lib/Release Client/ +/lib/Release/ +/lib/x64/ +/packages/ +build-* diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..fb36f62 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +language: c + +os: + - linux + - osx + +before_script: + - if [ "$TRAVIS_OS_NAME" = "osx" ]; then brew install qt; fi + +script: make diff --git a/COPYING b/COPYING index d46968d..a2d288f 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink +Copyright (C) 2007 Contributors This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/Makefile b/Makefile index 0a97efa..efd88b4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,10 @@ # default target all: +.PHONY: all clean editor + +QMAKE ?= qmake + # default build flags CFLAGS = -g -O2 -Wall @@ -13,31 +17,57 @@ ifdef COMSPEC SDL_LIBS = -lSDL LDLIBS += -lws2_32 else - OPENGL_LIBS = -lGL -lGLU + UNAME_S := $(shell uname -s) + + ifeq ($(UNAME_S), Linux) + CPPFLAGS += -DUSE_GETADDRINFO + OPENGL_LIBS = -lGL -lGLU + else ifeq ($(UNAME_S), Darwin) + CPPFLAGS += -DUSE_GETADDRINFO + OPENGL_LIBS = -framework OpenGL + else + OPENGL_LIBS = -lGL -lGLU + endif + SDL_CFLAGS = $(shell sdl-config --cflags) SDL_LIBS = $(shell sdl-config --libs) LDLIBS += -lm endif -SYNC_OBJS = \ - sync/data.o \ - sync/device.o \ - sync/track.o +LIB_OBJS = \ + lib/device.o \ + lib/track.o -all: lib/librocket.a +all: lib/librocket.a lib/librocket-player.a editor -bin/example_bass$X: CPPFLAGS += -Iexample_bass/include -bin/example_bass$X: CXXFLAGS += $(SDL_CFLAGS) -bin/example_bass$X: LDLIBS += -Lexample_bass/lib -lbass -bin/example_bass$X: LDLIBS += $(OPENGL_LIBS) $(SDL_LIBS) +example_bass/%$X: CPPFLAGS += -Iexample_bass/include +example_bass/%$X: CXXFLAGS += $(SDL_CFLAGS) +example_bass/%$X: LDLIBS += -Lexample_bass/lib -lbass +example_bass/%$X: LDLIBS += $(OPENGL_LIBS) $(SDL_LIBS) clean: - $(RM) -rf $(SYNC_OBJS) lib bin + $(RM) $(LIB_OBJS) lib/librocket.a lib/librocket-player.a + $(RM) example_bass/example_bass$X example_bass/example_bass-player$X + if test -e editor/Makefile; then $(MAKE) -C editor clean; fi; + $(RM) editor/editor editor/Makefile -lib/librocket.a: $(SYNC_OBJS) - @mkdir -p lib +lib/librocket.a: $(LIB_OBJS) $(AR) $(ARFLAGS) $@ $^ -bin/example_bass$X: example_bass/example_bass.cpp lib/librocket.a - @mkdir -p bin +%.player.o : %.c + $(COMPILE.c) -DSYNC_PLAYER $(OUTPUT_OPTION) $< + +lib/librocket-player.a: $(LIB_OBJS:.o=.player.o) + $(AR) $(ARFLAGS) $@ $^ + +example_bass/example_bass$X: example_bass/example_bass.cpp lib/librocket.a $(LINK.cpp) $^ $(LOADLIBES) $(LDLIBS) -o $@ + +example_bass/example_bass-player$X: example_bass/example_bass.cpp lib/librocket-player.a + $(LINK.cpp) -DSYNC_PLAYER $^ $(LOADLIBES) $(LDLIBS) -o $@ + +editor/Makefile: editor/editor.pro + cd editor && $(QMAKE) editor.pro -o Makefile + +editor: editor/Makefile + $(MAKE) -C editor diff --git a/README b/README deleted file mode 100644 index 87d979f..0000000 --- a/README +++ /dev/null @@ -1,91 +0,0 @@ -GNU Rocket -========== -GNU Rocket is an intuitive new way of... bah, whatever. It's a sync-tracker, -a tool for synchronizing music and visuals in demoscene productions. It -consists of a GUI editor that runs on Microsoft Windows, and an ANSI C -library that can either communicate with the editor over a network socket, -or play back an exported data-set. - -Compile Editor --------------- -GNU Rocket compiles using Microsoft Visual Studio 2008. Open editor.sln and -select "Build" -> "Build Solution" from the menu to build the editor. - -Compile Example ---------------- -GNU Rocket contains an example client called example_bass. This is a simple -OpenGL, SDL 1.2 and BASS audio library application, that demonstrates how to -use the GNU Rocket API. - -Before compiling the example, you need to make sure you have recent -SDL and BASS libraries and includes. These can be downloaded from the -following web-sites: - -http://www.libsdl.org/ -http://www.un4seen.com/ - -The header files and libraries can be installed local to the project by -copying all .lib-files to the example_bass/lib/, all .h files to -example_bass/inclide/, and all .dll files to the example_bass/. - -Once the prerequisites are installed, the example can be compiled much like -the editor; by opening examples.sln and selecting "Build" -> "Build Solution" -from the menu. - -Using the editor ----------------- -The GNU Rocket editor is laid out like a music-tracker; tracks (or columns) -and rows. Each track represents a separate "variable" in the demo, over the -entire time-domain of the demo. Each row represents a specific point in time, -and consists of a set of key-frames. The key-frames are interpolated over time -according to their interpolation modes. - -Interpolation modes -------------------- -Each key-frame has an interpolation mode associated with it, and that -interpolation mode is valid until the next key-frame is reached. The different -interpolation modes are the following: - -* Step : This is the simplest mode, and always returns the key's value. -* Linear : This does a linear interpolation between the current and the next - key's values. -* Smooth : This interpolates in a smooth fashion, the exact function is what - is usually called "smoothstep". Do not confuse this mode with - splines; this only interpolates smoothly between two different - values, it does not try to calculate tangents or any such things. -* Ramp : This is similar to "Linear", but additionally applies an - exponentiation of the interpolation factor. - -Keyboard shortcuts -------------------- -Some of the GNU Rocket editor's features are available through the menu and -some keyboard shortcut. Here's a list of the supported keyboard shortcuts: - -Up/Down/Left/Right Move cursor -PgUp/PgDn Move cursor 16 rows up/down -Home/End Move cursor to begining/end -Ctrl+Left/Right Move track -Enter Enter key-frame value -Del Delete key-frame -i Enumerate interpolation mode -k Toggle bookmark -Alt+PgUp/PgDn Go to prev/next bookmark -Space Pause/Resume demo -Shift+Up/Down/Left/Right Select -Ctrl+C Copy -Ctrl+V Paste -Ctrl+Z Undo -Shift+Ctrl+Z Redo -Ctrl+B Bias keyframes -Shift+Ctrl+Up/Down Quick-bias by +/- 0.1 -Ctrl+Up/Down Quick-bias by +/- 1 -Ctrl+PgUp/PgDn Quick-bias by +/- 10 -Shift+Ctrl+PgUp/PgDn Quick-bias by +/- 100 - -Bugs and feed-back ------------------- -Please report bugs or other feed-back to the GNU Rocket mailing list: -rocket-users@lists.sourceforge.net - -Patches or technical questions can be sent to the developer-list: -rocket-developers@lists.sourceforge.net diff --git a/README.md b/README.md new file mode 100644 index 0000000..f119a1b --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +GNU Rocket +========== + +[![Build status](https://ci.appveyor.com/api/projects/status/dfq8qaedc6mtsefg/branch/master?svg=true)](https://ci.appveyor.com/project/kusma/rocket/branch/master) +[![Build Status](https://travis-ci.org/kusma/rocket.svg?branch=master)](https://travis-ci.org/kusma/rocket) + +GNU Rocket is an intuitive new way of... bah, whatever. It's a sync-tracker, +a tool for synchronizing music and visuals in demoscene productions. It +consists of a GUI editor (using Qt), and an ANSI C library that can either +communicate with the editor over a network socket, or play back an exported +data-set. + +Compile Editor +-------------- +The GNU Rocket editor uses qmake as a build-system abstraction, which can +be used to output Makefiles, Visual Studio project files or can be built +directly from QtCreator. See the qmake documentation for details. + +Compile Example +--------------- +GNU Rocket contains an example client called example\_bass. This is a simple +OpenGL, SDL 1.2 and BASS audio library application, that demonstrates how to +use the GNU Rocket API. + +Before compiling the example, you need to make sure you have recent [SDL](http://www.libsdl.org/) +and [BASS](http://www.un4seen.com/) libraries and includes. + +The header files and libraries can be installed local to the project by +copying all .lib-files to the example\_bass/lib/, all .h files to +example\_bass/include/, and all .dll files to the example\_bass/. + +Once the prerequisites are installed, the example can be compiled either by +opening examples.sln and selecting "Build" -> "Build Solution" from Visual +Studio 2008, or by doing "make example_bass/example_bass" on Unix-base +systems. + +Using the editor +---------------- +The GNU Rocket editor is laid out like a music-tracker; tracks (or columns) +and rows. Each track represents a separate "variable" in the demo, over the +entire time-domain of the demo. Each row represents a specific point in time, +and consists of a set of key frames. The key frames are interpolated over time +according to their interpolation modes. + +Interpolation modes +------------------- +Each key frame has an interpolation mode associated with it, and that +interpolation mode is valid until the next key frame is reached. The different +interpolation modes are the following: + +* Step : This is the simplest mode, and always returns the key's value. +* Linear : This does a linear interpolation between the current and the next + key's values. +* Smooth : This interpolates in a smooth fashion, the exact function is what + is usually called "smoothstep". Do not confuse this mode with + splines; this only interpolates smoothly between two different + values, it does not try to calculate tangents or any such things. +* Ramp : This is similar to "Linear", but additionally applies an + exponentiation of the interpolation factor. + +Keyboard shortcuts +------------------- +Some of the GNU Rocket editor's features are available through the menu and +some keyboard shortcut. Here's a list of the supported keyboard shortcuts: + +| Shortcut | Action | +|:-------------------------|:-----------------------------| +| Up/Down/Left/Right | Move cursor | +| PgUp/PgDn | Move cursor 16 rows up/down | +| Home/End | Move cursor to begining/end | +| Ctrl+Left/Right | Move track | +| Enter | Enter key frame value | +| Del | Delete key frame | +| i | Enumerate interpolation mode | +| k | Toggle bookmark | +| Alt+PgUp/PgDn | Go to prev/next bookmark | +| Space | Pause/Resume demo | +| Shift+Up/Down/Left/Right | Select | +| Ctrl+C | Copy | +| Ctrl+V | Paste | +| Ctrl+Z | Undo | +| Shift+Ctrl+Z | Redo | +| Ctrl+B | Bias key frames | +| Shift+Ctrl+Up/Down | Quick-bias by +/- 0.1 | +| Ctrl+Up/Down | Quick-bias by +/- 1 | +| Ctrl+PgUp/PgDn | Quick-bias by +/- 10 | +| Shift+Ctrl+PgUp/PgDn | Quick-bias by +/- 100 | + +Bugs and feedback +------------------ +Please report bugs or other feedback to the GNU Rocket mailing list: +gnu-rocket@googlegroups.com diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..89a0795 --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,48 @@ +version: '{build}' + +environment: + matrix: + - platform: x64 + cc: VS2013 + QTDIR: C:\Qt\5.5\msvc2013_64 + - platform: Win32 + cc: VS2013 + QTDIR: C:\Qt\5.5\msvc2013 + +configuration: + - Release + +cache: + - packages -> **\packages.config + +install: + - nuget restore examples.vs2013.sln + # download and install bass + - curl -fsS -o bass24.zip http://www.un4seen.com/files/bass24.zip + - 7z x -obass24 bass24.zip > NUL + - mkdir example_bass\lib + - mkdir example_bass\lib64 + - mkdir example_bass\include + - copy bass24\c\bass.lib example_bass\lib + - copy bass24\c\x64\bass.lib example_bass\lib64 + - copy bass24\c\bass.h example_bass\include + +before_build: + - set PATH=%QTDIR%\bin;%PATH% + +build_script: + - msbuild examples.vs2013.sln + - msbuild examples.vs2013.sln /property:Configuration="Release Client" + - cd editor + - qmake -tp vc editor.pro + - msbuild + +after_build: + - mkdir staging + - cd staging + - copy ..\release\editor.exe . + - windeployqt --release editor.exe + +artifacts: + - path: editor\staging + name: editor diff --git a/contrib/DotRocket/DotRocket/DotRocket.cpp b/contrib/DotRocket/DotRocket/DotRocket.cpp index 5f2a913..79f9563 100644 --- a/contrib/DotRocket/DotRocket/DotRocket.cpp +++ b/contrib/DotRocket/DotRocket/DotRocket.cpp @@ -4,21 +4,22 @@ #include "DotRocket.h" -#include "../../../sync/sync.h" -#include "../../../sync/track.h" +#include "../../../lib/sync.h" +#include "../../../lib/track.h" using System::Runtime::InteropServices::Marshal; using DotRocket::Track; using DotRocket::PlayerDevice; -private ref class PlayerTrack: public Track { +private ref class PlayerTrack: public Track +{ const sync_track *track; public: PlayerTrack(const sync_track *track): track(track) {} - virtual float GetValue(double time) override + virtual double GetValue(double time) override { return sync_get_val(track, time); - }; + } }; PlayerDevice::PlayerDevice(System::String ^name) diff --git a/contrib/DotRocket/DotRocket/DotRocket.h b/contrib/DotRocket/DotRocket/DotRocket.h index 3625fd7..1df3d9c 100644 --- a/contrib/DotRocket/DotRocket/DotRocket.h +++ b/contrib/DotRocket/DotRocket/DotRocket.h @@ -3,7 +3,6 @@ #pragma once struct sync_device; -struct sync_track; using namespace System; using namespace System::Collections::Generic; @@ -13,7 +12,7 @@ namespace DotRocket public ref class Track abstract { public: - virtual float GetValue(double time) = 0; + virtual double GetValue(double time) = 0; }; public delegate void PauseEventHandler(bool flag); diff --git a/contrib/DotRocket/DotRocket/DotRocket.vcproj b/contrib/DotRocket/DotRocket/DotRocket.vcproj index f3a1cda..054e191 100644 --- a/contrib/DotRocket/DotRocket/DotRocket.vcproj +++ b/contrib/DotRocket/DotRocket/DotRocket.vcproj @@ -59,7 +59,7 @@ /> using DotRocket::Track; using DotRocket::Device; using DotRocket::ClientDevice; -private ref class ClientTrack: public Track { +private ref class ClientTrack: public Track +{ const sync_track *track; public: ClientTrack(const sync_track *track) : track(track) {} - virtual float GetValue(double time) override + virtual double GetValue(double time) override { return sync_get_val(track, time); + } +}; + +namespace +{ + class DeviceReference + { + public: + DeviceReference(Device ^dev) : dev(dev) {} + Device ^GetDevice() { return dev; } + private: + gcroot dev; }; -}; - -ref class Callbacks { -public: - static Device ^DeviceCurrenltyBeingProcessed = nullptr; -}; - -void cb_pause(void *arg, int row) -{ - Callbacks::DeviceCurrenltyBeingProcessed->Pause(row); } -void cb_set_row(void *arg, int row) +static void cb_pause(void *arg, int flag) { - Callbacks::DeviceCurrenltyBeingProcessed->SetRow(row); + ((DeviceReference *)arg)->GetDevice()->Pause(!!flag); } -int cb_is_playing(void *arg) +static void cb_set_row(void *arg, int row) { - return !!Callbacks::DeviceCurrenltyBeingProcessed->IsPlaying(); + ((DeviceReference *)arg)->GetDevice()->SetRow(row); } -sync_cb callbacks[] = { +static int cb_is_playing(void *arg) +{ + return ((DeviceReference *)arg)->GetDevice()->IsPlaying(); +} + +static sync_cb callbacks[] = { cb_pause, cb_set_row, cb_is_playing @@ -76,13 +85,13 @@ ClientDevice::!ClientDevice() bool ClientDevice::Connect(System::String^ host, unsigned short port) { char *chost = (char *)(void *)Marshal::StringToHGlobalAnsi(host); - int result = sync_connect((sync_device *)device, chost, port); + int result = sync_connect(device, chost, port); Marshal::FreeHGlobal((System::IntPtr)chost); return !result; } bool ClientDevice::Update(int row) { - Callbacks::DeviceCurrenltyBeingProcessed = this; - return !sync_update(device, row, callbacks, device); + DeviceReference devref(this); + return !sync_update(device, row, callbacks, &devref); } diff --git a/contrib/DotRocket/DotRocketClient/DotRocketClient.h b/contrib/DotRocket/DotRocketClient/DotRocketClient.h index d9ecf87..35f118e 100644 --- a/contrib/DotRocket/DotRocketClient/DotRocketClient.h +++ b/contrib/DotRocket/DotRocketClient/DotRocketClient.h @@ -3,13 +3,14 @@ #pragma once struct sync_device; -struct sync_track; using namespace System; using namespace System::Collections::Generic; -namespace DotRocket { - public ref class ClientDevice: public Device { +namespace DotRocket +{ + public ref class ClientDevice: public Device + { protected: sync_device *device; Dictionary ^tracks; diff --git a/contrib/DotRocket/DotRocketClient/DotRocketClient.vcproj b/contrib/DotRocket/DotRocketClient/DotRocketClient.vcproj index 660b426..c9c1175 100644 --- a/contrib/DotRocket/DotRocketClient/DotRocketClient.vcproj +++ b/contrib/DotRocket/DotRocketClient/DotRocketClient.vcproj @@ -59,7 +59,7 @@ /> -#define ID_FILE_NEW 0xE100 -#define ID_FILE_OPEN 0xE101 -#define ID_FILE_SAVE 0xE103 -#define ID_FILE_SAVE_AS 0xE104 -#define ID_EDIT_CLEAR 0xE120 -#define ID_EDIT_COPY 0xE122 -#define ID_EDIT_CUT 0xE123 -#define ID_EDIT_PASTE 0xE125 -#define ID_EDIT_SELECT_ALL 0xE12A -#define ID_EDIT_UNDO 0xE12B -#define ID_EDIT_REDO 0xE12C diff --git a/editor/appicon.icns b/editor/appicon.icns new file mode 100644 index 0000000..fff9e92 Binary files /dev/null and b/editor/appicon.icns differ diff --git a/editor/clientsocket.cpp b/editor/clientsocket.cpp index 3e47624..95df10d 100644 --- a/editor/clientsocket.cpp +++ b/editor/clientsocket.cpp @@ -1,47 +1,177 @@ #include "clientsocket.h" -#include "../sync/track.h" +#include "syncdocument.h" -#include -#include +#include +#include -void ClientSocket::sendSetKeyCommand(const std::string &trackName, const struct track_key &key) +bool WebSocket::readFrame(QByteArray &buf) +{ + unsigned char header[2]; + if (!TcpSocket::recv((char *)header, 2)) + return false; + + // int flags = header[0] >> 4; + int opcode = header[0] & 0xF; + int masked = header[1] >> 7; + int payload_len = header[1] & 0x7f; + + if (payload_len == 126) { + quint16 tmp; + if (!TcpSocket::recv((char *)&tmp, 2)) + return false; + payload_len = qFromBigEndian(tmp); + } else if (payload_len == 127) { + // dude, that's one crazy big payload! let's bail! + return false; + } + + unsigned char mask[4] = { 0 }; + if (masked) { + if (!TcpSocket::recv((char *)mask, sizeof(mask))) + return false; + } + + buf.resize(payload_len); + if (payload_len > 0) { + if (!TcpSocket::recv(buf.data(), payload_len)) + return false; + } + + for (int i = 0; i < payload_len; ++i) + buf[i] = buf[i] ^ mask[i & 3]; + + switch (opcode) { + case 9: + // got ping, send pong! + sendFrame(10, buf.data(), buf.length(), true); + buf.clear(); + return true; + + case 8: + // close + disconnect(); + buf.clear(); + return false; + } + + return true; +} + +bool WebSocket::recv(char *buffer, int length) +{ + if (!connected()) + return false; + while (length) { + while (!buf.length() && !readFrame(buf)) + return false; + + int bytes = qMin(buf.length(), length); + memcpy(buffer, buf.data(), bytes); + buf.remove(0, bytes); + buffer += bytes; + length -= bytes; + } + return true; +} + +bool WebSocket::sendFrame(int opcode, const char *payloadData, size_t payloadLength, bool endOfMessage) +{ + unsigned char header[2]; + header[0] = (endOfMessage ? 0x80 : 0) | (unsigned char)opcode; + header[1] = payloadLength < 126 ? (unsigned char)(payloadLength) : 126; + if (!TcpSocket::send((const char *)header, 2, false)) + return false; + + if (payloadLength >= 126) { + Q_ASSERT(payloadLength < 0xffff); + quint16 tmp = qToBigEndian((quint16)(payloadLength)); + if (!TcpSocket::send((const char *)&tmp, 2, false)) + return false; + } + + firstFrame = endOfMessage; + return TcpSocket::send(payloadData, payloadLength, endOfMessage); +} + +WebSocket *WebSocket::upgradeFromHttp(QTcpSocket *socket) +{ + QByteArray key; + for (;;) { + QByteArray line; + for (;;) { + char ch; + if (socket->read(&ch, 1) != 1) + return NULL; + + if (ch == '\n') + break; + if (ch != '\r') + line.push_back(ch); + } + + const char *prefix = "Sec-WebSocket-Key: "; + if (line.startsWith(prefix)) + key = line.right(line.length() - int(strlen(prefix))); + else if (!line.length()) + break; + } + + if (!key.length()) + return NULL; + + key.append("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); + + QCryptographicHash hash(QCryptographicHash::Sha1); + hash.addData(key.data(), key.size()); + + QString response = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "; + response.append(hash.result().toBase64()); + response.append("\r\n\r\n"); + + socket->write(response.toUtf8().constData(), response.length()); + + return new WebSocket(socket); +} + + +void ClientSocket::sendSetKeyCommand(const QString &trackName, const SyncTrack::TrackKey &key) { if (!connected() || clientTracks.count(trackName) == 0) return; - uint32_t track = htonl(clientTracks[trackName]); - uint32_t row = htonl(key.row); + quint32 track = qToBigEndian((quint32)clientTracks[trackName]); + quint32 row = qToBigEndian((quint32)key.row); union { float f; - uint32_t i; + quint32 i; } v; v.f = key.value; - v.i = htonl(v.i); + v.i = qToBigEndian(v.i); - assert(key.type < KEY_TYPE_COUNT); + Q_ASSERT(key.type < SyncTrack::TrackKey::KEY_TYPE_COUNT); unsigned char cmd = SET_KEY; - send((char *)&cmd, 1, 0); - send((char *)&track, sizeof(track), 0); - send((char *)&row, sizeof(row), 0); - send((char *)&v.i, sizeof(v.i), 0); - send((char *)&key.type, 1, 0); + send((char *)&cmd, 1, false); + send((char *)&track, sizeof(track), false); + send((char *)&row, sizeof(row), false); + send((char *)&v.i, sizeof(v.i), false); + send((char *)&key.type, 1, true); } -void ClientSocket::sendDeleteKeyCommand(const std::string &trackName, int row) +void ClientSocket::sendDeleteKeyCommand(const QString &trackName, int row) { if (!connected() || clientTracks.count(trackName) == 0) return; - uint32_t track = htonl(int(clientTracks[trackName])); - row = htonl(row); + quint32 track = qToBigEndian((quint32)clientTracks[trackName]); + row = qToBigEndian((quint32)row); unsigned char cmd = DELETE_KEY; - send((char *)&cmd, 1, 0); - send((char *)&track, sizeof(int), 0); - send((char *)&row, sizeof(int), 0); + send((char *)&cmd, 1, false); + send((char *)&track, sizeof(int), false); + send((char *)&row, sizeof(int), true); } void ClientSocket::sendSetRowCommand(int row) @@ -50,9 +180,9 @@ void ClientSocket::sendSetRowCommand(int row) return; unsigned char cmd = SET_ROW; - row = htonl(row); - send((char *)&cmd, 1, 0); - send((char *)&row, sizeof(int), 0); + row = qToBigEndian((quint32)row); + send((char *)&cmd, 1, false); + send((char *)&row, sizeof(int), true); } void ClientSocket::sendPauseCommand(bool pause) @@ -61,9 +191,8 @@ void ClientSocket::sendPauseCommand(bool pause) return; unsigned char cmd = PAUSE, flag = pause; - send((char *)&cmd, 1, 0); - send((char *)&flag, 1, 0); - clientPaused = pause; + send((char *)&cmd, 1, false); + send((char *)&flag, 1, true); } void ClientSocket::sendSaveCommand() @@ -72,5 +201,5 @@ void ClientSocket::sendSaveCommand() return; unsigned char cmd = SAVE_TRACKS; - send((char *)&cmd, 1, 0); + send((char *)&cmd, 1, true); } diff --git a/editor/clientsocket.h b/editor/clientsocket.h index f8f82e6..b3f9d36 100644 --- a/editor/clientsocket.h +++ b/editor/clientsocket.h @@ -1,63 +1,178 @@ -#include "../sync/base.h" -#include +#ifndef CLIENTSOCKET_H +#define CLIENTSOCKET_H -class ClientSocket { +#include +#include +#include + +#include "synctrack.h" + +#define CLIENT_GREET "hello, synctracker!" +#define SERVER_GREET "hello, demo!" + +enum { + SET_KEY = 0, + DELETE_KEY = 1, + GET_TRACK = 2, + SET_ROW = 3, + PAUSE = 4, + SAVE_TRACKS = 5 +}; + +class TcpSocket { public: - ClientSocket() : socket(INVALID_SOCKET) {} - explicit ClientSocket(SOCKET socket) : socket(socket), clientPaused(true) {} + explicit TcpSocket(QAbstractSocket *socket) : socket(socket) {} bool connected() const { - return INVALID_SOCKET != socket; + return socket != NULL; + } + + virtual void disconnect() + { + if (connected()) + socket->close(); + socket = NULL; + } + + virtual bool recv(char *buffer, int length) + { + if (!connected()) + return false; + + // wait for enough data to arrive + while (socket->bytesAvailable() < int(length)) { + if (!socket->waitForReadyRead(-1)) + break; + } + + qint64 ret = socket->read(buffer, length); + if (ret != int(length)) { + TcpSocket::disconnect(); + return false; + } + return true; + } + + virtual bool send(const char *buffer, size_t length, bool endOfMessage) + { + (void)endOfMessage; + if (!connected()) + return false; + int ret = socket->write(buffer, length); + if (ret != int(length)) { + TcpSocket::disconnect(); + return false; + } + return true; + } + + virtual bool pollRead() + { + if (!connected()) + return false; + return socket->bytesAvailable() > 0; + } + + QAbstractSocket *socket; +}; + +class WebSocket : public TcpSocket { +public: + explicit WebSocket(QTcpSocket *socket) : TcpSocket(socket), firstFrame(true) {} + + bool recv(char *buffer, int length); + bool send(const char *buffer, size_t length, bool endOfMessage) + { + return sendFrame(firstFrame ? 2 : 0, buffer, length, endOfMessage); + } + + + virtual void disconnect() + { + sendFrame(8, NULL, 0, true); + TcpSocket::disconnect(); + } + + bool pollRead() + { + if (buf.length() > 0) + return true; + return TcpSocket::pollRead(); + } + + // helpers + bool readFrame(QByteArray &buf); + bool sendFrame(int opcode, const char *payloadData, size_t payloadLength, bool endOfMessage); + static WebSocket *upgradeFromHttp(QTcpSocket *socket); + +private: + bool firstFrame; + QByteArray buf; +}; + +class ClientSocket : public QObject { + Q_OBJECT +public: + ClientSocket() : socket(NULL) {} + + bool connected() const + { + if (!socket) + return false; + return socket->connected(); } void disconnect() { - closesocket(socket); - socket = INVALID_SOCKET; + if (socket) + socket->disconnect(); clientTracks.clear(); } - bool recv(char *buffer, size_t length, int flags) + bool recv(char *buffer, int length) { - if (!connected()) + if (!socket) return false; - int ret = ::recv(socket, buffer, int(length), flags); - if (ret != int(length)) { - disconnect(); - return false; - } - return true; + return socket->recv(buffer, length); } - bool send(const char *buffer, size_t length, int flags) + bool send(const char *buffer, size_t length, bool endOfMessage) { - if (!connected()) + if (!socket) return false; - int ret = ::send(socket, buffer, int(length), flags); - if (ret != int(length)) { - disconnect(); - return false; - } - return true; + return socket->send(buffer, length, endOfMessage); } bool pollRead() { if (!connected()) return false; - return !!socket_poll(socket); + return socket->pollRead(); } - void sendSetKeyCommand(const std::string &trackName, const struct track_key &key); - void sendDeleteKeyCommand(const std::string &trackName, int row); + void sendSetKeyCommand(const QString &trackName, const SyncTrack::TrackKey &key); + void sendDeleteKeyCommand(const QString &trackName, int row); void sendSetRowCommand(int row); void sendPauseCommand(bool pause); void sendSaveCommand(); - bool clientPaused; - std::map clientTracks; + QMap clientTracks; + TcpSocket *socket; -private: - SOCKET socket; +public slots: + void onPauseChanged(bool paused) + { + sendPauseCommand(paused); + } + + void onKeyFrameChanged(const SyncTrack &track, int row) + { + if (track.isKeyFrame(row)) + sendSetKeyCommand(track.name, track.getKeyFrame(row)); + else + sendDeleteKeyCommand(track.name, row); + } }; + +#endif // !defined(CLIENTSOCKET_H) diff --git a/editor/editor.cpp b/editor/editor.cpp index cce7426..ea829e7 100644 --- a/editor/editor.cpp +++ b/editor/editor.cpp @@ -1,765 +1,34 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ +#include +#include +#include +#include "mainwindow.h" -#include "../sync/base.h" -#include "afxres.h" -#include "resource.h" - -#include -#include -#include -#include -#include -#include - -// Windows XP look and feel. Seems to enable Vista look as well. -#pragma comment(linker, \ - "\"/manifestdependency:type='Win32' " \ - "name='Microsoft.Windows.Common-Controls' " \ - "version='6.0.0.0' " \ - "processorArchitecture='*' " \ - "publicKeyToken='6595b64144ccf1df' " \ - "language='*'\"") - -#include "trackview.h" -#include "recentfiles.h" - -#include -static const wchar_t *mainWindowClassName = L"MainWindow"; -static const char *mainWindowTitle = "GNU Rocket System"; -static const wchar_t *mainWindowTitleW = L"GNU Rocket System"; -static const char *keyName = "SOFTWARE\\GNU Rocket"; - -static void verror(const char *fmt, va_list va) +int main(int argc, char *argv[]) { - char temp[4096]; - vsnprintf(temp, sizeof(temp), fmt, va); - MessageBox(NULL, temp, mainWindowTitle, MB_OK | MB_ICONERROR); -} + QApplication app(argc, argv); + app.setOrganizationName("GNU Rocket Foundation"); + app.setApplicationName("GNU Rocket Editor"); + app.setWindowIcon(QIcon(":appicon.ico")); -static void error(const char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - verror(fmt, va); - va_end(va); -} - -static void die(const char *fmt, ...) -{ - va_list va; - va_start(va, fmt); - verror(fmt, va); - va_end(va); - exit(EXIT_FAILURE); -} - -static HINSTANCE hInst; -static HWND hwnd = NULL; -static TrackView *trackView = NULL; -static HWND trackViewWin = NULL; -static HWND statusBarWin = NULL; -static HKEY regConfigKey = NULL; -static RecentFiles mruFileList(NULL); - -#define WM_SETROWS (WM_USER+1) -#define WM_BIASSELECTION (WM_USER+2) - -#include "../sync/sync.h" - -static LRESULT CALLBACK setRowsDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch (message) - { - case WM_INITDIALOG: - { - size_t *rows = (size_t *)lParam; - SetDlgItemInt(hDlg, IDC_SETROWS_EDIT, *rows, FALSE); - return TRUE; - } - break; - - case WM_COMMAND: - if (LOWORD(wParam) == IDOK) { - /* get value */ - size_t result = GetDlgItemInt(hDlg, IDC_SETROWS_EDIT, NULL, FALSE); - - /* update editor */ - SendMessage(GetParent(hDlg), WM_SETROWS, 0, result); - - /* end dialog */ - return EndDialog(hDlg, LOWORD(wParam)); - } else if(LOWORD(wParam) == IDCANCEL) - return EndDialog( hDlg, LOWORD(wParam)); - break; - - case WM_CLOSE: - EndDialog(hDlg, LOWORD(wParam)); - return TRUE; - } - - return FALSE; -} - -static LRESULT CALLBACK biasSelectionDialogProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) -{ - switch (message) - { - case WM_INITDIALOG: - { - int *intialBias = (int*)lParam; - SetDlgItemInt(hDlg, IDC_BIASSELECTION_EDIT, *intialBias, TRUE); - return TRUE; - } - break; - - case WM_COMMAND: - if (LOWORD(wParam) == IDOK) { - /* get value */ - int bias = GetDlgItemInt(hDlg, IDC_BIASSELECTION_EDIT, NULL, FALSE); - - /* update editor */ - SendMessage(GetParent(hDlg), WM_BIASSELECTION, 0, LPARAM(bias)); - - /* end dialog */ - return EndDialog(hDlg, LOWORD(wParam)); - } else if(LOWORD(wParam) == IDCANCEL) - return EndDialog( hDlg, LOWORD(wParam)); - break; - - case WM_CLOSE: - EndDialog(hDlg, LOWORD(wParam)); - return TRUE; - } - - return FALSE; -} - -static void setWindowFileName(std::wstring fileName) -{ - wchar_t drive[_MAX_DRIVE],dir[_MAX_DIR],fname[_MAX_FNAME],ext[_MAX_EXT]; - _wsplitpath(fileName.c_str(), drive, dir, fname, ext); - std::wstring windowTitle = std::wstring(fname) + std::wstring(L" - ") + std::wstring(mainWindowTitleW); - SetWindowTextW(hwnd, windowTitle.c_str()); -} - -static HMENU findSubMenuContaining(HMENU menu, UINT id) -{ - for (int i = 0; i < GetMenuItemCount(menu); ++i) - { - if (GetMenuItemID(menu, i) == id) return menu; - else - { - HMENU subMenu = GetSubMenu(menu, i); - if ((HMENU)0 != subMenu) - { - HMENU ret = findSubMenuContaining(subMenu, id); - if ((HMENU)0 != ret) return ret; - } - } - } - return (HMENU)0; -} - -static void setDocument(SyncDocument *newDoc) -{ - SyncDocument *oldDoc = trackView->getDocument(); - - if (oldDoc && oldDoc->clientSocket.connected()) { - // delete old key-frames - for (size_t i = 0; i < oldDoc->num_tracks; ++i) { - sync_track *t = oldDoc->tracks[i]; - for (int j = 0; j < t->num_keys; ++j) - oldDoc->clientSocket.sendDeleteKeyCommand(t->name, t->keys[j].row); - } - - if (newDoc) { - // add back missing client-tracks - std::map::const_iterator it; - for (it = oldDoc->clientSocket.clientTracks.begin(); it != oldDoc->clientSocket.clientTracks.end(); ++it) { - int trackIndex = sync_find_track(newDoc, it->first.c_str()); - if (0 > trackIndex) - trackIndex = int(newDoc->createTrack(it->first.c_str())); - } - - // copy socket and update client - newDoc->clientSocket = oldDoc->clientSocket; - - for (size_t i = 0; i < newDoc->num_tracks; ++i) { - sync_track *t = newDoc->tracks[i]; - for (int j = 0; j < t->num_keys; ++j) - newDoc->clientSocket.sendSetKeyCommand(t->name, t->keys[j]); - } - } + QTcpServer serverSocket; + if (!serverSocket.listen(QHostAddress::Any, 1338)) { + QMessageBox::critical(NULL, NULL, QString("Could not start server:\n%1").arg(serverSocket.errorString()), QMessageBox::Ok); + exit(EXIT_FAILURE); } - trackView->setDocument(newDoc); - SendMessage(hwnd, WM_CURRVALDIRTY, 0, 0); - InvalidateRect(trackViewWin, NULL, FALSE); + MainWindow mainWindow(&serverSocket); - if (oldDoc) - delete oldDoc; -} - -static void fileNew() -{ - setDocument(new SyncDocument); - setWindowFileName(L"Untitled"); -} - -static void loadDocument(const std::wstring &_fileName) -{ - SyncDocument *newDoc = SyncDocument::load(_fileName); - if (newDoc) { - // update MRU list - mruFileList.insert(_fileName); - mruFileList.update(); - DrawMenuBar(hwnd); - - // set new document - setDocument(newDoc); - setWindowFileName(_fileName.c_str()); - } - else - error("failed to open file"); -} - -static void fileOpen() -{ - wchar_t temp[_MAX_FNAME + 1]; - temp[0] = L'\0'; // clear string - - OPENFILENAMEW ofn; - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.lpstrFile = temp; - ofn.nMaxFile = _MAX_FNAME; - ofn.lpstrDefExt = L"rocket"; - ofn.lpstrFilter = L"ROCKET File (*.rocket)\0*.rocket\0All Files (*.*)\0*.*\0\0"; - ofn.Flags = OFN_SHOWHELP | OFN_FILEMUSTEXIST; - if (GetOpenFileNameW(&ofn)) - { - loadDocument(temp); - } -} - -static bool fileSaveAs() -{ - wchar_t temp[_MAX_FNAME + 1]; - temp[0] = '\0'; - - OPENFILENAMEW ofn; - ZeroMemory(&ofn, sizeof(ofn)); - ofn.lStructSize = sizeof(ofn); - ofn.lpstrFile = temp; - ofn.nMaxFile = _MAX_FNAME; - ofn.lpstrDefExt = L"rocket"; - ofn.lpstrFilter = L"ROCKET File (*.rocket)\0*.rocket\0All Files (*.*)\0*.*\0\0"; - ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT; - - if (GetSaveFileNameW(&ofn)) { - SyncDocument *doc = trackView->getDocument(); - if (doc->save(temp)) { - doc->clientSocket.sendSaveCommand(); - setWindowFileName(temp); - doc->fileName = temp; - - mruFileList.insert(temp); - mruFileList.update(); - DrawMenuBar(hwnd); - return true; - } else - error("Failed to save file"); - } - return false; -} - -static bool fileSave() -{ - SyncDocument *doc = trackView->getDocument(); - if (doc->fileName.empty()) - return fileSaveAs(); - - if (!doc->save(doc->fileName)) { - doc->clientSocket.sendSaveCommand(); - error("Failed to save file"); - return false; - } - return true; -} - -static void attemptQuit() -{ - SyncDocument *doc = trackView->getDocument(); - if (doc->modified()) { - UINT res = MessageBox(hwnd, "Save before exit?", mainWindowTitle, MB_YESNOCANCEL | MB_ICONQUESTION); - if ((IDYES == res && fileSave()) || (IDNO == res)) - DestroyWindow(hwnd); - } - else DestroyWindow(hwnd); -} - -static HWND createStatusBar(HINSTANCE hInstance, HWND hpwnd) -{ - HWND hwnd = CreateWindowEx( - 0, // no extended styles - STATUSCLASSNAME, // status bar - (LPCTSTR)NULL, // no text - SBARS_SIZEGRIP | WS_VISIBLE | WS_CHILD, // styles - 0, 0, 0, 0, // x, y, cx, cy - hpwnd, // parent window - NULL, // menu - hInstance, // instance - NULL // window data - ); - - int statwidths[] = { 150, 150 + 32, 150 + 32 * 2, 150 + 32 * 4, 150 + 32 * 6}; - SendMessage(hwnd, SB_SETPARTS, sizeof(statwidths) / sizeof(int), (LPARAM)statwidths); - SendMessage(hwnd, SB_SETTEXT, 0, (LPARAM)"Not connected"); - SendMessage(hwnd, SB_SETTEXT, 1, (LPARAM)"0"); - SendMessage(hwnd, SB_SETTEXT, 2, (LPARAM)"0"); - SendMessage(hwnd, SB_SETTEXT, 3, (LPARAM)"---"); - return hwnd; -} - -static LRESULT CALLBACK mainWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - SyncDocument *doc = trackView ? trackView->getDocument() : NULL; - switch(msg) - { - case WM_CREATE: - { - trackViewWin = trackView->create(hInst, hwnd); - InitCommonControls(); - statusBarWin = createStatusBar(hInst, hwnd); - - if (ERROR_SUCCESS != RegOpenKey(HKEY_CURRENT_USER, keyName, ®ConfigKey)) - { - if (ERROR_SUCCESS != RegCreateKey(HKEY_CURRENT_USER, keyName, ®ConfigKey)) - die("failed to create registry key"); - } - - /* Recent Files menu */ - mruFileList = RecentFiles(findSubMenuContaining(GetMenu(hwnd), ID_RECENTFILES_NORECENTFILES)); - mruFileList.load(regConfigKey); + if (app.arguments().size() > 1) { + if (app.arguments().size() > 2) { + QMessageBox::critical(&mainWindow, NULL, QString("usage: %1 [filename.rocket]").arg(argv[0]), QMessageBox::Ok); + exit(EXIT_FAILURE); } - break; + mainWindow.loadDocument(app.arguments()[1]); + } else + mainWindow.fileNew(); - case WM_CLOSE: - attemptQuit(); - break; - - case WM_DESTROY: - mruFileList.save(regConfigKey); - RegCloseKey(regConfigKey); - regConfigKey = NULL; - PostQuitMessage(0); - break; - - case WM_SIZE: - { - int width = LOWORD(lParam); - int height = HIWORD(lParam); - - RECT statusBarRect; - GetClientRect(statusBarWin, &statusBarRect); - int statusBarHeight = statusBarRect.bottom - statusBarRect.top; - - MoveWindow(trackViewWin, 0, 0, width, height - statusBarHeight, TRUE); - MoveWindow(statusBarWin, 0, height - statusBarHeight, width, statusBarHeight, TRUE); - } - break; - - case WM_SETFOCUS: - SetFocus(trackViewWin); // needed to forward keyboard input - break; - - case WM_SETROWS: - trackView->setRows(int(lParam)); - break; - - case WM_BIASSELECTION: - trackView->editBiasValue(float(lParam)); - break; - - case WM_COMMAND: - switch (LOWORD(wParam)) - { - case ID_FILE_NEW: - fileNew(); - InvalidateRect(trackViewWin, NULL, FALSE); - break; - - case ID_FILE_OPEN: - fileOpen(); - break; - - case ID_FILE_SAVE_AS: - fileSaveAs(); - break; - - case ID_FILE_SAVE: - fileSave(); - break; - - case ID_EDIT_SELECTALL: - trackView->selectAll(); - break; - - case ID_EDIT_SELECTTRACK: - trackView->selectTrack(trackView->getEditTrack()); - break; - - case ID_EDIT_SELECTROW: - trackView->selectRow(trackView->getEditRow()); - break; - - case ID_FILE_REMOTEEXPORT: - doc->clientSocket.sendSaveCommand(); - break; - - case ID_RECENTFILES_FILE1: - case ID_RECENTFILES_FILE2: - case ID_RECENTFILES_FILE3: - case ID_RECENTFILES_FILE4: - case ID_RECENTFILES_FILE5: - { - int index = LOWORD(wParam) - ID_RECENTFILES_FILE1; - std::wstring fileName; - if (mruFileList.getEntry(index, fileName)) - { - loadDocument(fileName); - } - } - break; - - case ID_FILE_EXIT: - attemptQuit(); - break; - - case ID_EDIT_UNDO: SendMessage(trackViewWin, WM_UNDO, 0, 0); break; - case ID_EDIT_REDO: SendMessage(trackViewWin, WM_REDO, 0, 0); break; - case ID_EDIT_COPY: SendMessage(trackViewWin, WM_COPY, 0, 0); break; - case ID_EDIT_CUT: SendMessage(trackViewWin, WM_CUT, 0, 0); break; - case ID_EDIT_PASTE: SendMessage(trackViewWin, WM_PASTE, 0, 0); break; + mainWindow.show(); + app.exec(); - case ID_EDIT_BOOKMARK_PREV: - { - int row = doc->prevRowBookmark(trackView->getEditRow()); - if (row >= 0) - trackView->setEditRow(row); - } - break; - - case ID_EDIT_BOOKMARK_NEXT: - { - int row = doc->nextRowBookmark(trackView->getEditRow()); - if (row >= 0) - trackView->setEditRow(row); - } - break; - - - case ID_EDIT_SETROWS: - { - size_t rows = trackView->getRows(); - INT_PTR result = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_SETROWS), hwnd, (DLGPROC)setRowsDialogProc, (LPARAM)&rows); - if (FAILED(result)) - error("unable to create dialog box"); - } - break; - - case ID_EDIT_BIAS: - { - int initialBias = 0; - INT_PTR result = DialogBoxParam(hInst, MAKEINTRESOURCE(IDD_BIASSELECTION), hwnd, (DLGPROC)biasSelectionDialogProc, (LPARAM)&initialBias); - if (FAILED(result)) - error("unable to create dialog box"); - } - break; - } - break; - - case WM_ROWCHANGED: - { - char temp[256]; - snprintf(temp, 256, "%d", lParam ); - SendMessage(statusBarWin, SB_SETTEXT, 1, (LPARAM)temp); - } - break; - - case WM_TRACKCHANGED: - { - char temp[256]; - snprintf(temp, 256, "%d", lParam); - SendMessage(statusBarWin, SB_SETTEXT, 2, (LPARAM)temp); - } - break; - - case WM_CURRVALDIRTY: - { - char temp[256]; - if (doc->num_tracks > 0) { - const sync_track *t = doc->tracks[doc->getTrackIndexFromPos(trackView->getEditTrack())]; - int row = trackView->getEditRow(); - int idx = key_idx_floor(t, row); - snprintf(temp, 256, "%f", sync_get_val(t, row)); - const char *str = "---"; - if (idx >= 0) { - switch (t->keys[idx].type) { - case KEY_STEP: str = "step"; break; - case KEY_LINEAR: str = "linear"; break; - case KEY_SMOOTH: str = "smooth"; break; - case KEY_RAMP: str = "ramp"; break; - } - } - SendMessage(statusBarWin, SB_SETTEXT, 4, (LPARAM)str); - } else - snprintf(temp, 256, "---"); - SendMessage(statusBarWin, SB_SETTEXT, 3, (LPARAM)temp); - } - break; - - default: - return DefWindowProcW(hwnd, msg, wParam, lParam); - } return 0; } - -static ATOM registerMainWindowClass(HINSTANCE hInstance) -{ - WNDCLASSEXW wc; - - wc.cbSize = sizeof(wc); - wc.style = 0; - wc.lpfnWndProc = mainWindowProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = 0; - wc.hInstance = hInstance; - wc.hIcon = LoadIcon(hInstance, IDI_APPLICATION); - wc.hCursor = LoadCursor(NULL, IDC_ARROW); - wc.hbrBackground = (HBRUSH)0; - wc.lpszMenuName = MAKEINTRESOURCEW(IDR_MENU); - wc.lpszClassName = mainWindowClassName; - wc.hIconSm = wc.hIcon; - - return RegisterClassExW(&wc); -} - -static SOCKET clientConnect(SOCKET serverSocket, sockaddr_in *host) -{ - sockaddr_in hostTemp; - int hostSize = sizeof(sockaddr_in); - SOCKET clientSocket = accept(serverSocket, (sockaddr*)&hostTemp, &hostSize); - if (INVALID_SOCKET == clientSocket) return INVALID_SOCKET; - - const char *expectedGreeting = CLIENT_GREET; - char recievedGreeting[128]; - - recv(clientSocket, recievedGreeting, int(strlen(expectedGreeting)), 0); - - if (strncmp(expectedGreeting, recievedGreeting, strlen(expectedGreeting)) != 0) - { - closesocket(clientSocket); - return INVALID_SOCKET; - } - - const char *greeting = SERVER_GREET; - send(clientSocket, greeting, int(strlen(greeting)), 0); - - if (NULL != host) *host = hostTemp; - return clientSocket; -} - -static size_t clientIndex; -static void processCommand(ClientSocket &sock) -{ - SyncDocument *doc = trackView->getDocument(); - int strLen, serverIndex, newRow; - std::string trackName; - const sync_track *t; - unsigned char cmd = 0; - if (sock.recv((char*)&cmd, 1, 0)) { - switch (cmd) { - case GET_TRACK: - // read data - sock.recv((char *)&strLen, sizeof(int), 0); - strLen = ntohl(strLen); - if (!sock.connected()) - return; - - trackName.resize(strLen); - if (!sock.recv(&trackName[0], strLen, 0)) - return; - - // find track - serverIndex = sync_find_track(doc, - trackName.c_str()); - if (0 > serverIndex) - serverIndex = - int(doc->createTrack(trackName)); - - // setup remap - doc->clientSocket.clientTracks[trackName] = clientIndex++; - - // send key-frames - t = doc->tracks[serverIndex]; - for (int i = 0; i < (int)t->num_keys; ++i) - doc->clientSocket.sendSetKeyCommand(trackName, - t->keys[i]); - - InvalidateRect(trackViewWin, NULL, FALSE); - break; - - case SET_ROW: - sock.recv((char*)&newRow, sizeof(int), 0); - trackView->setEditRow(ntohl(newRow)); - break; - } - } -} - -int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, - LPSTR /*lpCmdLine*/, int /*nShowCmd*/) -{ -#ifdef _DEBUG - _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -/* _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_DEBUG); - _CrtSetReportMode(_CRT_ERROR, _CRTDBG_MODE_DEBUG); - _CrtSetReportMode(_CRT_ASSERT, _CRTDBG_MODE_DEBUG); */ -// _CrtSetBreakAlloc(254); -#endif - - hInst = hInstance; - CoInitialize(NULL); - - WSADATA wsa; - if (0 != WSAStartup(MAKEWORD(2, 0), &wsa)) - die("Failed to init network"); - - SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0); - - struct sockaddr_in sin; - memset(&sin, 0, sizeof sin); - - sin.sin_family = AF_INET; - sin.sin_addr.s_addr = INADDR_ANY; - sin.sin_port = htons(1338); - - if (SOCKET_ERROR == bind(serverSocket, (struct sockaddr *)&sin, - sizeof(sin))) - die("Could not start server"); - - while (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) - ; /* nothing */ - - ATOM mainClass = registerMainWindowClass(hInstance); - ATOM trackViewClass = registerTrackViewWindowClass(hInstance); - if (!mainClass || !trackViewClass) - die("Window Registration Failed!"); - - trackView = new TrackView(); - - hwnd = CreateWindowExW( - 0, - mainWindowClassName, - mainWindowTitleW, - WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN, - CW_USEDEFAULT, CW_USEDEFAULT, // x, y - CW_USEDEFAULT, CW_USEDEFAULT, // width, height - NULL, NULL, hInstance, NULL - ); - - if (NULL == hwnd) - die("Window Creation Failed!"); - - int argc; - LPWSTR *argv = argv = CommandLineToArgvW(GetCommandLineW(), &argc); - if (argv && argc > 1) { - if (argc > 2) { - char prog[MAX_PATH]; - GetModuleFileNameA(NULL, prog, sizeof(prog)); - die("usage: %s [filename.rocket]", prog); - } - loadDocument(argv[1]); - } else - fileNew(); - - HACCEL accel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR)); - - ShowWindow(hwnd, TRUE); - UpdateWindow(hwnd); - - bool done = false; - MSG msg; - bool guiConnected = false; - while (!done) { - SyncDocument *doc = trackView->getDocument(); - if (!doc->clientSocket.connected()) { - SOCKET clientSocket = INVALID_SOCKET; - fd_set fds; - FD_ZERO(&fds); -#pragma warning(suppress: 4127) - FD_SET(serverSocket, &fds); - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 0; - - // look for new clients - if (select(0, &fds, NULL, NULL, &timeout) > 0) - { - SendMessage(statusBarWin, SB_SETTEXT, 0, (LPARAM)"Accepting..."); - sockaddr_in client; - clientSocket = clientConnect(serverSocket, &client); - if (INVALID_SOCKET != clientSocket) - { - char temp[256]; - snprintf(temp, 256, "Connected to %s", inet_ntoa(client.sin_addr)); - SendMessage(statusBarWin, SB_SETTEXT, 0, (LPARAM)temp); - doc->clientSocket = ClientSocket(clientSocket); - clientIndex = 0; - doc->clientSocket.sendPauseCommand(true); - doc->clientSocket.sendSetRowCommand(trackView->getEditRow()); - guiConnected = true; - } - else SendMessage(statusBarWin, SB_SETTEXT, 0, (LPARAM)"Not Connected."); - } - } - - if (doc->clientSocket.connected()) { - ClientSocket &clientSocket = doc->clientSocket; - - // look for new commands - while (clientSocket.pollRead()) - processCommand(clientSocket); - } - - if (!doc->clientSocket.connected() && guiConnected) { - doc->clientSocket.clientPaused = true; - InvalidateRect(trackViewWin, NULL, FALSE); - SendMessage(statusBarWin, SB_SETTEXT, 0, (LPARAM)"Not Connected."); - guiConnected = false; - } - - while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) - { - if (!TranslateAccelerator(hwnd, accel, &msg)) - { - TranslateMessage(&msg); - DispatchMessage(&msg); - if (WM_QUIT == msg.message) done = true; - } - } - Sleep(1); - } - - closesocket(serverSocket); - WSACleanup(); - - delete trackView; - trackView = NULL; - - UnregisterClassW(mainWindowClassName, hInstance); - return int(msg.wParam); -} diff --git a/editor/editor.pro b/editor/editor.pro new file mode 100644 index 0000000..625c254 --- /dev/null +++ b/editor/editor.pro @@ -0,0 +1,28 @@ +TEMPLATE = app +TARGET = editor +DEPENDPATH += . +INCLUDEPATH += . + +QT = core gui xml network + +greaterThan(QT_MAJOR_VERSION, 4) { + QT += widgets +} + +# Input +HEADERS += clientsocket.h \ + mainwindow.h \ + syncdocument.h \ + synctrack.h \ + trackview.h + +SOURCES += clientsocket.cpp \ + editor.cpp \ + mainwindow.cpp \ + syncdocument.cpp \ + trackview.cpp + +RESOURCES += editor.qrc + +RC_FILE = editor.rc +ICON = appicon.icns diff --git a/editor/editor.qrc b/editor/editor.qrc new file mode 100644 index 0000000..8021387 --- /dev/null +++ b/editor/editor.qrc @@ -0,0 +1,5 @@ + + + appicon.ico + + diff --git a/editor/editor.rc b/editor/editor.rc index a80935e..341a8ac 100644 --- a/editor/editor.rc +++ b/editor/editor.rc @@ -1,194 +1 @@ -// Microsoft Visual C++ generated resource script. -// -#include "resource.h" - -#define APSTUDIO_READONLY_SYMBOLS -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 2 resource. -// -#include "afxres.h" - -///////////////////////////////////////////////////////////////////////////// -#undef APSTUDIO_READONLY_SYMBOLS - -///////////////////////////////////////////////////////////////////////////// -// Norwegian (Bokmal) resources - -#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_NOR) -#ifdef _WIN32 -LANGUAGE LANG_NORWEGIAN, SUBLANG_NORWEGIAN_BOKMAL -#pragma code_page(1252) -#endif //_WIN32 - -#ifdef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// TEXTINCLUDE -// - -1 TEXTINCLUDE -BEGIN - "resource.h\0" -END - -2 TEXTINCLUDE -BEGIN - "#include ""afxres.h""\r\n" - "\0" -END - -3 TEXTINCLUDE -BEGIN - "\r\n" - "\0" -END - -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Accelerator -// - -IDR_ACCELERATOR ACCELERATORS -BEGIN - "B", ID_EDIT_BIAS, VIRTKEY, CONTROL, NOINVERT - "C", ID_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT - "X", ID_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT - "V", ID_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT - "Z", ID_EDIT_REDO, VIRTKEY, SHIFT, CONTROL, NOINVERT - "Z", ID_EDIT_UNDO, VIRTKEY, CONTROL, NOINVERT - "N", ID_FILE_NEW, VIRTKEY, CONTROL, NOINVERT - "O", ID_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT - "S", ID_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT - "E", ID_FILE_REMOTEEXPORT, VIRTKEY, CONTROL, NOINVERT - "A", ID_EDIT_SELECT_ALL, VIRTKEY, CONTROL, NOINVERT - "T", ID_EDIT_SELECTTRACK, VIRTKEY, CONTROL, NOINVERT - "R", ID_EDIT_SETROWS, VIRTKEY, CONTROL, NOINVERT - VK_PRIOR, ID_EDIT_BOOKMARK_PREV, VIRTKEY, ALT, NOINVERT - VK_NEXT, ID_EDIT_BOOKMARK_NEXT, VIRTKEY, ALT, NOINVERT -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Menu -// - -IDR_MENU MENU -BEGIN - POPUP "&File" - BEGIN - MENUITEM "New\tCtrl+N", ID_FILE_NEW - MENUITEM "&Open\tCtrl+O", ID_FILE_OPEN - MENUITEM "&Save\tCtrl+S", ID_FILE_SAVE - MENUITEM "Save &As", ID_FILE_SAVE_AS - MENUITEM SEPARATOR - MENUITEM "Remote &Export\tCtrl+E", ID_FILE_REMOTEEXPORT - POPUP "Recent &Files" - BEGIN - MENUITEM "No recent files", ID_RECENTFILES_NORECENTFILES, GRAYED - END - MENUITEM SEPARATOR - MENUITEM "E&xit", ID_FILE_EXIT - END - POPUP "&Edit" - BEGIN - MENUITEM "Undo\tCtrl+Z", ID_EDIT_UNDO - MENUITEM "Redo\tCtrl+Shift+Z", ID_EDIT_REDO - MENUITEM SEPARATOR - MENUITEM "&Copy\tCtrl+C", ID_EDIT_COPY - MENUITEM "Cu&t\tCtrl+X", ID_EDIT_CUT - MENUITEM "&Paste\tCtrl+V", ID_EDIT_PASTE - MENUITEM "Clear\tDel", ID_EDIT_CLEAR - MENUITEM SEPARATOR - MENUITEM "Select All\tCtrl+A", ID_EDIT_SELECTALL - MENUITEM "Select Track\tCtrl+T", ID_EDIT_SELECTTRACK - MENUITEM "Select Row", ID_EDIT_SELECTROW - MENUITEM SEPARATOR - MENUITEM "&Bias Selection\tCtrl+B", ID_EDIT_BIAS - MENUITEM SEPARATOR - MENUITEM "Set Rows\tCtrl+R", ID_EDIT_SETROWS - MENUITEM SEPARATOR - MENUITEM "Previous Bookmark\tAlt+PgDn", ID_EDIT_BOOKMARK_PREV - MENUITEM "Next Bookmark\tAlt+PgUp", ID_EDIT_BOOKMARK_NEXT - END -END - - -///////////////////////////////////////////////////////////////////////////// -// -// Dialog -// - -IDD_SETROWS DIALOGEX 0, 0, 129, 27 -STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Set Rows" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - EDITTEXT IDC_SETROWS_EDIT,7,6,59,12,ES_AUTOHSCROLL | ES_NUMBER - DEFPUSHBUTTON "OK",IDOK,72,6,50,14 -END - -IDD_BIASSELECTION DIALOGEX 0, 0, 129, 27 -STYLE DS_SETFONT | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "Bias Selection" -FONT 8, "MS Shell Dlg", 400, 0, 0x1 -BEGIN - EDITTEXT IDC_BIASSELECTION_EDIT,7,6,59,12,ES_AUTOHSCROLL | ES_NUMBER - DEFPUSHBUTTON "OK",IDOK,72,6,50,14 -END - - -///////////////////////////////////////////////////////////////////////////// -// -// DESIGNINFO -// - -#ifdef APSTUDIO_INVOKED -GUIDELINES DESIGNINFO -BEGIN - IDD_SETROWS, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 122 - TOPMARGIN, 7 - BOTTOMMARGIN, 20 - END - - IDD_BIASSELECTION, DIALOG - BEGIN - LEFTMARGIN, 7 - RIGHTMARGIN, 122 - TOPMARGIN, 7 - BOTTOMMARGIN, 20 - END -END -#endif // APSTUDIO_INVOKED - - -///////////////////////////////////////////////////////////////////////////// -// -// Icon -// - -// Icon with lowest ID value placed first to ensure application icon -// remains consistent on all systems. IDI_APPLICATION ICON "appicon.ico" -#endif // Norwegian (Bokmal) resources -///////////////////////////////////////////////////////////////////////////// - - - -#ifndef APSTUDIO_INVOKED -///////////////////////////////////////////////////////////////////////////// -// -// Generated from the TEXTINCLUDE 3 resource. -// - - -///////////////////////////////////////////////////////////////////////////// -#endif // not APSTUDIO_INVOKED - diff --git a/editor/editor.vcproj b/editor/editor.vcproj deleted file mode 100644 index e4a8135..0000000 --- a/editor/editor.vcproj +++ /dev/null @@ -1,269 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/editor/mainwindow.cpp b/editor/mainwindow.cpp new file mode 100644 index 0000000..aa1cfc8 --- /dev/null +++ b/editor/mainwindow.cpp @@ -0,0 +1,578 @@ +#include "mainwindow.h" +#include "trackview.h" +#include "syncdocument.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow(QTcpServer *serverSocket) : + QMainWindow(), + serverSocket(serverSocket), + clientIndex(0) +{ + trackView = new TrackView(this); + setCentralWidget(trackView); + + connect(trackView, SIGNAL(posChanged(int, int)), + this, SLOT(onPosChanged(int, int))); + connect(trackView, SIGNAL(pauseChanged(bool)), + &clientSocket, SLOT(onPauseChanged(bool))); + connect(trackView, SIGNAL(currValDirty()), + this, SLOT(onCurrValDirty())); + + createMenuBar(); + createStatusBar(); + + connect(serverSocket, SIGNAL(newConnection()), + this, SLOT(onNewConnection())); +} + +void MainWindow::showEvent(QShowEvent *event) +{ + QMainWindow::showEvent(event); + + // workaround for QTBUG-16507 + QString filePath = windowFilePath(); + setWindowFilePath(filePath + "foo"); + setWindowFilePath(filePath); +} + + +void MainWindow::createMenuBar() +{ + fileMenu = menuBar()->addMenu("&File"); + fileMenu->addAction(QIcon::fromTheme("document-new"), "New", this, SLOT(fileNew()), QKeySequence::New); + fileMenu->addAction(QIcon::fromTheme("document-open"), "&Open", this, SLOT(fileOpen()), QKeySequence::Open); + fileMenu->addAction(QIcon::fromTheme("document-save"), "&Save", this, SLOT(fileSave()), QKeySequence::Save); + fileMenu->addAction(QIcon::fromTheme("document-save-as"),"Save &As", this, SLOT(fileSaveAs()), QKeySequence::SaveAs); + fileMenu->addSeparator(); + fileMenu->addAction("Remote &Export", this, SLOT(fileRemoteExport()), Qt::CTRL + Qt::Key_E); + recentFilesMenu = fileMenu->addMenu(QIcon::fromTheme("document-open-recent"), "Recent &Files"); + for (int i = 0; i < 5; ++i) { + recentFileActions[i] = recentFilesMenu->addAction(QIcon::fromTheme("document-open-recent"), ""); + recentFileActions[i]->setVisible(false); + connect(recentFileActions[i], SIGNAL(triggered()), + this, SLOT(openRecentFile())); + } + updateRecentFiles(); + fileMenu->addSeparator(); + fileMenu->addAction(QIcon::fromTheme("application-exit"), "E&xit", this, SLOT(fileQuit()), QKeySequence::Quit); + + editMenu = menuBar()->addMenu("&Edit"); + editMenu->addAction(QIcon::fromTheme("edit-undo"), "Undo", trackView, SLOT(editUndo()), QKeySequence::Undo); + editMenu->addAction(QIcon::fromTheme("edit-redo"), "Redo", trackView, SLOT(editRedo()), QKeySequence::Redo); + editMenu->addSeparator(); + editMenu->addAction(QIcon::fromTheme("edit-copy"), "&Copy", trackView, SLOT(editCopy()), QKeySequence::Copy); + editMenu->addAction(QIcon::fromTheme("edit-cut"), "Cu&t", trackView, SLOT(editCut()), QKeySequence::Cut); + editMenu->addAction(QIcon::fromTheme("edit-paste"), "&Paste", trackView, SLOT(editPaste()), QKeySequence::Paste); + editMenu->addAction(QIcon::fromTheme("edit-clear"), "Clear", trackView, SLOT(editClear()), QKeySequence::Delete); + editMenu->addSeparator(); + editMenu->addAction(QIcon::fromTheme("edit-select-all"), "Select All", trackView, SLOT(selectAll()), QKeySequence::SelectAll); + editMenu->addAction("Select Track", trackView, SLOT(selectTrack()), Qt::CTRL + Qt::Key_T); + editMenu->addAction("Select Row", trackView, SLOT(selectRow())); + editMenu->addSeparator(); + editMenu->addAction("Bias Selection", this, SLOT(editBiasSelection()), Qt::CTRL + Qt::Key_B); + editMenu->addSeparator(); + editMenu->addAction("Set Rows", this, SLOT(editSetRows()), Qt::CTRL + Qt::Key_R); + editMenu->addSeparator(); + editMenu->addAction("Previous Bookmark", this, SLOT(editPreviousBookmark()), Qt::ALT + Qt::Key_PageUp); + editMenu->addAction("Next Bookmark", this, SLOT(editNextBookmark()), Qt::ALT + Qt::Key_PageDown); +} + +void MainWindow::createStatusBar() +{ + statusPos = new QLabel; + statusValue = new QLabel; + statusKeyType = new QLabel; + + statusBar()->addPermanentWidget(statusPos); + statusBar()->addPermanentWidget(statusValue); + statusBar()->addPermanentWidget(statusKeyType); + + statusBar()->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); + + setStatusText("Not connected"); + setStatusPosition(0, 0); + setStatusValue(0.0f, false); + setStatusKeyType(SyncTrack::TrackKey::STEP, false); +} + +static QStringList getRecentFiles() +{ +#ifdef Q_OS_WIN32 + QSettings settings("HKEY_CURRENT_USER\\Software\\GNU Rocket", + QSettings::NativeFormat); +#else + QSettings settings; +#endif + QStringList list; + for (int i = 0; i < 5; ++i) { + QVariant string = settings.value(QString("RecentFile%1").arg(i)); + if (string.isValid()) + list.push_back(string.toString()); + } + return list; +} + +static void setRecentFiles(const QStringList &files) +{ +#ifdef Q_OS_WIN32 + QSettings settings("HKEY_CURRENT_USER\\Software\\GNU Rocket", + QSettings::NativeFormat); +#else + QSettings settings; +#endif + + for (int i = 0; i < files.size(); ++i) + settings.setValue(QString("RecentFile%1").arg(i), files[i]); + + // remove keys not in the list + for (int i = files.size(); ;++i) { + QString key = QString("RecentFile%1").arg(i); + + if (!settings.contains(key)) + break; + + settings.remove(key); + } +} + +void MainWindow::updateRecentFiles() +{ + QStringList files = getRecentFiles(); + + if (!files.size()) { + recentFilesMenu->setEnabled(false); + return; + } + + Q_ASSERT(files.size() <= 5); + for (int i = 0; i < files.size(); ++i) { + QFileInfo info(files[i]); + QString text = QString("&%1 %2").arg(i + 1).arg(info.fileName()); + + recentFileActions[i]->setText(text); + recentFileActions[i]->setData(info.absoluteFilePath()); + recentFileActions[i]->setVisible(true); + } + for (int i = files.size(); i < 5; ++i) + recentFileActions[i]->setVisible(false); + recentFilesMenu->setEnabled(true); +} + +void MainWindow::setCurrentFileName(const QString &fileName) +{ + QFileInfo info(fileName); + + QStringList files = getRecentFiles(); + files.removeAll(info.absoluteFilePath()); + files.prepend(info.absoluteFilePath()); + while (files.size() > 5) + files.removeLast(); + setRecentFiles(files); + + updateRecentFiles(); + setWindowFilePath(fileName); +} + +void MainWindow::setStatusText(const QString &text) +{ + statusBar()->showMessage(text); +} + +void MainWindow::setStatusPosition(int col, int row) +{ + statusPos->setText(QString("Row %1, Col %2").arg(row).arg(col)); +} + +void MainWindow::setStatusValue(double val, bool valid) +{ + if (valid) + statusValue->setText(QString::number(val, 'f', 3)); + else + statusValue->setText("---"); +} + +void MainWindow::setStatusKeyType(SyncTrack::TrackKey::KeyType keyType, bool valid) +{ + if (!valid) { + statusKeyType->setText("---"); + return; + } + + switch (keyType) { + case SyncTrack::TrackKey::STEP: statusKeyType->setText("step"); break; + case SyncTrack::TrackKey::LINEAR: statusKeyType->setText("linear"); break; + case SyncTrack::TrackKey::SMOOTH: statusKeyType->setText("smooth"); break; + case SyncTrack::TrackKey::RAMP: statusKeyType->setText("ramp"); break; + default: Q_ASSERT(false); + } +} + +void MainWindow::setDocument(SyncDocument *newDoc) +{ + SyncDocument *oldDoc = trackView->getDocument(); + + if (oldDoc) + QObject::disconnect(oldDoc, SIGNAL(modifiedChanged(bool)), + this, SLOT(setWindowModified(bool))); + + if (oldDoc && clientSocket.connected()) { + // delete old key frames + for (int i = 0; i < oldDoc->getTrackCount(); ++i) { + SyncTrack *t = oldDoc->getTrack(i); + QMap keyMap = t->getKeyMap(); + QMap::const_iterator it; + for (it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) + t->removeKey(it.key()); + QObject::disconnect(t, SIGNAL(keyFrameChanged(const SyncTrack &, int)), + &clientSocket, SLOT(onKeyFrameChanged(const SyncTrack &, int))); + } + + if (newDoc) { + // add back missing client-tracks + QMap::const_iterator it; + for (it = clientSocket.clientTracks.begin(); it != clientSocket.clientTracks.end(); ++it) { + SyncTrack *t = newDoc->findTrack(it.key()); + if (!t) + newDoc->createTrack(it.key()); + } + + for (int i = 0; i < newDoc->getTrackCount(); ++i) { + SyncTrack *t = newDoc->getTrack(i); + QMap keyMap = t->getKeyMap(); + QMap::const_iterator it; + for (it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) + clientSocket.sendSetKeyCommand(t->name.toUtf8().constData(), *it); + QObject::connect(t, SIGNAL(keyFrameChanged(const SyncTrack &, int)), + &clientSocket, SLOT(onKeyFrameChanged(const SyncTrack &, int))); + } + } + } + + trackView->setDocument(newDoc); + trackView->dirtyCurrentValue(); + trackView->viewport()->update(); + + QObject::connect(newDoc, SIGNAL(modifiedChanged(bool)), + this, SLOT(setWindowModified(bool))); + + if (oldDoc) + delete oldDoc; +} + +void MainWindow::fileNew() +{ + setDocument(new SyncDocument); + setWindowFilePath("Untitled"); +} + +bool MainWindow::loadDocument(const QString &path) +{ + SyncDocument *newDoc = SyncDocument::load(path); + if (newDoc) { + // set new document + setDocument(newDoc); + setCurrentFileName(path); + return true; + } + return false; +} + +void MainWindow::fileOpen() +{ + QString fileName = QFileDialog::getOpenFileName(this, "Open File", "", "ROCKET File (*.rocket);;All Files (*.*)"); + if (fileName.length()) { + loadDocument(fileName); + } +} + +void MainWindow::fileSaveAs() +{ + QString fileName = QFileDialog::getSaveFileName(this, "Save File", "", "ROCKET File (*.rocket);;All Files (*.*)"); + if (fileName.length()) { + SyncDocument *doc = trackView->getDocument(); + if (doc->save(fileName)) { + clientSocket.sendSaveCommand(); + setCurrentFileName(fileName); + doc->fileName = fileName; + } + } +} + +void MainWindow::fileSave() +{ + SyncDocument *doc = trackView->getDocument(); + if (doc->fileName.isEmpty()) + return fileSaveAs(); + + if (!doc->save(doc->fileName)) + clientSocket.sendSaveCommand(); +} + +void MainWindow::fileRemoteExport() +{ + clientSocket.sendSaveCommand(); +} + +void MainWindow::openRecentFile() +{ + QAction *action = qobject_cast(sender()); + if (action) { + QString fileName = action->data().toString(); + if (!loadDocument(fileName)) { + QStringList files = getRecentFiles(); + files.removeAll(fileName); + setRecentFiles(files); + updateRecentFiles(); + } + } +} + +void MainWindow::fileQuit() +{ + SyncDocument *doc = trackView->getDocument(); + if (doc->isModified()) { + QMessageBox::StandardButton res = QMessageBox::question( + this, "GNU Rocket", "Save before exit?", + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + fileSave(); + QApplication::quit(); + } else if (res == QMessageBox::No) + QApplication::quit(); + } + else QApplication::quit(); +} + +void MainWindow::editBiasSelection() +{ + bool ok = false; + float bias = QInputDialog::getDouble(this, "Bias Selection", "", 0, INT_MIN, INT_MAX, 1, &ok); + if (ok) + trackView->editBiasValue(bias); +} + +void MainWindow::editSetRows() +{ + bool ok = false; + int rows = QInputDialog::getInt(this, "Set Rows", "", trackView->getRows(), 0, INT_MAX, 1, &ok); + if (ok) + trackView->setRows(rows); +} + +void MainWindow::editPreviousBookmark() +{ + int row = trackView->getDocument()->prevRowBookmark(trackView->getEditRow()); + if (row >= 0) + trackView->setEditRow(row); +} + +void MainWindow::editNextBookmark() +{ + int row = trackView->getDocument()->nextRowBookmark(trackView->getEditRow()); + if (row >= 0) + trackView->setEditRow(row); +} + +void MainWindow::onPosChanged(int col, int row) +{ + setStatusPosition(col, row); + if (trackView->paused && clientSocket.connected()) + clientSocket.sendSetRowCommand(row); +} + +void MainWindow::onCurrValDirty() +{ + SyncDocument *doc = trackView->getDocument(); + if (doc && doc->getTrackCount() > 0) { + const SyncTrack *t = doc->getTrack(doc->getTrackIndexFromPos(trackView->getEditTrack())); + int row = trackView->getEditRow(); + + setStatusValue(t->getValue(row), true); + + const SyncTrack::TrackKey *k = t->getPrevKeyFrame(row); + if (k) + setStatusKeyType(k->type, true); + else + setStatusKeyType(SyncTrack::TrackKey::STEP, false); + } else { + setStatusValue(0.0f, false); + setStatusKeyType(SyncTrack::TrackKey::STEP, false); + } +} + +void MainWindow::processCommand(ClientSocket &sock) +{ + unsigned char cmd = 0; + if (sock.recv((char*)&cmd, 1)) { + switch (cmd) { + case GET_TRACK: + processGetTrack(sock); + break; + + case SET_ROW: + processSetRow(sock); + break; + } + } +} + +void MainWindow::processGetTrack(ClientSocket &sock) +{ + SyncDocument *doc = trackView->getDocument(); + + // read data + int strLen; + sock.recv((char *)&strLen, sizeof(int)); + strLen = qFromBigEndian((quint32)strLen); + if (!sock.connected()) + return; + + if (!strLen) { + sock.disconnect(); + trackView->update(); + return; + } + + QByteArray trackNameBuffer; + trackNameBuffer.resize(strLen); + if (!sock.recv(trackNameBuffer.data(), strLen)) + return; + + if (trackNameBuffer.contains('\0')) { + sock.disconnect(); + trackView->update(); + return; + } + + QString trackName = QString::fromUtf8(trackNameBuffer); + + // find track + const SyncTrack *t = doc->findTrack(trackName.toUtf8()); + if (!t) + t = doc->createTrack(trackName); + + // hook up signals to slots + QObject::connect(t, SIGNAL(keyFrameChanged(const SyncTrack &, int)), + &clientSocket, SLOT(onKeyFrameChanged(const SyncTrack &, int))); + + // setup remap + clientSocket.clientTracks[trackName] = clientIndex++; + + // send key frames + QMap keyMap = t->getKeyMap(); + QMap::const_iterator it; + for (it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) + clientSocket.sendSetKeyCommand(t->name.toUtf8().constData(), *it); + + trackView->update(); +} + +void MainWindow::processSetRow(ClientSocket &sock) +{ + int newRow; + sock.recv((char*)&newRow, sizeof(int)); + trackView->setEditRow(qToBigEndian((quint32)newRow)); +} + +static TcpSocket *clientConnect(QTcpServer *serverSocket, QHostAddress *host) +{ + QTcpSocket *clientSocket = serverSocket->nextPendingConnection(); + Q_ASSERT(clientSocket != NULL); + + QByteArray line; + + // Read greetings or WebSocket upgrade + // command from the socket + for (;;) { + char ch; + if (!clientSocket->getChar(&ch)) { + // Read failed; wait for data and try again + clientSocket->waitForReadyRead(); + if(!clientSocket->getChar(&ch)) { + clientSocket->close(); + return NULL; + } + } + + if (ch == '\n') + break; + if (ch != '\r') + line.push_back(ch); + if (ch == '!') + break; + } + + TcpSocket *ret = NULL; + if (line.startsWith("GET ")) { + ret = WebSocket::upgradeFromHttp(clientSocket); + line.resize(int(strlen(CLIENT_GREET))); + if (!ret || !ret->recv(line.data(), line.size())) { + clientSocket->close(); + return NULL; + } + } else + ret = new TcpSocket(clientSocket); + + if (!line.startsWith(CLIENT_GREET) || + !ret->send(SERVER_GREET, strlen(SERVER_GREET), true)) { + ret->disconnect(); + return NULL; + } + + if (NULL != host) + *host = clientSocket->peerAddress(); + return ret; +} + +void MainWindow::onReadyRead() +{ + while (clientSocket.pollRead()) + processCommand(clientSocket); +} + +void MainWindow::onNewConnection() +{ + if (!clientSocket.connected()) { + setStatusText("Accepting..."); + QHostAddress client; + TcpSocket *socket = clientConnect(serverSocket, &client); + if (socket) { + setStatusText(QString("Connected to %1").arg(client.toString())); + clientSocket.socket = socket; + connect(socket->socket, SIGNAL(readyRead()), this, SLOT(onReadyRead())); + connect(socket->socket, SIGNAL(disconnected()), this, SLOT(onDisconnected())); + clientIndex = 0; + clientSocket.sendPauseCommand(trackView->paused); + clientSocket.sendSetRowCommand(trackView->getEditRow()); + trackView->connected = true; + } else + setStatusText(QString("Not Connected: %1").arg(serverSocket->errorString())); + } +} + +void MainWindow::onDisconnected() +{ + trackView->paused = true; + clientSocket.disconnect(); + + // disconnect track-signals + SyncDocument *doc = trackView->getDocument(); + for (int i = 0; i < doc->getTrackCount(); ++i) + QObject::disconnect(doc->getTrack(i), SIGNAL(keyFrameChanged(const SyncTrack &, int)), + &clientSocket, SLOT(onKeyFrameChanged(const SyncTrack &, int))); + + trackView->update(); + setStatusText("Not Connected."); + trackView->connected = false; +} diff --git a/editor/mainwindow.h b/editor/mainwindow.h new file mode 100644 index 0000000..a3a49dc --- /dev/null +++ b/editor/mainwindow.h @@ -0,0 +1,73 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include "synctrack.h" +#include "clientsocket.h" + +class QLabel; +class QAction; +class QTcpServer; + +class SyncDocument; +class TrackView; +class ClientSocket; + +class MainWindow : public QMainWindow { + Q_OBJECT + +public: + MainWindow(QTcpServer *serverSocket); + void showEvent(QShowEvent *event); + + void createMenuBar(); + void createStatusBar(); + void updateRecentFiles(); + void setCurrentFileName(const QString &fileName); + bool loadDocument(const QString &path); + void setDocument(SyncDocument *newDoc); + + void processCommand(ClientSocket &sock); + void processGetTrack(ClientSocket &sock); + void processSetRow(ClientSocket &sock); + + void setStatusPosition(int row, int col); + void setStatusText(const QString &text); + void setStatusValue(double val, bool valid); + void setStatusKeyType(SyncTrack::TrackKey::KeyType keyType, bool valid); + + QTcpServer *serverSocket; + ClientSocket clientSocket; + size_t clientIndex; + + TrackView *trackView; + QLabel *statusPos, *statusValue, *statusKeyType; + QMenu *fileMenu, *recentFilesMenu, *editMenu; + QAction *recentFileActions[5]; + +public slots: + void fileNew(); + void fileOpen(); + void fileSave(); + void fileSaveAs(); + void fileRemoteExport(); + void openRecentFile(); + void fileQuit(); + + void editBiasSelection(); + + void editSetRows(); + + void editPreviousBookmark(); + void editNextBookmark(); + + void onPosChanged(int col, int row); + void onCurrValDirty(); + +private slots: + void onReadyRead(); + void onNewConnection(); + void onDisconnected(); +}; + +#endif // MAINWINDOW_H diff --git a/editor/recentfiles.cpp b/editor/recentfiles.cpp deleted file mode 100644 index e1a754f..0000000 --- a/editor/recentfiles.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include "../sync/base.h" -#include "recentfiles.h" -#include "resource.h" -#include - -#define MAX_DIR_LEN 64 - -static bool setRegString(HKEY key, const std::wstring &name, const std::wstring &value) -{ - return ERROR_SUCCESS == RegSetValueExW(key, name.c_str(), 0, REG_SZ, (BYTE *)value.c_str(), (DWORD)(value.size() + 1) * 2); -} - -static bool getRegString(HKEY key, const std::wstring &name, std::wstring &out) -{ - DWORD size = 0; - DWORD type = 0; - if (ERROR_SUCCESS != RegQueryValueExW(key, name.c_str(), 0, &type, (LPBYTE)NULL, &size)) return false; - if (REG_SZ != type) return false; - - assert(!(size % 1)); - out.resize(size / 2); - DWORD ret = RegQueryValueExW(key, name.c_str(), 0, &type, (LPBYTE)&out[0], &size); - while (out.size() > 0 && out[out.size() - 1] == L'\0') out.resize(out.size() - 1); - - assert(ret == ERROR_SUCCESS); - assert(REG_SZ == type); - assert(size == (out.size() + 1) * 2); - - return true; -} - -void RecentFiles::load(HKEY key) -{ - for (size_t i = 0; i < 5; ++i) - { - std::wstring fileName; - if (getRegString(key, getEntryName(i), fileName)) - { - mruList.push_back(fileName); - } - } - - if (mruList.size() > 0) update(); -} - -void RecentFiles::save(HKEY key) -{ - std::list::const_iterator it; - size_t i; - for (i = 0, it = mruList.begin(); it != mruList.end(); ++it, ++i) - { - assert(i <= 5); - setRegString(key, getEntryName(i), *it); - } -} - -void RecentFiles::insert(const std::wstring &fileName) -{ - mruList.remove(fileName); // remove, if present - mruList.push_front(fileName); // add to front - while (mruList.size() > 5) mruList.pop_back(); // remove old entries -} - -void RecentFiles::update() -{ - while (0 != RemoveMenu(mruFileMenu, 0, MF_BYPOSITION)); - std::list::const_iterator it; - size_t i; - for (i = 0, it = mruList.begin(); it != mruList.end(); ++it, ++i) - { - assert(i <= 5); - std::wstring menuEntry = std::wstring(L"&"); - menuEntry += wchar_t(L'1' + i); - menuEntry += L" "; - - wchar_t path[_MAX_PATH], drive[_MAX_DRIVE], dir[_MAX_DIR], fname[_MAX_FNAME], ext[_MAX_EXT]; - _wsplitpath(it->c_str(), drive, dir, fname, ext); - if (wcslen(dir) > MAX_DIR_LEN) wcscpy(dir, L"\\..."); - _wmakepath(path, drive, dir, fname, ext); - menuEntry += std::wstring(path); - - AppendMenuW(mruFileMenu, MF_STRING, ID_RECENTFILES_FILE1 + i, menuEntry.c_str()); - } -} - -bool RecentFiles::getEntry(size_t index, std::wstring &out) const -{ - std::list::const_iterator it; - size_t i; - for (i = 0, it = mruList.begin(); it != mruList.end(); ++it, ++i) - { - if (i == index) - { - out = *it; - return true; - } - } - return false; -} diff --git a/editor/recentfiles.h b/editor/recentfiles.h deleted file mode 100644 index 251f3fe..0000000 --- a/editor/recentfiles.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once -#include -#include - -class RecentFiles -{ -public: - RecentFiles(HMENU menu) : mruFileMenu(menu) { } - - void load(HKEY key); - void save(HKEY key); - void insert(const std::wstring &fileName); - void update(); - - size_t getEntryCount() const - { - return mruList.size(); - } - - bool getEntry(size_t index, std::wstring &out) const; - -private: - static std::wstring getEntryName(size_t i) - { - std::wstring temp = std::wstring(L"RecentFile"); - temp += char(L'0' + i); - return temp; - } - - std::list mruList; - HMENU mruFileMenu; -}; diff --git a/editor/resource.h b/editor/resource.h deleted file mode 100644 index 69770d2..0000000 --- a/editor/resource.h +++ /dev/null @@ -1,40 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by editor.rc -// -#define IDR_ACCELERATOR 101 -#define IDR_MENU 102 -#define IDD_SETROWS 103 -#define IDD_BIASSELECTION 104 -#define IDC_EDIT 1002 -#define IDC_SETROWS_EDIT 1002 -#define IDC_BIASSELECTION_EDIT 1003 -#define ID_FILE 40001 -#define ID_FILE_EXIT 40002 -#define ID_FILE_REMOTEEXPORT 40003 -#define ID_FILE_RECENTFILES 40004 -#define ID_RECENTFILES_NORECENTFILES 40010 -#define ID_RECENTFILES_FILE1 40011 -#define ID_RECENTFILES_FILE2 40012 -#define ID_RECENTFILES_FILE3 40013 -#define ID_RECENTFILES_FILE4 40014 -#define ID_RECENTFILES_FILE5 40015 -#define ID_EDIT 40020 -#define ID_EDIT_SETROWS 40021 -#define ID_EDIT_BIAS 40022 -#define ID_EDIT_SELECTALL 40023 -#define ID_EDIT_SELECTROW 40024 -#define ID_EDIT_SELECTTRACK 40025 -#define ID_EDIT_BOOKMARK_NEXT 40026 -#define ID_EDIT_BOOKMARK_PREV 40027 - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 105 -#define _APS_NEXT_COMMAND_VALUE 40028 -#define _APS_NEXT_CONTROL_VALUE 1004 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/editor/syncdocument.cpp b/editor/syncdocument.cpp index 39e16c7..496373e 100644 --- a/editor/syncdocument.cpp +++ b/editor/syncdocument.cpp @@ -1,167 +1,324 @@ #include "syncdocument.h" -#include +#include +#include +#include +#include + SyncDocument::~SyncDocument() { - sync_data_deinit(this); - clearUndoStack(); - clearRedoStack(); + for (int i = 0; i < tracks.size(); ++i) + delete tracks[i]; } -#import named_guids - -SyncDocument *SyncDocument::load(const std::wstring &fileName) +SyncDocument *SyncDocument::load(const QString &fileName) { SyncDocument *ret = new SyncDocument; ret->fileName = fileName; - MSXML2::IXMLDOMDocumentPtr doc(MSXML2::CLSID_DOMDocument); - try { - doc->load(fileName.c_str()); - MSXML2::IXMLDOMNamedNodeMapPtr attribs = doc->documentElement->Getattributes(); - - MSXML2::IXMLDOMNodePtr rowsParam = attribs->getNamedItem("rows"); - if (rowsParam) { - std::string rowsString = rowsParam->Gettext(); - ret->setRows(atoi(rowsString.c_str())); - } - - MSXML2::IXMLDOMNodeListPtr trackNodes = - doc->documentElement->selectNodes("//track"); - for (int i = 0; i < trackNodes->Getlength(); ++i) { - MSXML2::IXMLDOMNodePtr trackNode = trackNodes->Getitem(i); - MSXML2::IXMLDOMNamedNodeMapPtr attribs = trackNode->Getattributes(); - - std::string name = attribs->getNamedItem("name")->Gettext(); - - // look up track-name, create it if it doesn't exist - int trackIndex = sync_find_track(ret, name.c_str()); - if (0 > trackIndex) trackIndex = int(ret->createTrack(name)); - - MSXML2::IXMLDOMNodeListPtr rowNodes = trackNode->GetchildNodes(); - for (int i = 0; i < rowNodes->Getlength(); ++i) { - MSXML2::IXMLDOMNodePtr keyNode = rowNodes->Getitem(i); - std::string baseName = keyNode->GetbaseName(); - if (baseName == "key") { - MSXML2::IXMLDOMNamedNodeMapPtr rowAttribs = keyNode->Getattributes(); - std::string rowString = rowAttribs->getNamedItem("row")->Gettext(); - std::string valueString = rowAttribs->getNamedItem("value")->Gettext(); - std::string interpolationString = rowAttribs->getNamedItem("interpolation")->Gettext(); - - track_key k; - k.row = atoi(rowString.c_str()); - k.value = float(atof(valueString.c_str())); - k.type = key_type(atoi(interpolationString.c_str())); - - assert(!is_key_frame(ret->tracks[trackIndex], k.row)); - if (sync_set_key(ret->tracks[trackIndex], &k)) - throw std::bad_alloc("sync_set_key"); - } - } - } - - MSXML2::IXMLDOMNodeListPtr bookmarkNodes = - doc->documentElement->selectNodes( - "/sync/bookmarks/bookmark"); - for (int i = 0; i < bookmarkNodes->Getlength(); ++i) { - MSXML2::IXMLDOMNodePtr bookmarkNode = - bookmarkNodes->Getitem(i); - MSXML2::IXMLDOMNamedNodeMapPtr bookmarkAttribs = - bookmarkNode->Getattributes(); - std::string str = - bookmarkAttribs->getNamedItem("row")->Gettext(); - int row = atoi(str.c_str()); - ret->toggleRowBookmark(row); - } - } - catch(_com_error &e) - { - char temp[256]; - _snprintf(temp, 256, "Error loading: %s\n", (const char*)_bstr_t(e.Description())); - MessageBox(NULL, temp, NULL, MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + QFile file(fileName); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::critical(NULL, "Error", file.errorString()); return NULL; } - + + QDomDocument doc; + QString err; + if (!doc.setContent(&file, &err)) { + file.close(); + QMessageBox::critical(NULL, "Error", err); + return NULL; + } + file.close(); + + QDomNamedNodeMap attribs = doc.documentElement().attributes(); + QDomNode rowsParam = attribs.namedItem("rows"); + if (!rowsParam.isNull()) { + QString rowsString = rowsParam.nodeValue(); + ret->setRows(rowsString.toInt()); + } + + QDomNodeList trackNodes = + doc.documentElement().elementsByTagName("track"); + for (int i = 0; i < int(trackNodes.length()); ++i) { + QDomNode trackNode = trackNodes.item(i); + QDomNamedNodeMap attribs = trackNode.attributes(); + + QString name = attribs.namedItem("name").nodeValue(); + + // look up track-name, create it if it doesn't exist + SyncTrack *t = ret->findTrack(name.toUtf8()); + if (!t) + t = ret->createTrack(name.toUtf8().constData()); + + QDomNodeList rowNodes = trackNode.childNodes(); + for (int i = 0; i < int(rowNodes.length()); ++i) { + QDomNode keyNode = rowNodes.item(i); + QString baseName = keyNode.nodeName(); + if (baseName == "key") { + QDomNamedNodeMap rowAttribs = keyNode.attributes(); + QString rowString = rowAttribs.namedItem("row").nodeValue(); + QString valueString = rowAttribs.namedItem("value").nodeValue(); + QString interpolationString = rowAttribs.namedItem("interpolation").nodeValue(); + + SyncTrack::TrackKey k; + k.row = rowString.toInt(); + k.value = valueString.toFloat(); + k.type = SyncTrack::TrackKey::KeyType(interpolationString.toInt()); + + Q_ASSERT(!t->isKeyFrame(k.row)); + t->setKey(k); + } + } + } + + // YUCK: gathers from entire document + QDomNodeList bookmarkNodes = + doc.documentElement().elementsByTagName("bookmark"); + for (int i = 0; i < int(bookmarkNodes.length()); ++i) { + QDomNode bookmarkNode = + bookmarkNodes.item(i); + QDomNamedNodeMap bookmarkAttribs = + bookmarkNode.attributes(); + QString str = + bookmarkAttribs.namedItem("row").nodeValue(); + int row = str.toInt(); + ret->toggleRowBookmark(row); + } + return ret; } -bool SyncDocument::save(const std::wstring &fileName) +bool SyncDocument::save(const QString &fileName) { - MSXML2::IXMLDOMDocumentPtr doc(MSXML2::CLSID_DOMDocument); - try { - MSXML2::IXMLDOMElementPtr rootNode = doc->createElement("sync"); - rootNode->setAttribute("rows", getRows()); - doc->appendChild(rootNode); - rootNode->appendChild(doc->createTextNode("\n\t")); + QDomDocument doc; + QDomElement rootNode = doc.createElement("sync"); + rootNode.setAttribute("rows", int(getRows())); + doc.appendChild(rootNode); - MSXML2::IXMLDOMElementPtr tracksNode = - doc->createElement("tracks"); - for (size_t i = 0; i < num_tracks; ++i) { - const sync_track *t = tracks[trackOrder[i]]; + rootNode.appendChild(doc.createTextNode("\n\t")); + QDomElement tracksNode = + doc.createElement("tracks"); + for (int i = 0; i < getTrackCount(); ++i) { + const SyncTrack *t = getTrack(trackOrder[i]); - MSXML2::IXMLDOMElementPtr trackElem = - doc->createElement("track"); - trackElem->setAttribute("name", t->name); + QDomElement trackElem = + doc.createElement("track"); + trackElem.setAttribute("name", t->name); - for (int i = 0; i < (int)t->num_keys; ++i) { - size_t row = t->keys[i].row; - float value = t->keys[i].value; - char interpolationType = char(t->keys[i].type); + QMap keyMap = t->getKeyMap(); + QMap::const_iterator it; + for (it = keyMap.constBegin(); it != keyMap.constEnd(); ++it) { + int row = it.key(); + float value = it->value; + char interpolationType = char(it->type); - MSXML2::IXMLDOMElementPtr keyElem = - doc->createElement("key"); + QDomElement keyElem = + doc.createElement("key"); - keyElem->setAttribute("row", row); - keyElem->setAttribute("value", value); - keyElem->setAttribute("interpolation", - (int)interpolationType); + keyElem.setAttribute("row", row); + keyElem.setAttribute("value", value); + keyElem.setAttribute("interpolation", + (int)interpolationType); - trackElem->appendChild( - doc->createTextNode("\n\t\t\t")); - trackElem->appendChild(keyElem); - } - if (t->num_keys) - trackElem->appendChild( - doc->createTextNode("\n\t\t")); - - tracksNode->appendChild(doc->createTextNode("\n\t\t")); - tracksNode->appendChild(trackElem); + trackElem.appendChild( + doc.createTextNode("\n\t\t\t")); + trackElem.appendChild(keyElem); } - if (0 != num_tracks) - tracksNode->appendChild(doc->createTextNode("\n\t")); - rootNode->appendChild(tracksNode); - rootNode->appendChild(doc->createTextNode("\n\t")); + if (keyMap.size()) + trackElem.appendChild( + doc.createTextNode("\n\t\t")); - MSXML2::IXMLDOMElementPtr bookmarksNode = - doc->createElement("bookmarks"); - std::set::const_iterator it; - for (it = rowBookmarks.begin(); it != rowBookmarks.end(); ++it) { - MSXML2::IXMLDOMElementPtr bookmarkElem = - doc->createElement("bookmark"); - bookmarkElem->setAttribute("row", *it); - - bookmarksNode->appendChild( - doc->createTextNode("\n\t\t")); - bookmarksNode->appendChild(bookmarkElem); - } - if (0 != rowBookmarks.size()) - bookmarksNode->appendChild( - doc->createTextNode("\n\t")); - rootNode->appendChild(bookmarksNode); - rootNode->appendChild(doc->createTextNode("\n")); - - doc->save(fileName.c_str()); - - savePointDelta = 0; - savePointUnreachable = false; + tracksNode.appendChild(doc.createTextNode("\n\t\t")); + tracksNode.appendChild(trackElem); } - catch(_com_error &e) - { - char temp[256]; - _snprintf(temp, 256, "Error saving: %s\n", (const char*)_bstr_t(e.Description())); - MessageBox(NULL, temp, NULL, MB_OK | MB_ICONERROR | MB_SETFOREGROUND); + if (getTrackCount()) + tracksNode.appendChild(doc.createTextNode("\n\t")); + rootNode.appendChild(tracksNode); + rootNode.appendChild(doc.createTextNode("\n\t")); + + QDomElement bookmarksNode = + doc.createElement("bookmarks"); + QList::const_iterator it; + for (it = rowBookmarks.begin(); it != rowBookmarks.end(); ++it) { + QDomElement bookmarkElem = + doc.createElement("bookmark"); + bookmarkElem.setAttribute("row", *it); + + bookmarksNode.appendChild( + doc.createTextNode("\n\t\t")); + bookmarksNode.appendChild(bookmarkElem); + } + if (0 != rowBookmarks.size()) + bookmarksNode.appendChild( + doc.createTextNode("\n\t")); + rootNode.appendChild(bookmarksNode); + rootNode.appendChild(doc.createTextNode("\n")); + + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) { + QMessageBox::critical(NULL, "Error", file.errorString()); return false; } + QTextStream streamFileOut(&file); + streamFileOut.setCodec("UTF-8"); + streamFileOut << doc.toString(); + streamFileOut.flush(); + file.close(); + + undoStack.setClean(); return true; } +int SyncDocument::getTrackIndexFromPos(int track) const +{ + Q_ASSERT(track < trackOrder.size()); + return trackOrder[track]; +} + +void SyncDocument::swapTrackOrder(int t1, int t2) +{ + Q_ASSERT(t1 < trackOrder.size()); + Q_ASSERT(t2 < trackOrder.size()); + std::swap(trackOrder[t1], trackOrder[t2]); +} + +bool SyncDocument::isRowBookmark(int row) const +{ + QList::const_iterator it = qLowerBound(rowBookmarks.begin(), rowBookmarks.end(), row); + return it != rowBookmarks.end() && *it == row; +} + +void SyncDocument::toggleRowBookmark(int row) +{ + QList::iterator it = qLowerBound(rowBookmarks.begin(), rowBookmarks.end(), row); + if (it == rowBookmarks.end() || *it != row) + rowBookmarks.insert(it, row); + else + rowBookmarks.erase(it); +} + +int SyncDocument::nextRowBookmark(int row) const +{ + QList::const_iterator it = qLowerBound(rowBookmarks.begin(), rowBookmarks.end(), row); + if (it == rowBookmarks.end()) + return -1; + return *it; +} + +int SyncDocument::prevRowBookmark(int row) const +{ + QList::const_iterator it = qLowerBound(rowBookmarks.begin(), rowBookmarks.end(), row); + if (it == rowBookmarks.end()) { + + // this can only really happen if the list is empty + if (it == rowBookmarks.begin()) + return -1; + + // reached the end, pick the last bookmark if it's after the current row + it--; + return *it < row ? *it : -1; + } + + // pick the previous key (if any) + return it != rowBookmarks.begin() ? *(--it) : -1; +} + +class InsertCommand : public QUndoCommand +{ +public: + InsertCommand(SyncTrack *track, const SyncTrack::TrackKey &key, QUndoCommand *parent = 0) : + QUndoCommand("insert", parent), + track(track), + key(key) + {} + + void redo() + { + Q_ASSERT(!track->isKeyFrame(key.row)); + track->setKey(key); + } + + void undo() + { + Q_ASSERT(track->isKeyFrame(key.row)); + track->removeKey(key.row); + } + +private: + SyncTrack *track; + SyncTrack::TrackKey key; +}; + +class DeleteCommand : public QUndoCommand +{ +public: + DeleteCommand(SyncTrack *track, int row, QUndoCommand *parent = 0) : + QUndoCommand("delete", parent), + track(track), + row(row) + {} + + void redo() + { + Q_ASSERT(track->isKeyFrame(row)); + oldKey = track->getKeyFrame(row); + Q_ASSERT(oldKey.row == row); + track->removeKey(row); + } + + void undo() + { + Q_ASSERT(!track->isKeyFrame(row)); + Q_ASSERT(oldKey.row == row); + track->setKey(oldKey); + } + +private: + SyncTrack *track; + int row; + SyncTrack::TrackKey oldKey; +}; + + +class EditCommand : public QUndoCommand +{ +public: + EditCommand(SyncTrack *track, const SyncTrack::TrackKey &key, QUndoCommand *parent = 0) : + QUndoCommand("edit", parent), + track(track), + key(key) + {} + + void redo() + { + Q_ASSERT(track->isKeyFrame(key.row)); + oldKey = track->getKeyFrame(key.row); + Q_ASSERT(key.row == oldKey.row); + track->setKey(key); + } + + void undo() + { + Q_ASSERT(track->isKeyFrame(oldKey.row)); + Q_ASSERT(key.row == oldKey.row); + track->setKey(oldKey); + } + +private: + SyncTrack *track; + SyncTrack::TrackKey oldKey, key; +}; + +void SyncDocument::setKeyFrame(SyncTrack *track, const SyncTrack::TrackKey &key) +{ + if (track->isKeyFrame(key.row)) + undoStack.push(new EditCommand(track, key)); + else + undoStack.push(new InsertCommand(track, key)); +} + +void SyncDocument::deleteKeyFrame(SyncTrack *track, int row) +{ + undoStack.push(new DeleteCommand(track, row)); +} diff --git a/editor/syncdocument.h b/editor/syncdocument.h index bade169..edb71ca 100644 --- a/editor/syncdocument.h +++ b/editor/syncdocument.h @@ -1,324 +1,106 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ +#ifndef SYNCDOCUMENT_H +#define SYNCDOCUMENT_H -#pragma once - -extern "C" { -#include "../sync/data.h" -} - -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "clientsocket.h" +#include "synctrack.h" -class SyncDocument : public sync_data { +class SyncDocument : public QObject { + Q_OBJECT public: SyncDocument() : - rows(128), savePointDelta(0), savePointUnreachable(false) + rows(128) { - this->tracks = NULL; - this->num_tracks = 0; + QObject::connect(&undoStack, SIGNAL(cleanChanged(bool)), + this, SLOT(cleanChanged(bool))); } ~SyncDocument(); - size_t createTrack(const std::string &name) + SyncTrack *createTrack(const QString &name) { - size_t index = sync_create_track(this, name.c_str()); + SyncTrack *t = new SyncTrack(name); + tracks.append(t); + + int index = tracks.size() - 1; trackOrder.push_back(index); - return index; + Q_ASSERT(trackOrder.size() == tracks.size()); + return t; } - class Command + SyncTrack *getTrack(int index) { - public: - virtual ~Command() {} - virtual void exec(SyncDocument *data) = 0; - virtual void undo(SyncDocument *data) = 0; - }; - - class InsertCommand : public Command - { - public: - InsertCommand(int track, const track_key &key) : track(track), key(key) {} - ~InsertCommand() {} - - void exec(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - assert(!is_key_frame(t, key.row)); - if (sync_set_key(t, &key)) - throw std::bad_alloc("sync_set_key"); - data->clientSocket.sendSetKeyCommand(t->name, key); // update clients - } - - void undo(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - assert(is_key_frame(t, key.row)); - if (sync_del_key(t, key.row)) - throw std::bad_alloc("sync_del_key"); - data->clientSocket.sendDeleteKeyCommand(t->name, key.row); // update clients - } - - private: - int track; - track_key key; - }; - - class DeleteCommand : public Command - { - public: - DeleteCommand(int track, int row) : track(track), row(row) {} - ~DeleteCommand() {} - - void exec(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - int idx = sync_find_key(t, row); - assert(idx >= 0); - oldKey = t->keys[idx]; - if (sync_del_key(t, row)) - throw std::bad_alloc("sync_del_key"); - data->clientSocket.sendDeleteKeyCommand(t->name, row); // update clients - } - - void undo(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - assert(!is_key_frame(t, row)); - if (sync_set_key(t, &oldKey)) - throw std::bad_alloc("sync_set_key"); - data->clientSocket.sendSetKeyCommand(t->name, oldKey); // update clients - } - - private: - int track, row; - struct track_key oldKey; - }; - - - class EditCommand : public Command - { - public: - EditCommand(int track, const track_key &key) : track(track), key(key) {} - ~EditCommand() {} - - void exec(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - int idx = sync_find_key(t, key.row); - assert(idx >= 0); - oldKey = t->keys[idx]; - if (sync_set_key(t, &key)) - throw std::bad_alloc("sync_set_key"); - data->clientSocket.sendSetKeyCommand(t->name, key); // update clients - } - - void undo(SyncDocument *data) - { - sync_track *t = data->tracks[track]; - assert(is_key_frame(t, key.row)); - if (sync_set_key(t, &oldKey)) - throw std::bad_alloc("sync_set_key"); - data->clientSocket.sendSetKeyCommand(t->name, oldKey); // update clients - } - - private: - int track; - track_key oldKey, key; - }; - - class MultiCommand : public Command - { - public: - ~MultiCommand() - { - std::list::iterator it; - for (it = commands.begin(); it != commands.end(); ++it) - { - delete *it; - } - commands.clear(); - } - - void addCommand(Command *cmd) - { - commands.push_back(cmd); - } - - size_t getSize() const { return commands.size(); } - - void exec(SyncDocument *data) - { - std::list::iterator it; - for (it = commands.begin(); it != commands.end(); ++it) (*it)->exec(data); - } - - void undo(SyncDocument *data) - { - std::list::reverse_iterator it; - for (it = commands.rbegin(); it != commands.rend(); ++it) (*it)->undo(data); - } - - private: - std::list commands; - }; - - void exec(Command *cmd) - { - undoStack.push(cmd); - cmd->exec(this); - clearRedoStack(); - - if (savePointDelta < 0) savePointUnreachable = true; - savePointDelta++; - } - - bool undo() - { - if (undoStack.size() == 0) return false; - - Command *cmd = undoStack.top(); - undoStack.pop(); - - redoStack.push(cmd); - cmd->undo(this); - - savePointDelta--; - - return true; - } - - bool redo() - { - if (redoStack.size() == 0) return false; - - Command *cmd = redoStack.top(); - redoStack.pop(); - - undoStack.push(cmd); - cmd->exec(this); - - savePointDelta++; - - return true; - } - - void clearUndoStack() - { - while (!undoStack.empty()) - { - Command *cmd = undoStack.top(); - undoStack.pop(); - delete cmd; - } - } - - void clearRedoStack() - { - while (!redoStack.empty()) - { - Command *cmd = redoStack.top(); - redoStack.pop(); - delete cmd; - } - } - - Command *getSetKeyFrameCommand(int track, const track_key &key) - { - sync_track *t = tracks[track]; - SyncDocument::Command *cmd; - if (is_key_frame(t, key.row)) cmd = new EditCommand(track, key); - else cmd = new InsertCommand(track, key); - return cmd; + Q_ASSERT(index >= 0 && index < tracks.size()); + return tracks[index]; } - size_t getTrackOrderCount() const + const SyncTrack *getTrack(int index) const { - return trackOrder.size(); - } - - size_t getTrackIndexFromPos(size_t track) const - { - assert(track < trackOrder.size()); - return trackOrder[track]; + Q_ASSERT(index >= 0 && index < tracks.size()); + return tracks[index]; } - void swapTrackOrder(size_t t1, size_t t2) + SyncTrack *findTrack(const QString &name) { - assert(t1 < trackOrder.size()); - assert(t2 < trackOrder.size()); - std::swap(trackOrder[t1], trackOrder[t2]); + for (int i = 0; i < tracks.size(); ++i) + if (name == tracks[i]->name) + return tracks[i]; + return NULL; } - static SyncDocument *load(const std::wstring &fileName); - bool save(const std::wstring &fileName); - - bool modified() const + int getTrackCount() const { - if (savePointUnreachable) return true; - return 0 != savePointDelta; + Q_ASSERT(trackOrder.size() == tracks.size()); + return tracks.size(); } - bool isRowBookmark(int row) const - { - return !!rowBookmarks.count(row); - } + void undo() { undoStack.undo(); } + void redo() { undoStack.redo(); } + bool isModified() const { return !undoStack.isClean(); } + bool canUndo () const { return undoStack.canUndo(); } + bool canRedo () const { return undoStack.canRedo(); } - void toggleRowBookmark(int row) - { - if (isRowBookmark(row)) - rowBookmarks.erase(row); - else - rowBookmarks.insert(row); - } + void beginMacro(const QString &text) { undoStack.beginMacro(text); } + void setKeyFrame(SyncTrack *track, const SyncTrack::TrackKey &key); + void deleteKeyFrame(SyncTrack *track, int row); + void endMacro() { undoStack.endMacro(); } - ClientSocket clientSocket; + int getTrackIndexFromPos(int track) const; + void swapTrackOrder(int t1, int t2); - size_t getRows() const { return rows; } - void setRows(size_t rows) { this->rows = rows; } + static SyncDocument *load(const QString &fileName); + bool save(const QString &fileName); - std::wstring fileName; + bool isRowBookmark(int row) const; + void toggleRowBookmark(int row); - int nextRowBookmark(int row) const - { - std::set::const_iterator it = rowBookmarks.upper_bound(row); - if (it == rowBookmarks.end()) - return -1; - return *it; - } + int getRows() const { return rows; } + void setRows(int rows) { this->rows = rows; } - int prevRowBookmark(int row) const - { - std::set::const_iterator it = rowBookmarks.lower_bound(row); - if (it == rowBookmarks.end()) { - std::set::const_reverse_iterator it = rowBookmarks.rbegin(); - if (it == rowBookmarks.rend()) - return -1; - return *it; - } else - it--; - if (it == rowBookmarks.end()) - return -1; - return *it; - } + QString fileName; + + int nextRowBookmark(int row) const; + int prevRowBookmark(int row) const; private: - std::set rowBookmarks; - std::vector trackOrder; - size_t rows; + QList tracks; + QList rowBookmarks; + QVector trackOrder; + int rows; - // undo / redo functionality - std::stack undoStack; - std::stack redoStack; - int savePointDelta; // how many undos must be done to get to the last saved state - bool savePointUnreachable; // is the save-point reachable? + QUndoStack undoStack; +signals: + void modifiedChanged(bool modified); + +private slots: + void cleanChanged(bool clean) { emit modifiedChanged(!clean); } }; + +#endif // !defined(SYNCDOCUMENT_H) diff --git a/editor/synctrack.h b/editor/synctrack.h new file mode 100644 index 0000000..03c8c3e --- /dev/null +++ b/editor/synctrack.h @@ -0,0 +1,152 @@ +#ifndef SYNCTRACK_H +#define SYNCTRACK_H + +#include +#include + +class SyncTrack : public QObject { + Q_OBJECT +public: + SyncTrack(const QString &name) : + name(name) + { + } + + struct TrackKey { + int row; + float value; + enum KeyType { + STEP, /* stay constant */ + LINEAR, /* lerp to the next value */ + SMOOTH, /* smooth curve to the next value */ + RAMP, /* ramp up */ + KEY_TYPE_COUNT + } type; + }; + + void setKey(const TrackKey &key) + { + keys[key.row] = key; + emit keyFrameChanged(*this, key.row); + } + + void removeKey(int row) + { + Q_ASSERT(keys.find(row) != keys.end()); + keys.remove(row); + emit keyFrameChanged(*this, row); + } + + bool isKeyFrame(int row) const + { + QMap::const_iterator it = keys.lowerBound(row); + return it != keys.end() && it.key() == row; + } + + TrackKey getKeyFrame(int row) const + { + Q_ASSERT(isKeyFrame(row)); + QMap::const_iterator it = keys.lowerBound(row); + return it.value(); + } + + const TrackKey *getPrevKeyFrame(int row) const + { + QMap::const_iterator it = keys.lowerBound(row); + if (it != keys.constBegin() && (it == keys.constEnd() || it.key() != row)) + --it; + + if (it == keys.constEnd() || it.key() > row) + return NULL; + + return &it.value(); + } + + const TrackKey *getNextKeyFrame(int row) const + { + QMap::const_iterator it = keys.lowerBound(row); + + if (it == keys.constEnd() || it.key() < row) + return NULL; + + return &it.value(); + } + + static void getPolynomial(float coeffs[4], const TrackKey *key) + { + coeffs[0] = key->value; + switch (key->type) { + case TrackKey::STEP: + coeffs[1] = coeffs[2] = coeffs[3] = 0.0f; + break; + + case TrackKey::LINEAR: + coeffs[1] = 1.0f; + coeffs[2] = coeffs[3] = 0.0f; + break; + + case TrackKey::SMOOTH: + coeffs[1] = 0.0f; + coeffs[2] = 3.0f; + coeffs[3] = -2.0f; + break; + + case TrackKey::RAMP: + coeffs[1] = coeffs[3] = 0.0f; + coeffs[2] = 1.0f; + break; + + default: + Q_ASSERT(0); + coeffs[0] = 0.0f; + coeffs[1] = 0.0f; + coeffs[2] = 0.0f; + coeffs[3] = 0.0f; + } + } + + double getValue(int row) const + { + if (!keys.size()) + return 0.0; + + const TrackKey *prevKey = getPrevKeyFrame(row); + const TrackKey *nextKey = getNextKeyFrame(row); + + Q_ASSERT(prevKey != NULL || nextKey != NULL); + + if (!prevKey) + return nextKey->value; + if (!nextKey) + return prevKey->value; + if (prevKey == nextKey) + return prevKey->value; + + float coeffs[4]; + getPolynomial(coeffs, prevKey); + + float x = double(row - prevKey->row) / + double(nextKey->row - prevKey->row); + float mag = nextKey->value - prevKey->value; + return coeffs[0] + (coeffs[1] + (coeffs[2] + coeffs[3] * x) * x) * x * mag; + } + + const QMap getKeyMap() const + { + return keys; + } + + bool isActive() const + { + return receivers(SIGNAL(keyFrameChanged(const SyncTrack &, int))) > 0; + } + + QString name; +private: + QMap keys; + +signals: + void keyFrameChanged(const SyncTrack &track, int row); +}; + +#endif // !defined(SYNCTRACK_H) diff --git a/editor/trackview.cpp b/editor/trackview.cpp index db62e39..d40b29a 100644 --- a/editor/trackview.cpp +++ b/editor/trackview.cpp @@ -1,121 +1,120 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - #include "trackview.h" -#include -#include -#include +#include "syncdocument.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include -using std::min; -using std::max; - -static const char *trackViewWindowClassName = "TrackView"; - -static DWORD darken(DWORD col, float amt) +TrackView::TrackView(QWidget *parent) : + QAbstractScrollArea(parent), + paused(true), + connected(false), + windowRows(0), + document(NULL), + dragging(false) { - return RGB(GetRValue(col) * amt, GetGValue(col) * amt, GetBValue(col) * amt); -} +#ifdef Q_OS_WIN + setFont(QFont("Fixedsys")); +#else + QFont font("Monospace"); + font.setStyleHint(QFont::TypeWriter); + setFont(font); +#endif -static int getMaxCharacterWidth(HDC hdc, const char *chars, size_t len) -{ - int maxDigitWidth = 0; - for (size_t i = 0; i < len; ++i) - { - SIZE size; - GetTextExtentPoint32(hdc, &chars[i], 1, &size); - maxDigitWidth = max(maxDigitWidth, int(size.cx)); - } - return maxDigitWidth; -} + lineEdit = new QLineEdit(this); + lineEdit->setAutoFillBackground(true); + lineEdit->hide(); + QDoubleValidator *lineEditValidator = new QDoubleValidator(); + lineEditValidator->setNotation(QDoubleValidator::StandardNotation); + lineEditValidator->setLocale(QLocale::c()); + lineEdit->setValidator(lineEditValidator); -static int getMaxCharacterWidthFromString(HDC hdc, const char *chars) -{ - return getMaxCharacterWidth(hdc, chars, strlen(chars)); -} + QObject::connect(lineEdit, SIGNAL(editingFinished()), this, SLOT(onEditingFinished())); + + viewport()->setAutoFillBackground(false); + + setFocus(Qt::OtherFocusReason); + + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); -TrackView::TrackView() : - document(NULL) -{ scrollPosX = 0; scrollPosY = 0; - windowWidth = -1; - windowHeight = -1; - + editRow = 0; editTrack = 0; selectStartTrack = selectStopTrack = 0; selectStartRow = selectStopRow = 0; - - this->hwnd = NULL; - - bgBaseBrush = GetSysColorBrush(COLOR_WINDOW); - bgDarkBrush = CreateSolidBrush(darken(GetSysColor(COLOR_WINDOW), 0.9f)); - - selectBaseBrush = GetSysColorBrush(COLOR_HIGHLIGHT); - selectDarkBrush = CreateSolidBrush(darken(GetSysColor(COLOR_HIGHLIGHT), 0.9f)); - - rowPen = CreatePen(PS_SOLID, 1, darken(GetSysColor(COLOR_WINDOW), 0.7f)); - rowSelectPen = CreatePen(PS_SOLID, 1, darken(GetSysColor(COLOR_HIGHLIGHT), 0.7f)); - - lerpPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0)); - cosinePen = CreatePen(PS_SOLID, 2, RGB(0, 255, 0)); - rampPen = CreatePen(PS_SOLID, 2, RGB(0, 0, 255)); - editBrush = CreateSolidBrush(RGB(255, 255, 0)); // yellow - bookmarkBrush = CreateSolidBrush(RGB(128, 128, 255)); // red + updateFont(); + updatePalette(); - handCursor = LoadCursor(NULL, IDC_HAND); + handCursor = QCursor(Qt::OpenHandCursor); + setMouseTracking(true); - clipboardFormat = RegisterClipboardFormat("syncdata"); - assert(0 != clipboardFormat); + setupScrollBars(); + QObject::connect(horizontalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onHScroll(int))); + QObject::connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(onVScroll(int))); +} + +void TrackView::updatePalette() +{ + bgBaseBrush = palette().base(); + bgDarkBrush = palette().window(); + + selectBaseBrush = palette().highlight(); + selectDarkBrush = palette().highlight().color().darker(100.0 / 0.9); + + rowPen = QPen(QBrush(palette().base().color().darker(100.0 / 0.7)), 1); + rowSelectPen = QPen(QBrush(palette().highlight().color().darker(100.0 / 0.7)), 1); + + lerpPen = QPen(QBrush(QColor(255, 0, 0)), 2); + cosinePen = QPen(QBrush(QColor(0, 255, 0)), 2); + rampPen = QPen(QBrush(QColor(0, 0, 255)), 2); + + editBrush = Qt::yellow; + bookmarkBrush = QColor(128, 128, 255); +} + +void TrackView::updateFont() +{ + rowHeight = fontMetrics().lineSpacing(); + trackWidth = fontMetrics().width('0') * 16; + + topMarginHeight = rowHeight + 4; + leftMarginWidth = fontMetrics().width('0') * 8; } TrackView::~TrackView() { - DeleteObject(bgBaseBrush); - DeleteObject(bgDarkBrush); - DeleteObject(selectBaseBrush); - DeleteObject(selectDarkBrush); - DeleteObject(lerpPen); - DeleteObject(cosinePen); - DeleteObject(rampPen); - DeleteObject(editBrush); - DeleteObject(bookmarkBrush); - DeleteObject(rowPen); - DeleteObject(rowSelectPen); if (document) delete document; } -void TrackView::setFont(HFONT font) +int TrackView::getLogicalX(int track) const { - this->font = font; - - assert(NULL != hwnd); - HDC hdc = GetDC(hwnd); - SelectObject(hdc, font); - - TEXTMETRIC tm; - GetTextMetrics(hdc, &tm); - - rowHeight = tm.tmHeight + tm.tmExternalLeading; - fontWidth = tm.tmAveCharWidth; - trackWidth = getMaxCharacterWidthFromString(hdc, "0123456789.") * 16; - - topMarginHeight = rowHeight + 4; - leftMarginWidth = getMaxCharacterWidthFromString(hdc, "0123456789abcdefh") * 8; + return track * trackWidth; } -int TrackView::getScreenY(int row) const +int TrackView::getLogicalY(int row) const { - return topMarginHeight + (row * rowHeight) - scrollPosY; + return row * rowHeight; } -int TrackView::getScreenX(size_t track) const +int TrackView::getPhysicalX(int track) const { - return int(leftMarginWidth + (track * trackWidth)) - scrollPosX; + return leftMarginWidth + getLogicalX(track) - scrollPosX; +} + +int TrackView::getPhysicalY(int row) const +{ + return topMarginHeight + getLogicalY(row) - scrollPosY; } inline int divfloor(int a, int b) @@ -125,272 +124,291 @@ inline int divfloor(int a, int b) return a / b; } -int TrackView::getTrackFromX(int x) const +int TrackView::getTrackFromLogicalX(int x) const { - return divfloor(x + scrollPosX - leftMarginWidth, trackWidth); + return divfloor(x, trackWidth); } - -LRESULT TrackView::onCreate() +int TrackView::getTrackFromPhysicalX(int x) const { -// setFont((HFONT)GetStockObject(SYSTEM_FONT)); - setFont((HFONT)GetStockObject(SYSTEM_FIXED_FONT)); - - setupScrollBars(); - return FALSE; + return getTrackFromLogicalX(x - leftMarginWidth + scrollPosX); } -LRESULT TrackView::onPaint() +void TrackView::paintEvent(QPaintEvent *event) { - PAINTSTRUCT ps; - HDC hdc = BeginPaint(hwnd, &ps); - -// SelectObject(hdc, GetStockObject(SYSTEM_FIXED_FONT)); - SelectObject(hdc, font); - paintTracks(hdc, ps.rcPaint); - - EndPaint(hwnd, &ps); - - return FALSE; + QPainter painter(this->viewport()); + paintTopMargin(painter, event->rect()); + paintLeftMargin(painter, event->rect()); + paintTracks(painter, event->rect()); } -void TrackView::paintTopMargin(HDC hdc, RECT rcTracks) +void TrackView::paintTopMargin(QPainter &painter, const QRect &rcTracks) { - RECT fillRect; - RECT topLeftMargin; + QRect topLeftMargin; const SyncDocument *doc = getDocument(); if (NULL == doc) return; - topLeftMargin.top = 0; - topLeftMargin.bottom = topMarginHeight; - topLeftMargin.left = 0; - topLeftMargin.right = leftMarginWidth; - fillRect = topLeftMargin; - DrawEdge(hdc, &fillRect, BDR_RAISEDINNER | BDR_RAISEDOUTER, BF_ADJUST | BF_BOTTOM); - FillRect(hdc, &fillRect, GetSysColorBrush(COLOR_3DFACE)); - - int startTrack = scrollPosX / trackWidth; - int endTrack = min(startTrack + windowTracks + 1, int(getTrackCount())); - - SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); - + topLeftMargin.setTop(-1); + topLeftMargin.setBottom(topMarginHeight - 1); + topLeftMargin.setLeft(-1); + topLeftMargin.setRight(leftMarginWidth + 1); + painter.fillRect(topLeftMargin, palette().button()); + qDrawWinButton(&painter, topLeftMargin, palette()); + + QRect topRightMargin; + topRightMargin.setTop(-1); + topRightMargin.setBottom(topMarginHeight - 1); + topRightMargin.setLeft(getPhysicalX(getTrackCount()) - 1); + topRightMargin.setRight(rcTracks.right() + 1); + painter.fillRect(topRightMargin, palette().button()); + qDrawWinButton(&painter, topRightMargin, palette()); + + int startTrack = qBound(0, getTrackFromPhysicalX(qMax(rcTracks.left(), leftMarginWidth)), int(getTrackCount())); + int endTrack = qBound(0, getTrackFromPhysicalX(rcTracks.right()) + 1, int(getTrackCount())); + for (int track = startTrack; track < endTrack; ++track) { - size_t index = doc->getTrackIndexFromPos(track); - const sync_track *t = doc->tracks[index]; + int index = doc->getTrackIndexFromPos(track); + const SyncTrack *t = doc->getTrack(index); - RECT topMargin; - topMargin.top = 0; - topMargin.bottom = topMarginHeight; - topMargin.left = getScreenX(track); - topMargin.right = topMargin.left + trackWidth; - - if (!RectVisible(hdc, &topMargin)) + QRect topMargin(getPhysicalX(track), 0, trackWidth, topMarginHeight); + if (!rcTracks.intersects(topMargin)) continue; - RECT fillRect = topMargin; + QRect fillRect = topMargin; - HBRUSH bgBrush = GetSysColorBrush(COLOR_3DFACE); + QBrush bgBrush = palette().button(); if (track == editTrack) bgBrush = editBrush; - DrawEdge(hdc, &fillRect, BDR_RAISEDINNER | BDR_RAISEDOUTER, BF_ADJUST | BF_LEFT | BF_RIGHT | BF_BOTTOM); - FillRect(hdc, &fillRect, bgBrush); + painter.fillRect(fillRect, bgBrush); + qDrawWinButton(&painter, fillRect, palette()); - if (!doc->clientSocket.clientTracks.count(t->name)) - SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); + if (!t->isActive()) + painter.setPen(QColor(128, 128, 128)); else - SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); - TextOut(hdc, fillRect.left, 0, t->name, int(strlen(t->name))); + painter.setPen(QColor(0, 0, 0)); + + painter.drawText(fillRect, t->name); } - - RECT topRightMargin; - topRightMargin.top = 0; - topRightMargin.bottom = topMarginHeight; - topRightMargin.left = getScreenX(getTrackCount()); - topRightMargin.right = rcTracks.right; - fillRect = topRightMargin; - DrawEdge(hdc, &fillRect, BDR_RAISEDINNER | BDR_RAISEDOUTER, BF_ADJUST | BF_BOTTOM); - FillRect(hdc, &fillRect, GetSysColorBrush(COLOR_3DFACE)); - + // make sure that the top margin isn't overdrawn by the track-data - ExcludeClipRect(hdc, 0, 0, rcTracks.right, topMarginHeight); + painter.setClipRegion(QRect(0, topMarginHeight, rcTracks.right() + 1, rcTracks.bottom() + 1)); } -void TrackView::paintTracks(HDC hdc, RECT rcTracks) +void TrackView::paintLeftMargin(QPainter &painter, const QRect &rcTracks) { const SyncDocument *doc = getDocument(); if (NULL == doc) return; - - char temp[256]; - + int firstRow = editRow - windowRows / 2 - 1; int lastRow = editRow + windowRows / 2 + 1; - - /* clamp first & last row */ - firstRow = min(max(firstRow, 0), int(getRows()) - 1); - lastRow = min(max(lastRow, 0), int(getRows()) - 1); - - SetBkMode(hdc, TRANSPARENT); - paintTopMargin(hdc, rcTracks); - - for (int row = firstRow; row <= lastRow; ++row) - { - RECT leftMargin; - leftMargin.left = 0; - leftMargin.right = leftMarginWidth; - leftMargin.top = getScreenY(row); - leftMargin.bottom = leftMargin.top + rowHeight; - - if (!RectVisible(hdc, &leftMargin)) continue; - HBRUSH fillBrush; + /* clamp first & last row */ + firstRow = qBound(0, firstRow, int(getRows()) - 1); + lastRow = qBound(0, lastRow, int(getRows()) - 1); + + for (int row = firstRow; row <= lastRow; ++row) { + QRect leftMargin(0, getPhysicalY(row), leftMarginWidth, rowHeight); + if (!rcTracks.intersects(leftMargin)) + continue; + + QBrush fillBrush; if (row == editRow) fillBrush = editBrush; else if (doc->isRowBookmark(row)) fillBrush = bookmarkBrush; else - fillBrush = GetSysColorBrush(COLOR_3DFACE); - FillRect(hdc, &leftMargin, fillBrush); + fillBrush = palette().button(); + painter.fillRect(leftMargin, fillBrush); - DrawEdge(hdc, &leftMargin, BDR_RAISEDINNER | BDR_RAISEDOUTER, BF_RIGHT | BF_BOTTOM | BF_TOP); - if ((row % 8) == 0) SetTextColor(hdc, RGB(0, 0, 0)); - else if ((row % 4) == 0) SetTextColor(hdc, RGB(64, 64, 64)); - else SetTextColor(hdc, RGB(128, 128, 128)); - -/* if ((row % 4) == 0) SetTextColor(hdc, GetSysColor(COLOR_BTNTEXT)); - else SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT)); */ - - snprintf(temp, 256, "%0*Xh", 5, row); - TextOut(hdc, - leftMargin.left, leftMargin.top, - temp, int(strlen(temp)) - ); + qDrawWinButton(&painter, leftMargin, palette()); + if ((row % 8) == 0) painter.setPen(QColor(0, 0, 0)); + else if ((row % 4) == 0) painter.setPen(QColor(64, 64, 64)); + else painter.setPen(QColor(128, 128, 128)); + + painter.drawText(leftMargin, QString("%1").arg(row, 5, 16, QChar('0')).toUpper() + "h"); } - - SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT)); - - int selectLeft = min(selectStartTrack, selectStopTrack); - int selectRight = max(selectStartTrack, selectStopTrack); - int selectTop = min(selectStartRow, selectStopRow); - int selectBottom = max(selectStartRow, selectStopRow); - - int startTrack = scrollPosX / trackWidth; - int endTrack = min(startTrack + windowTracks + 1, int(getTrackCount())); - - for (int track = startTrack; track < endTrack; ++track) { - const sync_track *t = doc->tracks[doc->getTrackIndexFromPos(track)]; - for (int row = firstRow; row <= lastRow; ++row) { - RECT patternDataRect; - patternDataRect.left = getScreenX(track); - patternDataRect.right = patternDataRect.left + trackWidth; - patternDataRect.top = getScreenY(row); - patternDataRect.bottom = patternDataRect.top + rowHeight; - - if (!RectVisible(hdc, &patternDataRect)) - continue; +} - int idx = sync_find_key(t, row); - int fidx = idx >= 0 ? idx : -idx - 2; - key_type interpolationType = (fidx >= 0) ? t->keys[fidx].type : KEY_STEP; - bool selected = (track >= selectLeft && track <= selectRight) && (row >= selectTop && row <= selectBottom); +void TrackView::paintTracks(QPainter &painter, const QRect &rcTracks) +{ + const SyncDocument *doc = getDocument(); + if (NULL == doc) return; - HBRUSH baseBrush = bgBaseBrush; - HBRUSH darkBrush = bgDarkBrush; - if (selected) - { - baseBrush = selectBaseBrush; - darkBrush = selectDarkBrush; - } - - HBRUSH bgBrush = baseBrush; - if (row % 8 == 0) bgBrush = darkBrush; - - RECT fillRect = patternDataRect; - FillRect( hdc, &fillRect, bgBrush); - if (row % 8 == 0) { - MoveToEx(hdc, patternDataRect.left, patternDataRect.top, (LPPOINT) NULL); - if (selected) SelectObject(hdc, rowSelectPen); - else SelectObject(hdc, rowPen); - LineTo(hdc, patternDataRect.right, patternDataRect.top); - } - - switch (interpolationType) { - case KEY_STEP: - break; - case KEY_LINEAR: - SelectObject(hdc, lerpPen); - break; - case KEY_SMOOTH: - SelectObject(hdc, cosinePen); - break; - case KEY_RAMP: - SelectObject(hdc, rampPen); - break; - } - if (interpolationType != KEY_STEP) { - MoveToEx(hdc, patternDataRect.right - 1, patternDataRect.top, (LPPOINT) NULL); - LineTo(hdc, patternDataRect.right - 1, patternDataRect.bottom); - } + int firstRow = editRow - windowRows / 2 - 1; + int lastRow = editRow + windowRows / 2 + 1; - bool drawEditString = false; - if (row == editRow && track == editTrack) { - FrameRect(hdc, &fillRect, (HBRUSH)GetStockObject(BLACK_BRUSH)); - if (editString.size() > 0) - drawEditString = true; - } - /* format the text */ - if (drawEditString) - snprintf(temp, 256, editString.c_str()); - else if (idx < 0) - snprintf(temp, 256, " ---"); - else { - float val = t->keys[idx].value; - snprintf(temp, 256, "% .2f", val); - } + /* clamp first & last row */ + firstRow = qBound(0, firstRow, int(getRows()) - 1); + lastRow = qBound(0, lastRow, int(getRows()) - 1); - COLORREF oldCol; - if (selected) oldCol = SetTextColor(hdc, GetSysColor(COLOR_HIGHLIGHTTEXT)); - TextOut(hdc, - patternDataRect.left, patternDataRect.top, - temp, int(strlen(temp)) - ); - if (selected) SetTextColor(hdc, oldCol); + int startTrack = qBound(0, getTrackFromPhysicalX(qMax(rcTracks.left(), leftMarginWidth)), int(getTrackCount())); + int endTrack = qBound(0, getTrackFromPhysicalX(rcTracks.right()) + 1, int(getTrackCount())); + + QRect topPadding(QPoint(rcTracks.left(), qMax(int(rcTracks.top()), topMarginHeight)), + QPoint(rcTracks.right(), getPhysicalY(0) - 1)); + painter.fillRect(topPadding, palette().dark()); + + QRect bottomPadding(QPoint(rcTracks.left(), getPhysicalY(int(getRows()))), + QPoint(rcTracks.right(), rcTracks.bottom())); + painter.fillRect(bottomPadding, palette().dark()); + + painter.setClipRect(leftMarginWidth, + topMarginHeight, + viewport()->width() - leftMarginWidth, + viewport()->height() - topMarginHeight); + + for (int track = startTrack; track < endTrack; ++track) + paintTrack(painter, rcTracks, track); + + QRect rightMargin(QPoint(getPhysicalX(getTrackCount()), getPhysicalY(0)), + QPoint(rcTracks.right(), getPhysicalY(int(getRows())) - 1)); + painter.fillRect(rightMargin, palette().dark()); +} + +static QPen getInterpolationBrush(SyncTrack::TrackKey::KeyType type) +{ + switch (type) { + case SyncTrack::TrackKey::STEP: + return QPen(); + + case SyncTrack::TrackKey::LINEAR: + return QPen(QBrush(QColor(255, 0, 0)), 2); + + case SyncTrack::TrackKey::SMOOTH: + return QPen(QBrush(QColor(0, 255, 0)), 2); + + case SyncTrack::TrackKey::RAMP: + return QPen(QBrush(QColor(0, 0, 255)), 2); + + default: + Q_ASSERT(false); + return QPen(); + } +} + +void TrackView::paintTrack(QPainter &painter, const QRect &rcTracks, int track) +{ + int firstRow = editRow - windowRows / 2 - 1; + int lastRow = editRow + windowRows / 2 + 1; + + /* clamp first & last row */ + firstRow = qBound(0, firstRow, int(getRows()) - 1); + lastRow = qBound(0, lastRow, int(getRows()) - 1); + + QRect selection = getSelection(); + + const SyncTrack *t = getDocument()->getTrack(getDocument()->getTrackIndexFromPos(track)); + QMap keyMap = t->getKeyMap(); + + for (int row = firstRow; row <= lastRow; ++row) { + QRect patternDataRect(getPhysicalX(track), getPhysicalY(row), trackWidth, rowHeight); + if (!rcTracks.intersects(patternDataRect)) + continue; + + QMap::const_iterator it = keyMap.lowerBound(row); + if (it != keyMap.constBegin() && it.key() != row) + --it; + + SyncTrack::TrackKey::KeyType interpolationType = + (it != keyMap.constEnd() && it.key() <= row) ? + it->type : SyncTrack::TrackKey::STEP; + bool selected = selection.contains(track, row); + + QBrush baseBrush = bgBaseBrush; + QBrush darkBrush = bgDarkBrush; + + if (selected) { + baseBrush = selectBaseBrush; + darkBrush = selectDarkBrush; } + + QBrush bgBrush = (row % 8 == 0) ? darkBrush : baseBrush; + + QRect fillRect = patternDataRect; + painter.fillRect(fillRect, bgBrush); + if (row % 8 == 0) { + painter.setPen(selected ? rowSelectPen : rowPen); + painter.drawLine(patternDataRect.topLeft(), + patternDataRect.topRight()); + } + + if (interpolationType != SyncTrack::TrackKey::STEP) { + painter.setPen(getInterpolationBrush(interpolationType)); + painter.drawLine(patternDataRect.topRight(), + patternDataRect.bottomRight()); + } + + if (row == editRow && track == editTrack) { + painter.setPen(QColor(0, 0, 0)); + painter.drawRect(fillRect.x(), fillRect.y(), fillRect.width() - 1, fillRect.height() - 1); + } + + painter.setPen(selected ? + palette().color(QPalette::HighlightedText) : + palette().color(QPalette::WindowText)); + painter.drawText(patternDataRect, t->isKeyFrame(row) ? + QString::number(t->getKeyFrame(row).value, 'f', 2) : + " ---"); } - - /* right margin */ - { - RECT rightMargin; - rightMargin.top = getScreenY(0); - rightMargin.bottom = getScreenY(int(getRows())); - rightMargin.left = getScreenX(getTrackCount()); - rightMargin.right = rcTracks.right; - FillRect( hdc, &rightMargin, GetSysColorBrush(COLOR_APPWORKSPACE)); +} + +void TrackView::mouseMoveEvent(QMouseEvent *event) +{ + int track = getTrackFromPhysicalX(event->pos().x()); + if (dragging) { + SyncDocument *doc = getDocument(); + const int trackCount = getTrackCount(); + + if (!doc || track < 0 || track >= trackCount) + return; + + if (track > anchorTrack) { + for (int i = anchorTrack; i < track; ++i) + doc->swapTrackOrder(i, i + 1); + anchorTrack = track; + setEditTrack(track); + viewport()->update(); + } else if (track < anchorTrack) { + for (int i = anchorTrack; i > track; --i) + doc->swapTrackOrder(i, i - 1); + anchorTrack = track; + setEditTrack(track); + viewport()->update(); + } + } else { + if (event->pos().y() < topMarginHeight && + track >= 0 && track < int(getTrackCount())) { + setCursor(handCursor); + } else + setCursor(QCursor(Qt::ArrowCursor)); } - - { - RECT bottomPadding; - bottomPadding.top = getScreenY(int(getRows())); - bottomPadding.bottom = rcTracks.bottom; - bottomPadding.left = rcTracks.left; - bottomPadding.right = rcTracks.right; - FillRect(hdc, &bottomPadding, GetSysColorBrush(COLOR_APPWORKSPACE)); +} + +void TrackView::mousePressEvent(QMouseEvent *event) +{ + int track = getTrackFromPhysicalX(event->pos().x()); + if (event->button() == Qt::LeftButton && + event->pos().y() < topMarginHeight && + track >= 0 && track < int(getTrackCount())) { + dragging = true; + anchorTrack = track; } - - { - RECT topPadding; - topPadding.top = max(int(rcTracks.top), topMarginHeight); - topPadding.bottom = getScreenY(0); - topPadding.left = rcTracks.left; - topPadding.right = rcTracks.right; - FillRect(hdc, &topPadding, GetSysColorBrush(COLOR_APPWORKSPACE)); +} + +void TrackView::mouseReleaseEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + dragging = false; + setCursor(QCursor(Qt::ArrowCursor)); + setEditTrack(editTrack); } } struct CopyEntry { int track; - track_key keyFrame; + SyncTrack::TrackKey keyFrame; }; void TrackView::editCopy() @@ -399,69 +417,52 @@ void TrackView::editCopy() if (NULL == doc) return; if (0 == getTrackCount()) { - MessageBeep(~0U); - return; - } - - int selectLeft = min(selectStartTrack, selectStopTrack); - int selectRight = max(selectStartTrack, selectStopTrack); - int selectTop = min(selectStartRow, selectStopRow); - int selectBottom = max(selectStartRow, selectStopRow); - - if (FAILED(OpenClipboard(getWin()))) - { - MessageBox(NULL, "Failed to open clipboard", NULL, MB_OK); + QApplication::beep(); return; } - std::vector copyEntries; - for (int track = selectLeft; track <= selectRight; ++track) { - const size_t trackIndex = doc->getTrackIndexFromPos(track); - const sync_track *t = doc->tracks[trackIndex]; + QRect selection = getSelection(); - for (int row = selectTop; row <= selectBottom; ++row) { - int idx = sync_find_key(t, row); - if (idx >= 0) { + QVector copyEntries; + for (int track = selection.left(); track <= selection.right(); ++track) { + const int trackIndex = doc->getTrackIndexFromPos(track); + const SyncTrack *t = doc->getTrack(trackIndex); + + for (int row = selection.top(); row <= selection.bottom(); ++row) { + if (t->isKeyFrame(row)) { CopyEntry ce; - ce.track = track - selectLeft; - ce.keyFrame = t->keys[idx]; - ce.keyFrame.row -= selectTop; + ce.track = track - selection.left(); + ce.keyFrame = t->getKeyFrame(row); + ce.keyFrame.row -= selection.top(); copyEntries.push_back(ce); } } } - int buffer_width = selectRight - selectLeft + 1; - int buffer_height = selectBottom - selectTop + 1; + int buffer_width = selection.width(); + int buffer_height = selection.height(); size_t buffer_size = copyEntries.size(); - - HGLOBAL hmem = GlobalAlloc(GMEM_MOVEABLE, sizeof(int) * 3 + sizeof(CopyEntry) * copyEntries.size()); - char *clipbuf = (char *)GlobalLock(hmem); - - // copy data - memcpy(clipbuf + 0, &buffer_width, sizeof(int)); - memcpy(clipbuf + sizeof(int), &buffer_height, sizeof(int)); - memcpy(clipbuf + 2 * sizeof(int), &buffer_size, sizeof(size_t)); - if (copyEntries.size() > 0 ) memcpy(clipbuf + 2 * sizeof(int) + sizeof(size_t), ©Entries[0], sizeof(CopyEntry) * copyEntries.size()); - - GlobalUnlock(hmem); - clipbuf = NULL; - - // update clipboard - EmptyClipboard(); - SetClipboardData(clipboardFormat, hmem); - CloseClipboard(); + + QByteArray data; + data.append((char *)&buffer_width, sizeof(int)); + data.append((char *)&buffer_height, sizeof(int)); + data.append((char *)&buffer_size, sizeof(size_t)); + data.append((char *)©Entries[0], sizeof(CopyEntry) * copyEntries.size()); + + QMimeData *mimeData = new QMimeData; + mimeData->setData("application/x-gnu-rocket", data); + QApplication::clipboard()->setMimeData(mimeData); } void TrackView::editCut() { if (0 == getTrackCount()) { - MessageBeep(~0U); + QApplication::beep(); return; } editCopy(); - editDelete(); + editClear(); } void TrackView::editPaste() @@ -470,134 +471,169 @@ void TrackView::editPaste() if (NULL == doc) return; if (0 == getTrackCount()) { - MessageBeep(~0U); + QApplication::beep(); return; } - - if (FAILED(OpenClipboard(getWin()))) - { - MessageBox(NULL, "Failed to open clipboard", NULL, MB_OK); - return; - } - - if (IsClipboardFormatAvailable(clipboardFormat)) - { - HGLOBAL hmem = GetClipboardData(clipboardFormat); - char *clipbuf = (char *)GlobalLock(hmem); + + const QMimeData *mimeData = QApplication::clipboard()->mimeData(); + if (mimeData->hasFormat("application/x-gnu-rocket")) { + const QByteArray mimeDataBuffer = mimeData->data("application/x-gnu-rocket"); + const char *clipbuf = mimeDataBuffer.data(); // copy data int buffer_width, buffer_height, buffer_size; memcpy(&buffer_width, clipbuf + 0, sizeof(int)); memcpy(&buffer_height, clipbuf + sizeof(int), sizeof(int)); - memcpy(&buffer_size, clipbuf + 2 * sizeof(int), sizeof(size_t)); - - SyncDocument::MultiCommand *multiCmd = new SyncDocument::MultiCommand(); + memcpy(&buffer_size, clipbuf + 2 * sizeof(int), sizeof(int)); + + doc->beginMacro("paste"); for (int i = 0; i < buffer_width; ++i) { - size_t trackPos = editTrack + i; + int trackPos = editTrack + i; if (trackPos >= getTrackCount()) continue; - size_t trackIndex = doc->getTrackIndexFromPos(trackPos); - const sync_track *t = doc->tracks[trackIndex]; + int trackIndex = doc->getTrackIndexFromPos(trackPos); + SyncTrack *t = doc->getTrack(trackIndex); for (int j = 0; j < buffer_height; ++j) { int row = editRow + j; - if (is_key_frame(t, row)) - multiCmd->addCommand(new SyncDocument::DeleteCommand(int(trackIndex), row)); + if (t->isKeyFrame(row)) + doc->deleteKeyFrame(t, row); } } - char *src = clipbuf + 2 * sizeof(int) + sizeof(size_t); + const char *src = clipbuf + 2 * sizeof(int) + sizeof(size_t); for (int i = 0; i < buffer_size; ++i) { struct CopyEntry ce; memcpy(&ce, src, sizeof(CopyEntry)); src += sizeof(CopyEntry); - - assert(ce.track >= 0); - assert(ce.track < buffer_width); - assert(ce.keyFrame.row >= 0); - assert(ce.keyFrame.row < buffer_height); - size_t trackPos = editTrack + ce.track; - if (trackPos < getTrackCount()) - { - size_t trackIndex = doc->getTrackIndexFromPos(trackPos); - track_key key = ce.keyFrame; + Q_ASSERT(ce.track >= 0); + Q_ASSERT(ce.track < buffer_width); + Q_ASSERT(ce.keyFrame.row >= 0); + Q_ASSERT(ce.keyFrame.row < buffer_height); + + int trackPos = editTrack + ce.track; + if (trackPos < getTrackCount()) { + int track = doc->getTrackIndexFromPos(trackPos); + SyncTrack::TrackKey key = ce.keyFrame; key.row += editRow; // since we deleted all keyframes in the edit-box already, we can just insert this one. - SyncDocument::Command *cmd = new SyncDocument::InsertCommand(int(trackIndex), key); - multiCmd->addCommand(cmd); + doc->setKeyFrame(doc->getTrack(track), key); } } - doc->exec(multiCmd); - - GlobalUnlock(hmem); + doc->endMacro(); + + invalidateRange(editTrack, editTrack + buffer_width - 1, editRow, editRow + buffer_height - 1); + dirtyCurrentValue(); + clipbuf = NULL; - } - else - MessageBeep(~0U); - - CloseClipboard(); + } else + QApplication::beep(); } +void TrackView::editUndo() +{ + SyncDocument *doc = getDocument(); + if (!doc) + return; + + if (!doc->canUndo()) + QApplication::beep(); + else + doc->undo(); + + // unfortunately, we don't know how much to invalidate... so we'll just invalidate it all. + invalidateAll(); +} + +void TrackView::editRedo() +{ + SyncDocument *doc = getDocument(); + if (!doc) + return; + + if (!doc->canRedo()) + QApplication::beep(); + else + doc->redo(); + + // unfortunately, we don't know how much to invalidate... so we'll just invalidate it all. + invalidateAll(); +} + +void TrackView::selectAll() +{ + selectStartTrack = int(this->getTrackCount()) - 1; + selectStopTrack = editTrack = 0; + selectStartRow = int(this->getRows()) - 1; + selectStopRow = editRow = 0; + invalidateAll(); +} + +void TrackView::selectTrack() +{ + selectStartTrack = selectStopTrack = editTrack; + selectStartRow = int(this->getRows()) - 1; + selectStopRow = editRow = 0; + invalidateAll(); +} + +void TrackView::selectRow() +{ + selectStartTrack = int(this->getTrackCount()) - 1; + selectStopTrack = editTrack = 0; + selectStartRow = selectStopRow = editRow; + invalidateAll(); +} + + void TrackView::setupScrollBars() { - SCROLLINFO si = { sizeof(si) }; - si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL; - si.nPos = editRow; - si.nPage = windowRows; - si.nMin = 0; - si.nMax = int(getRows()) - 1 + windowRows - 1; - SetScrollInfo(hwnd, SB_VERT, &si, TRUE); - - si.fMask = SIF_POS | SIF_PAGE | SIF_RANGE | SIF_DISABLENOSCROLL; - si.nPos = editTrack; - si.nPage = windowTracks; - si.nMin = 0; - si.nMax = int(getTrackCount()) - 1 + windowTracks - 1; - SetScrollInfo(hwnd, SB_HORZ, &si, TRUE); + verticalScrollBar()->setValue(editRow); + verticalScrollBar()->setMinimum(0); + verticalScrollBar()->setMaximum(int(getRows()) - 1); + verticalScrollBar()->setPageStep(windowRows); + + int contentWidth = getTrackCount() * trackWidth; + int viewWidth = qMax(viewport()->width() - leftMarginWidth, 0); + horizontalScrollBar()->setValue(editTrack * trackWidth); + horizontalScrollBar()->setRange(0, contentWidth - viewWidth); + horizontalScrollBar()->setSingleStep(20); + horizontalScrollBar()->setPageStep(viewWidth); } void TrackView::scrollWindow(int scrollX, int scrollY) { - RECT clip; - GetClientRect(hwnd, &clip); - - if (scrollX == 0) clip.top = topMarginHeight; // don't scroll the top margin - if (scrollY == 0) clip.left = leftMarginWidth; // don't scroll the left margin - - ScrollWindowEx( - hwnd, - scrollX, - scrollY, - NULL, - &clip, - NULL, - NULL, - SW_INVALIDATE - ); + QRect clip = viewport()->geometry(); + + if (scrollX == 0) clip.setTop(topMarginHeight); // don't scroll the top margin + if (scrollY == 0) clip.setLeft(leftMarginWidth); // don't scroll the left margin + + viewport()->scroll(scrollX, scrollY, clip); } void TrackView::setScrollPos(int newScrollPosX, int newScrollPosY) { // clamp newscrollPosX - newScrollPosX = max(newScrollPosX, 0); + newScrollPosX = qMax(newScrollPosX, 0); - if (newScrollPosX != scrollPosX || newScrollPosY != scrollPosY) - { - int scrollX = scrollPosX - newScrollPosX; - int scrollY = scrollPosY - newScrollPosY; - + if (newScrollPosX != scrollPosX || newScrollPosY != scrollPosY) { + int deltaX = scrollPosX - newScrollPosX; + int deltaY = scrollPosY - newScrollPosY; + // update scrollPos scrollPosX = newScrollPosX; scrollPosY = newScrollPosY; - - scrollWindow(scrollX, scrollY); + + scrollWindow(deltaX, deltaY); } - setupScrollBars(); + + horizontalScrollBar()->setValue(newScrollPosX); + verticalScrollBar()->setValue(editRow); } -void TrackView::setEditRow(int newEditRow) +void TrackView::setEditRow(int newEditRow, bool selecting) { SyncDocument *doc = getDocument(); if (NULL == doc) return; @@ -606,34 +642,28 @@ void TrackView::setEditRow(int newEditRow) editRow = newEditRow; // clamp to document - editRow = min(max(editRow, 0), int(getRows()) - 1); + editRow = qBound(0, editRow, int(getRows()) - 1); - if (oldEditRow != editRow) - { - if (GetKeyState(VK_SHIFT) < 0) - { + if (oldEditRow != editRow) { + if (selecting) { selectStopRow = editRow; invalidateRange(selectStartTrack, selectStopTrack, oldEditRow, editRow); - } - else - { + } else { invalidateRange(selectStartTrack, selectStopTrack, selectStartRow, selectStopRow); selectStartRow = selectStopRow = editRow; selectStartTrack = selectStopTrack = editTrack; - } if (doc->clientSocket.clientPaused) { - doc->clientSocket.sendSetRowCommand(editRow); } - SendMessage(GetParent(getWin()), WM_ROWCHANGED, 0, editRow); - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); + dirtyPosition(); + dirtyCurrentValue(); } invalidateRow(oldEditRow); invalidateRow(editRow); - setScrollPos(scrollPosX, (editRow * rowHeight) - ((windowHeight - topMarginHeight) / 2) + rowHeight / 2); + setScrollPos(scrollPosX, (editRow * rowHeight) - ((viewport()->height() - topMarginHeight) / 2) + rowHeight / 2); } -void TrackView::setEditTrack(int newEditTrack, bool autoscroll) +void TrackView::setEditTrack(int newEditTrack, bool autoscroll, bool selecting) { if (0 == getTrackCount()) return; @@ -641,12 +671,11 @@ void TrackView::setEditTrack(int newEditTrack, bool autoscroll) editTrack = newEditTrack; // clamp to document - editTrack = max(editTrack, 0); - editTrack = min(editTrack, int(getTrackCount()) - 1); + editTrack = qBound(0, editTrack, int(getTrackCount()) - 1); if (oldEditTrack != editTrack) { - if (GetKeyState(VK_SHIFT) < 0) + if (selecting) { selectStopTrack = editTrack; invalidateRange(oldEditTrack, editTrack, selectStartRow, selectStopRow); @@ -657,142 +686,93 @@ void TrackView::setEditTrack(int newEditTrack, bool autoscroll) selectStartRow = selectStopRow = editRow; selectStartTrack = selectStopTrack = editTrack; } - SendMessage(GetParent(getWin()), WM_TRACKCHANGED, 0, editTrack); - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); + dirtyPosition(); + dirtyCurrentValue(); + invalidateTrack(oldEditTrack); + invalidateTrack(editTrack); } - - invalidateTrack(oldEditTrack); - invalidateTrack(editTrack); - if (autoscroll) { - int firstTrack = scrollPosX / trackWidth; - int lastTrack = firstTrack + windowTracks; + if (autoscroll && viewport()->width() > 0) { + int viewportWidth = viewport()->width() - leftMarginWidth; + int minX = getLogicalX(editTrack); + int maxX = getLogicalX(editTrack + 1); - int newFirstTrack = firstTrack; - if (editTrack >= lastTrack) - newFirstTrack = editTrack - lastTrack + firstTrack + 1; - if (editTrack < firstTrack) - newFirstTrack = editTrack; - setScrollPos(newFirstTrack * trackWidth, scrollPosY); + if (minX < scrollPosX) + setScrollPos(minX, scrollPosY); + else if (maxX > scrollPosX + viewportWidth) + setScrollPos(maxX - viewportWidth, scrollPosY); } else setupScrollBars(); } -static int getScrollPos(HWND hwnd, int bar) -{ - SCROLLINFO si = { sizeof(si), SIF_TRACKPOS }; - GetScrollInfo(hwnd, bar, &si); - return int(si.nTrackPos); -} - -void TrackView::setRows(size_t rows) +void TrackView::setRows(int rows) { SyncDocument *doc = getDocument(); - assert(doc); + Q_ASSERT(doc); doc->setRows(rows); - InvalidateRect(getWin(), NULL, FALSE); - setEditRow(min(editRow, int(rows) - 1)); + viewport()->update(); + setEditRow(qMin(editRow, int(rows) - 1)); } - -LRESULT TrackView::onVScroll(UINT sbCode, int /*newPos*/) +int TrackView::getRows() const { - switch (sbCode) - { - case SB_TOP: - setEditRow(0); - break; - - case SB_LINEUP: - setEditRow(editRow - 1); - break; - - case SB_LINEDOWN: - setEditRow(editRow + 1); - break; - - case SB_PAGEUP: - setEditRow(editRow - windowRows / 2); - break; - - case SB_PAGEDOWN: - setEditRow(editRow + windowRows / 2); - break; - - case SB_THUMBPOSITION: - case SB_THUMBTRACK: - setEditRow(getScrollPos(hwnd, SB_VERT)); - break; - } - - return FALSE; + const SyncDocument *doc = getDocument(); + if (!doc) + return 0; + return doc->getRows(); } -LRESULT TrackView::onHScroll(UINT sbCode, int /*newPos*/) +int TrackView::getTrackCount() const { - switch (sbCode) - { - case SB_LEFT: - setEditTrack(0); - break; - - case SB_RIGHT: - setEditTrack(int(getTrackCount()) - 1); - break; - - case SB_LINELEFT: - setEditTrack(editTrack - 1); - break; - - case SB_LINERIGHT: - setEditTrack(editTrack + 1); - break; - - case SB_PAGELEFT: - setEditTrack(editTrack - windowTracks); - break; - - case SB_PAGEDOWN: - setEditTrack(editTrack + windowTracks); - break; - - case SB_THUMBPOSITION: - case SB_THUMBTRACK: - setEditTrack(getScrollPos(hwnd, SB_HORZ)); - break; - } - - return FALSE; + const SyncDocument *doc = getDocument(); + if (!doc) + return 0; + return doc->getTrackCount(); +}; + +void TrackView::onVScroll(int value) +{ + setEditRow(value); +} + +void TrackView::onHScroll(int value) +{ + setScrollPos(value, scrollPosY); +} + +void TrackView::onEditingFinished() +{ + editEnterValue(); } void TrackView::editEnterValue() { SyncDocument *doc = getDocument(); - if (NULL == doc) return; - - if (int(editString.size()) > 0 && editTrack < int(getTrackCount())) - { - size_t trackIndex = doc->getTrackIndexFromPos(editTrack); - const sync_track *t = doc->tracks[trackIndex]; + if (!doc || !lineEdit->isVisible()) + return; - track_key newKey; - newKey.type = KEY_STEP; + if (lineEdit->text().length() > 0 && editTrack < int(getTrackCount())) { + int track = doc->getTrackIndexFromPos(editTrack); + SyncTrack *t = doc->getTrack(track); + + SyncTrack::TrackKey newKey; + newKey.type = SyncTrack::TrackKey::STEP; newKey.row = editRow; - int idx = sync_find_key(t, editRow); - if (idx >= 0) - newKey = t->keys[idx]; // copy old key - newKey.value = float(atof(editString.c_str())); // modify value - editString.clear(); + if (t->isKeyFrame(editRow)) + newKey = t->getKeyFrame(editRow); // copy old key + QString text = lineEdit->text(); + text.remove(lineEdit->validator()->locale().groupSeparator()); // workaround QTBUG-40456 + newKey.value = lineEdit->validator()->locale().toFloat(text); // modify value - SyncDocument::Command *cmd = doc->getSetKeyFrameCommand(int(trackIndex), newKey); - doc->exec(cmd); + doc->setKeyFrame(t, newKey); - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); - InvalidateRect(getWin(), NULL, FALSE); - } - else - MessageBeep(~0U); + dirtyCurrentValue(); + invalidateTrack(editTrack); + } else + QApplication::beep(); + + lineEdit->hide(); } void TrackView::editToggleInterpolationType() @@ -801,512 +781,271 @@ void TrackView::editToggleInterpolationType() if (NULL == doc) return; if (editTrack < int(getTrackCount())) { - size_t trackIndex = doc->getTrackIndexFromPos(editTrack); - const sync_track *t = doc->tracks[trackIndex]; + int track = doc->getTrackIndexFromPos(editTrack); + SyncTrack *t = doc->getTrack(track); + QMap keyMap = t->getKeyMap(); - int idx = key_idx_floor(t, editRow); - if (idx < 0) { - MessageBeep(~0U); + QMap::const_iterator it = keyMap.lowerBound(editRow); + if (it != keyMap.constBegin() && it.key() != editRow) + --it; + + if (it.key() > editRow || it == keyMap.constEnd()) { + QApplication::beep(); return; } // copy and modify - track_key newKey = t->keys[idx]; - newKey.type = (enum key_type) - ((newKey.type + 1) % KEY_TYPE_COUNT); + SyncTrack::TrackKey newKey = *it; + newKey.type = (SyncTrack::TrackKey::KeyType) + ((newKey.type + 1) % SyncTrack::TrackKey::KEY_TYPE_COUNT); // apply change to data-set - SyncDocument::Command *cmd = doc->getSetKeyFrameCommand(int(trackIndex), newKey); - doc->exec(cmd); + doc->setKeyFrame(t, newKey); // update user interface - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); - InvalidateRect(getWin(), NULL, FALSE); + dirtyCurrentValue(); + invalidateTrack(editTrack); } else - MessageBeep(~0U); + QApplication::beep(); } -void TrackView::editDelete() +void TrackView::editClear() { SyncDocument *doc = getDocument(); if (NULL == doc) return; - - int selectLeft = min(selectStartTrack, selectStopTrack); - int selectRight = max(selectStartTrack, selectStopTrack); - int selectTop = min(selectStartRow, selectStopRow); - int selectBottom = max(selectStartRow, selectStopRow); - - if (0 == getTrackCount()) return; - assert(selectRight < int(getTrackCount())); - - SyncDocument::MultiCommand *multiCmd = new SyncDocument::MultiCommand(); - for (int track = selectLeft; track <= selectRight; ++track) { - size_t trackIndex = doc->getTrackIndexFromPos(track); - const sync_track *t = doc->tracks[trackIndex]; - for (int row = selectTop; row <= selectBottom; ++row) { - if (is_key_frame(t, row)) { - SyncDocument::Command *cmd = new SyncDocument::DeleteCommand(int(trackIndex), row); - multiCmd->addCommand(cmd); - } + QRect selection = getSelection(); + + if (0 == getTrackCount()) return; + Q_ASSERT(selection.right() < int(getTrackCount())); + + doc->beginMacro("clear"); + for (int track = selection.left(); track <= selection.right(); ++track) { + int trackIndex = doc->getTrackIndexFromPos(track); + SyncTrack *t = doc->getTrack(trackIndex); + + for (int row = selection.top(); row <= selection.bottom(); ++row) { + if (t->isKeyFrame(row)) + doc->deleteKeyFrame(t, row); } } - - if (0 == multiCmd->getSize()) { - MessageBeep(~0U); - delete multiCmd; - } - else - { - doc->exec(multiCmd); - - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); - InvalidateRect(getWin(), NULL, FALSE); - } + + doc->endMacro(); + dirtyCurrentValue(); + invalidateRange(selection.left(), selection.right(), selection.top(), selection.bottom()); } void TrackView::editBiasValue(float amount) { SyncDocument *doc = getDocument(); if (NULL == doc) return; - - int selectLeft = min(selectStartTrack, selectStopTrack); - int selectRight = max(selectStartTrack, selectStopTrack); - int selectTop = min(selectStartRow, selectStopRow); - int selectBottom = max(selectStartRow, selectStopRow); - + if (0 == getTrackCount()) { - MessageBeep(~0U); + QApplication::beep(); return; } - - SyncDocument::MultiCommand *multiCmd = new SyncDocument::MultiCommand(); - for (int track = selectLeft; track <= selectRight; ++track) { - assert(track < int(getTrackCount())); - size_t trackIndex = doc->getTrackIndexFromPos(track); - const sync_track *t = doc->tracks[trackIndex]; - for (int row = selectTop; row <= selectBottom; ++row) { - int idx = sync_find_key(t, row); - if (idx >= 0) { - struct track_key k = t->keys[idx]; // copy old key + QRect selection = getSelection(); + + doc->beginMacro("bias"); + for (int track = selection.left(); track <= selection.right(); ++track) { + Q_ASSERT(track < int(getTrackCount())); + int trackIndex = doc->getTrackIndexFromPos(track); + SyncTrack *t = doc->getTrack(trackIndex); + + for (int row = selection.top(); row <= selection.bottom(); ++row) { + if (t->isKeyFrame(row)) { + SyncTrack::TrackKey k = t->getKeyFrame(row); // copy old key k.value += amount; // modify value // add sub-command - SyncDocument::Command *cmd = doc->getSetKeyFrameCommand(int(trackIndex), k); - multiCmd->addCommand(cmd); + doc->setKeyFrame(t, k); } } } - - if (0 == multiCmd->getSize()) { - MessageBeep(~0U); - delete multiCmd; - } - else - { - doc->exec(multiCmd); - - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); - invalidateRange(selectLeft, selectRight, selectTop, selectBottom); - } + doc->endMacro(); + + dirtyCurrentValue(); + invalidateRange(selection.left(), selection.right(), selection.top(), selection.bottom()); } -LRESULT TrackView::onKeyDown(UINT keyCode, UINT /*flags*/) +void TrackView::keyPressEvent(QKeyEvent *event) { SyncDocument *doc = getDocument(); - if (NULL == doc) return FALSE; + if (NULL == doc) return; - if (!editString.empty()) - { - switch(keyCode) - { - case VK_UP: - case VK_DOWN: - case VK_LEFT: - case VK_RIGHT: - case VK_PRIOR: - case VK_NEXT: - case VK_HOME: - case VK_END: + if (paused && lineEdit->isVisible()) { + switch (event->key()) { + case Qt::Key_Up: + case Qt::Key_Down: + case Qt::Key_Left: + case Qt::Key_Right: + case Qt::Key_PageUp: + case Qt::Key_PageDown: + case Qt::Key_Home: + case Qt::Key_End: + case Qt::Key_Space: editEnterValue(); } } - - if (editString.empty()) - { - switch (keyCode) - { - case VK_LEFT: - if (GetKeyState(VK_CONTROL) < 0) { + + bool shiftDown = (event->modifiers() & Qt::ShiftModifier) != 0; + bool ctrlDown = (event->modifiers() & Qt::ControlModifier) != 0; + bool selecting = shiftDown; + + if (lineEdit->isHidden()) { + switch (event->key()) { + case Qt::Key_Backtab: + ctrlDown = false; + selecting = false; + // FALLTHROUGH + case Qt::Key_Left: + if (ctrlDown) { if (0 < editTrack) doc->swapTrackOrder(editTrack, editTrack - 1); else - MessageBeep(~0U); + QApplication::beep(); } if (0 != getTrackCount()) - setEditTrack(editTrack - 1); + setEditTrack(editTrack - 1, true, selecting); else - MessageBeep(~0U); - break; - - case VK_RIGHT: - if (GetKeyState(VK_CONTROL) < 0) { + QApplication::beep(); + return; + + case Qt::Key_Tab: + ctrlDown = false; + selecting = false; + // FALLTHROUGH + case Qt::Key_Right: + if (ctrlDown) { if (int(getTrackCount()) > editTrack + 1) doc->swapTrackOrder(editTrack, editTrack + 1); else - MessageBeep(~0U); + QApplication::beep(); } if (0 != getTrackCount()) - setEditTrack(editTrack + 1); + setEditTrack(editTrack + 1, true, selecting); else - MessageBeep(~0U); - break; + QApplication::beep(); + return; } } - if (editString.empty() && doc->clientSocket.clientPaused) { - switch (keyCode) { - case VK_UP: - if (GetKeyState(VK_CONTROL) < 0) - { - float bias = 1.0f; - if (GetKeyState(VK_SHIFT) < 0) bias = 0.1f; - if (int(getTrackCount()) > editTrack) editBiasValue(bias); + if (lineEdit->isHidden() && paused) { + switch (event->key()) { + case Qt::Key_Up: + if (ctrlDown) { + if (int(getTrackCount()) > editTrack) + editBiasValue(shiftDown ? 0.1f : 1.0f); else - MessageBeep(~0U); - } - else setEditRow(editRow - 1); - break; - - case VK_DOWN: - if (GetKeyState(VK_CONTROL) < 0) - { - float bias = 1.0f; - if (GetKeyState(VK_SHIFT) < 0) bias = 0.1f; - if (int(getTrackCount()) > editTrack) editBiasValue(-bias); + QApplication::beep(); + } else + setEditRow(editRow - 1, selecting); + return; + + case Qt::Key_Down: + if (ctrlDown) { + if (int(getTrackCount()) > editTrack) + editBiasValue(shiftDown ? -0.1f : -1.0f); else - MessageBeep(~0U); - } - else setEditRow(editRow + 1); - break; - - case VK_PRIOR: - if (GetKeyState(VK_CONTROL) < 0) { - float bias = 10.0f; - if (GetKeyState(VK_SHIFT) < 0) - bias = 100.0f; - editBiasValue(bias); + QApplication::beep(); } else - setEditRow(editRow - 0x10); - break; - - case VK_NEXT: - if (GetKeyState(VK_CONTROL) < 0) { - float bias = 10.0f; - if (GetKeyState(VK_SHIFT) < 0) - bias = 100.0f; - editBiasValue(-bias); - } else - setEditRow(editRow + 0x10); - break; - - case VK_HOME: - if (GetKeyState(VK_CONTROL) < 0) setEditTrack(0); - else setEditRow(0); - break; - - case VK_END: - if (GetKeyState(VK_CONTROL) < 0) setEditTrack(int(getTrackCount()) - 1); - else setEditRow(int(getRows()) - 1); - break; + setEditRow(editRow + 1, selecting); + return; + + case Qt::Key_PageUp: + if (ctrlDown) + editBiasValue(shiftDown ? 100.0f : 10.0f); + else + setEditRow(editRow - 0x10, selecting); + return; + + case Qt::Key_PageDown: + if (ctrlDown) + editBiasValue(shiftDown ? -100.0f : -10.0f); + else + setEditRow(editRow + 0x10, selecting); + return; + + case Qt::Key_Home: + if (ctrlDown) + setEditTrack(0); + else + setEditRow(0, selecting); + return; + + case Qt::Key_End: + if (ctrlDown) + setEditTrack(int(getTrackCount()) - 1); + else + setEditRow(int(getRows()) - 1, selecting); + return; } } - - switch (keyCode) - { - case VK_RETURN: editEnterValue(); break; - case VK_DELETE: editDelete(); break; - - case VK_BACK: - if (!editString.empty()) - { - editString.resize(editString.size() - 1); - invalidatePos(editTrack, editRow); - } - else - MessageBeep(~0U); - break; - - case VK_CANCEL: - case VK_ESCAPE: - if (!editString.empty()) { + + switch (event->key()) { + case Qt::Key_Delete: editClear(); return; + + case Qt::Key_Cancel: + case Qt::Key_Escape: + if (paused && lineEdit->isVisible()) { // return to old value (i.e don't clear) - editString.clear(); - invalidatePos(editTrack, editRow); - MessageBeep(~0U); + lineEdit->hide(); + QApplication::beep(); } - break; - case VK_SPACE: - if (!editString.empty()) { - editString.clear(); - invalidatePos(editTrack, editRow); - MessageBeep(~0U); - } - doc->clientSocket.sendPauseCommand( !doc->clientSocket.clientPaused ); - break; - } - return FALSE; -} + return; -LRESULT TrackView::onChar(UINT keyCode, UINT /*flags*/) -{ - switch (char(keyCode)) - { - case '-': - if (editString.empty()) - { - editString.push_back(char(keyCode)); - invalidatePos(editTrack, editRow); + case Qt::Key_Space: + if (connected) { + paused = !paused; + emit pauseChanged(paused); } - break; - case '.': - // only one '.' allowed - if (std::string::npos != editString.find('.')) { - MessageBeep(~0U); - break; - } - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - if (editTrack < int(getTrackCount())) - { - editString.push_back(char(keyCode)); - invalidatePos(editTrack, editRow); - } - else - MessageBeep(~0U); - break; - - case 'i': + return; + + case Qt::Key_I: editToggleInterpolationType(); - break; + return; - case 'k': + case Qt::Key_K: getDocument()->toggleRowBookmark(getEditRow()); invalidateRow(getEditRow()); - break; + return; + } + + if (paused && lineEdit->isHidden() && event->text().length() && doc->getTrackCount()) { + // no line-edit, check if input matches a double + QString str = event->text(); + int pos = 0; + if (lineEdit->validator()->validate(str, pos) != QValidator::Invalid) { + lineEdit->move(getPhysicalX(getEditTrack()), getPhysicalY(getEditRow())); + lineEdit->resize(trackWidth, rowHeight); + lineEdit->setText(""); + lineEdit->show(); + lineEdit->event(event); + lineEdit->setFocus(); + } } - return FALSE; } -LRESULT TrackView::onSize(int width, int height) +void TrackView::resizeEvent(QResizeEvent *event) { - windowWidth = width; - windowHeight = height; - - windowRows = (height - topMarginHeight) / rowHeight; - windowTracks = (width - leftMarginWidth) / trackWidth; - + windowRows = (event->size().height() - topMarginHeight) / rowHeight; setEditRow(editRow); setupScrollBars(); - return FALSE; } -LRESULT TrackView::onSetCursor(HWND /*win*/, UINT hitTest, UINT message) +void TrackView::changeEvent(QEvent *event) { - POINT cpos; - GetCursorPos(&cpos); - ScreenToClient(hwnd, &cpos); - int track = getTrackFromX(cpos.x); - if (cpos.y < topMarginHeight && - track >= 0 && track < int(getTrackCount())) { - SetCursor(handCursor); - return TRUE; - } - return DefWindowProc(this->hwnd, WM_SETCURSOR, (WPARAM)hwnd, - MAKELPARAM(hitTest, message)); -} - -LRESULT TrackView::onLButtonDown(UINT /*flags*/, POINTS pos) -{ - int track = getTrackFromX(pos.x); - if (pos.y < topMarginHeight && - track >= 0 && track < int(getTrackCount())) { - setEditTrack(track, false); - SetCapture(hwnd); - anchorTrack = track; - } - return FALSE; -} - -LRESULT TrackView::onLButtonUp(UINT /*flags*/, POINTS /*pos*/) -{ - ReleaseCapture(); - setEditTrack(editTrack); - return FALSE; -} - -LRESULT TrackView::onMouseMove(UINT /*flags*/, POINTS pos) -{ - if (GetCapture() == hwnd) { - SyncDocument *doc = getDocument(); - const int posTrack = getTrackFromX(pos.x), - trackCount = getTrackCount(); - - if (!doc || posTrack < 0 || posTrack >= trackCount) - return FALSE; - - if (posTrack > anchorTrack) { - for (int i = anchorTrack; i < posTrack; ++i) - doc->swapTrackOrder(i, i + 1); - anchorTrack = posTrack; - setEditTrack(posTrack); - InvalidateRect(hwnd, NULL, FALSE); - } else if (posTrack < anchorTrack) { - for (int i = anchorTrack; i > posTrack; --i) - doc->swapTrackOrder(i, i - 1); - anchorTrack = posTrack; - setEditTrack(posTrack); - InvalidateRect(hwnd, NULL, FALSE); - } - } - return FALSE; -} - -LRESULT TrackView::windowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - assert(hwnd == this->hwnd); - - switch(msg) - { - case WM_CREATE: return onCreate(); - case WM_SIZE: return onSize(LOWORD(lParam), HIWORD(lParam)); - case WM_VSCROLL: return onVScroll(LOWORD(wParam), getScrollPos(hwnd, SB_VERT)); - case WM_HSCROLL: return onHScroll(LOWORD(wParam), getScrollPos(hwnd, SB_HORZ)); - case WM_PAINT: return onPaint(); - case WM_KEYDOWN: return onKeyDown((UINT)wParam, (UINT)lParam); - case WM_CHAR: return onChar((UINT)wParam, (UINT)lParam); - case WM_LBUTTONDOWN: - return onLButtonDown((UINT)wParam, MAKEPOINTS(lParam)); - case WM_LBUTTONUP: - return onLButtonUp((UINT)wParam, MAKEPOINTS(lParam)); - case WM_MOUSEMOVE: - return onMouseMove((UINT)wParam, MAKEPOINTS(lParam)); - case WM_SETCURSOR: - return onSetCursor((HWND)wParam, LOWORD(lParam), - HIWORD(lParam)); - - case WM_COPY: editCopy(); break; - case WM_CUT: editCut(); break; - case WM_PASTE: - editPaste(); - SendMessage(GetParent(getWin()), WM_CURRVALDIRTY, 0, 0); - InvalidateRect(hwnd, NULL, FALSE); + switch (event->type()) { + case QEvent::FontChange: + updateFont(); break; - - case WM_UNDO: - if (NULL == getDocument()) return FALSE; - if (!getDocument()->undo()) - MessageBeep(~0U); - // unfortunately, we don't know how much to invalidate... so we'll just invalidate it all. - InvalidateRect(hwnd, NULL, FALSE); - break; - - case WM_REDO: - if (NULL == getDocument()) return FALSE; - if (!getDocument()->redo()) - MessageBeep(~0U); - // unfortunately, we don't know how much to invalidate... so we'll just invalidate it all. - InvalidateRect(hwnd, NULL, FALSE); - break; - - default: - return DefWindowProc(hwnd, msg, wParam, lParam); - } - return FALSE; -} -LRESULT CALLBACK trackViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) -{ - // Get the TrackView instance (if any) -#pragma warning(suppress:4312) /* remove a pointless warning */ - TrackView *trackView = (TrackView*)GetWindowLongPtr(hwnd, 0); - - switch(msg) - { - case WM_NCCREATE: - // Get TrackView from createstruct - trackView = (TrackView*)((CREATESTRUCT*)lParam)->lpCreateParams; - trackView->hwnd = hwnd; - - // Set the TrackView instance -#pragma warning(suppress:4244) /* remove a pointless warning */ - SetWindowLongPtr(hwnd, 0, (LONG_PTR)trackView); - - // call the proper window procedure - return trackView->windowProc(hwnd, msg, wParam, lParam); + case QEvent::PaletteChange: + updatePalette(); break; - - case WM_NCDESTROY: - assert(NULL != trackView); - { - // call the window proc and store away the return code - LRESULT res = trackView->windowProc(hwnd, msg, wParam, lParam); - - // get rid of the TrackView instance - trackView = NULL; - SetWindowLongPtr(hwnd, 0, (LONG_PTR)NULL); - - // return the stored return code - return res; - } - break; - - default: - assert(NULL != trackView); - return trackView->windowProc(hwnd, msg, wParam, lParam); + + default: ; } } - -ATOM registerTrackViewWindowClass(HINSTANCE hInstance) -{ - WNDCLASSEX wc; - - wc.cbSize = sizeof(WNDCLASSEX); - wc.style = 0; - wc.lpfnWndProc = trackViewWindowProc; - wc.cbClsExtra = 0; - wc.cbWndExtra = sizeof(TrackView*); - wc.hInstance = hInstance; - wc.hIcon = 0; - wc.hCursor = LoadCursor(NULL, IDC_IBEAM); - wc.hbrBackground = (HBRUSH)0; - wc.lpszMenuName = NULL; - wc.lpszClassName = trackViewWindowClassName; - wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); - - return RegisterClassEx(&wc); -} - -HWND TrackView::create(HINSTANCE hInstance, HWND hwndParent) -{ - HWND hwnd = CreateWindowEx( - WS_EX_CLIENTEDGE, - trackViewWindowClassName, "", - WS_VSCROLL | WS_HSCROLL | WS_CHILD | WS_VISIBLE, - CW_USEDEFAULT, CW_USEDEFAULT, // x, y - CW_USEDEFAULT, CW_USEDEFAULT, // width, height - hwndParent, NULL, hInstance, (void*)this - ); - return hwnd; -} - diff --git a/editor/trackview.h b/editor/trackview.h index 79eca11..35c1d53 100644 --- a/editor/trackview.h +++ b/editor/trackview.h @@ -1,26 +1,20 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ +#ifndef TRACKVIEW_H +#define TRACKVIEW_H -#pragma once +#include +#include +#include +#include -#include "syncdocument.h" -#include +class QLineEdit; +class SyncDocument; -// custom messages -#define WM_REDO (WM_USER + 0x40 + 3) -#define WM_ROWCHANGED (WM_USER + 0x40 + 4) -#define WM_TRACKCHANGED (WM_USER + 0x40 + 5) -#define WM_CURRVALDIRTY (WM_USER + 0x40 + 6) - -class TrackView +class TrackView : public QAbstractScrollArea { + Q_OBJECT public: - TrackView(); + TrackView(QWidget *parent); ~TrackView(); - - HWND create(HINSTANCE hInstance, HWND hwndParent); - HWND getWin() const { return hwnd; } void setDocument(SyncDocument *document) { @@ -30,184 +24,157 @@ public: const SyncDocument *getDocument() const { return document; } SyncDocument *getDocument() { return document; } - - void setRows(size_t rows); - size_t getRows() const - { - const SyncDocument *doc = getDocument(); - if (!doc) - return 0; - return doc->getRows(); - } - - void setFont(HFONT font); - + + void setRows(int rows); + int getRows() const; + void editEnterValue(); - void editDelete(); - void editCopy(); - void editCut(); - void editPaste(); void editBiasValue(float amount); void editToggleInterpolationType(); - void setEditRow(int newEditRow); + void setEditRow(int newEditRow, bool selecting = false); int getEditRow() const { return editRow; } - void setEditTrack(int newEditTrack, bool autoscroll = true); + void setEditTrack(int newEditTrack, bool autoscroll = true, bool selecting = false); int getEditTrack() const { return editTrack; } - - void selectAll() - { - selectStartTrack = int(this->getTrackCount()) - 1; - selectStopTrack = editTrack = 0; - selectStartRow = int(this->getRows()) - 1; - selectStopRow = editRow = 0; - - InvalidateRect(hwnd, NULL, FALSE); - } - - void selectTrack(int track) - { - selectStartTrack = selectStopTrack = editTrack = track; - selectStartRow = int(this->getRows()) - 1; - selectStopRow = editRow = 0; - - InvalidateRect(hwnd, NULL, FALSE); - } - - void selectRow(int row) - { - selectStartTrack = int(this->getTrackCount()) - 1; - selectStopTrack = editTrack = 0; - selectStartRow = selectStopRow = editRow = row; - - InvalidateRect(hwnd, NULL, FALSE); - } - + void selectNone() { selectStartTrack = selectStopTrack = editTrack; selectStartRow = selectStopRow = editRow; - InvalidateRect(hwnd, NULL, FALSE); + update(); } - -private: - // some nasty hackery to forward the window messages - friend LRESULT CALLBACK trackViewWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - LRESULT windowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam); - - // events - LRESULT onCreate(); - LRESULT onPaint(); - LRESULT onVScroll(UINT sbCode, int newPos); - LRESULT onHScroll(UINT sbCode, int newPos); - LRESULT onSize(int width, int height); - LRESULT onKeyDown(UINT keyCode, UINT flags); - LRESULT onChar(UINT keyCode, UINT flags); - LRESULT onSetCursor(HWND win, UINT hitTest, UINT message); - LRESULT onLButtonDown(UINT flags, POINTS pos); - LRESULT onLButtonUp(UINT flags, POINTS pos); - LRESULT onMouseMove(UINT flags, POINTS pos); - void paintTracks(HDC hdc, RECT rcTracks); - void paintTopMargin(HDC hdc, RECT rcTracks); - + void dirtyCurrentValue() + { + emit currValDirty(); + } + + void dirtyPosition() + { + emit posChanged(editTrack, editRow); + } + + bool paused, connected; + +signals: + void posChanged(int col, int row); + void pauseChanged(bool paused); + void currValDirty(); + +private slots: + void onHScroll(int value); + void onVScroll(int value); + void onEditingFinished(); + +public slots: + void editUndo(); + void editRedo(); + void editCopy(); + void editCut(); + void editPaste(); + void editClear(); + + void selectAll(); + void selectTrack(); + void selectRow(); + +private: + + /* paint helpers */ + void paintTopMargin(QPainter &painter, const QRect &rcTracks); + void paintLeftMargin(QPainter &painter, const QRect &rcTracks); + void paintTracks(QPainter &painter, const QRect &rcTracks); + void paintTrack(QPainter &painter, const QRect &rcTracks, int track); + + void paintEvent(QPaintEvent *); + void keyPressEvent(QKeyEvent *); + void resizeEvent(QResizeEvent *); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + void changeEvent(QEvent *); + void setupScrollBars(); void setScrollPos(int newScrollPosX, int newScrollPosY); void scrollWindow(int newScrollPosX, int newScrollPosY); - + void invalidateRange(int startTrack, int stopTrack, int startRow, int stopRow) { - RECT rect; - rect.left = getScreenX(std::min(startTrack, stopTrack)); - rect.right = getScreenX(std::max(startTrack, stopTrack) + 1); - rect.top = getScreenY(std::min(startRow, stopRow)); - rect.bottom = getScreenY(std::max(startRow, stopRow) + 1); - InvalidateRect(hwnd, &rect, FALSE); + QRect rect(QPoint(getPhysicalX(qMin(startTrack, stopTrack)), + getPhysicalY(qMin(startRow, stopRow))), + QPoint(getPhysicalX(qMax(startTrack, stopTrack) + 1) - 1, + getPhysicalY(qMax(startRow, stopRow) + 1) - 1)); + viewport()->update(rect); } - + void invalidatePos(int track, int row) { - RECT rect; - rect.left = getScreenX(track); - rect.right = getScreenX(track + 1); - rect.top = getScreenY(row); - rect.bottom = getScreenY(row + 1); - InvalidateRect(hwnd, &rect, FALSE); + invalidateRange(track, track, row, row); } - + void invalidateRow(int row) { - RECT clientRect; - GetClientRect(hwnd, &clientRect); - - RECT rect; - rect.left = clientRect.left; - rect.right = clientRect.right; - rect.top = getScreenY(row); - rect.bottom = getScreenY(row + 1); - - InvalidateRect(hwnd, &rect, FALSE); + invalidateRange(0, getTrackCount(), row, row); } - + void invalidateTrack(int track) { - RECT clientRect; - GetClientRect(hwnd, &clientRect); - - RECT rect; - rect.left = getScreenX(track); - rect.right = getScreenX(track + 1); - rect.top = clientRect.top; - rect.bottom = clientRect.bottom; - - InvalidateRect(hwnd, &rect, FALSE); + invalidateRange(track, track, 0, getRows()); } - - int getScreenY(int row) const; - int getScreenX(size_t track) const; - int getTrackFromX(int x) const; - size_t getTrackCount() const + void invalidateAll() { - const SyncDocument *doc = getDocument(); - if (NULL == doc) return 0; - return int(doc->getTrackOrderCount()); - }; - + invalidateRange(0, getTrackCount(), 0, getRows()); + } + + QRect getSelection() const + { + return QRect(QPoint(qMin(selectStartTrack, selectStopTrack), + qMin(selectStartRow, selectStopRow)), + QPoint(qMax(selectStartTrack, selectStopTrack), + qMax(selectStartRow, selectStopRow))); + } + + int getLogicalX(int track) const; + int getLogicalY(int row) const; + int getPhysicalX(int track) const; + int getPhysicalY(int row) const; + + int getTrackFromLogicalX(int x) const; + int getTrackFromPhysicalX(int x) const; + + int getTrackCount() const; + int selectStartTrack, selectStopTrack; int selectStartRow, selectStopRow; - - HFONT font; + int rowHeight; - int fontWidth; int trackWidth; int topMarginHeight; int leftMarginWidth; + void updateFont(); + + QBrush bgBaseBrush, bgDarkBrush; + QBrush selectBaseBrush, selectDarkBrush; + QPen rowPen, rowSelectPen; + QBrush editBrush, bookmarkBrush; + QPen lerpPen, cosinePen, rampPen; + QCursor handCursor; + void updatePalette(); - - HBRUSH bgBaseBrush, bgDarkBrush; - HBRUSH selectBaseBrush, selectDarkBrush; - HPEN rowPen, rowSelectPen; - HBRUSH editBrush, bookmarkBrush; - HPEN lerpPen, cosinePen, rampPen; - HCURSOR handCursor; - /* cursor position */ int editRow, editTrack; int scrollPosX, scrollPosY; - int windowWidth, windowHeight; - int windowRows, windowTracks; + int windowRows; SyncDocument *document; - - std::string editString; - - HWND hwnd; - - UINT clipboardFormat; + + QLineEdit *lineEdit; + + bool dragging; int anchorTrack; }; -ATOM registerTrackViewWindowClass(HINSTANCE hInstance); +#endif // !defined(TRACKVIEW_H) diff --git a/example_bass/example_bass.cpp b/example_bass/example_bass.cpp index 8218bec..5a9cc30 100644 --- a/example_bass/example_bass.cpp +++ b/example_bass/example_bass.cpp @@ -1,21 +1,24 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - * sdl+opengl examle by rasmus/loonies http://visualizethis.tumblr.com 2011 - */ - #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #include #endif #include +#ifdef WIN32 #undef main /* avoid SDL's nasty SDLmain hack */ +#endif #include #include #include #include #include -#include "../sync/sync.h" +#if defined(__APPLE__) && defined(__MACH__) +#include +#define gluPerspective(f, a, zn, zf) glMultMatrixf(GLKMatrix4MakePerspective((f) * M_PI / 180, a, zn, zf).m) +#define gluLookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz) glMultMatrixf(GLKMatrix4MakeLookAt(ex, ey, ez, cx, cy, cz, ux, uy, uz).m) +#endif + +#include "../lib/sync.h" static const float bpm = 150.0f; /* beats per minute */ static const int rpb = 8; /* rows per beat */ @@ -80,7 +83,7 @@ static void die(const char *fmt, ...) static const unsigned int width = 800; static const unsigned int height = 600; -SDL_Surface *setup_sdl() +void setup_sdl() { if (SDL_Init(SDL_INIT_VIDEO)) die("%s", SDL_GetError()); @@ -93,7 +96,8 @@ SDL_Surface *setup_sdl() SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16); SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1); - return SDL_SetVideoMode(width, height, 32, SDL_OPENGL); + if (!SDL_SetVideoMode(width, height, 32, SDL_OPENGL)) + die("%s", SDL_GetError()); } void draw_cube() @@ -147,13 +151,12 @@ void draw_cube() int main(int argc, char *argv[]) { - SDL_Surface *screen; HSTREAM stream; const struct sync_track *clear_r, *clear_g, *clear_b; const struct sync_track *cam_rot, *cam_dist; - screen = setup_sdl(); + setup_sdl(); /* init BASS */ if (!BASS_Init(-1, 44100, 0, 0, 0)) @@ -193,13 +196,13 @@ int main(int argc, char *argv[]) /* draw */ - glClearColor(sync_get_val(clear_r, row), - sync_get_val(clear_g, row), - sync_get_val(clear_b, row), 1.0f); + glClearColor(float(sync_get_val(clear_r, row)), + float(sync_get_val(clear_g, row)), + float(sync_get_val(clear_b, row)), 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - float rot = sync_get_val(cam_rot, row); - float dist = sync_get_val(cam_dist, row); + float rot = float(sync_get_val(cam_rot, row)); + float dist = float(sync_get_val(cam_dist, row)); glMatrixMode(GL_PROJECTION); glLoadIdentity(); diff --git a/example_bass/example_bass.vcproj b/example_bass/example_bass.vs2008.vcproj similarity index 100% rename from example_bass/example_bass.vcproj rename to example_bass/example_bass.vs2008.vcproj diff --git a/example_bass/example_bass.vs2013.vcxproj b/example_bass/example_bass.vs2013.vcxproj new file mode 100644 index 0000000..9b4612c --- /dev/null +++ b/example_bass/example_bass.vs2013.vcxproj @@ -0,0 +1,361 @@ + + + + + Debug Client + Win32 + + + Debug Client + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release Client + Win32 + + + Release Client + x64 + + + Release + Win32 + + + Release + x64 + + + + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3} + example_bass + Win32Proj + example_bass + + + + MultiByte + true + v120 + + + MultiByte + v120 + + + MultiByte + true + v120 + + + MultiByte + v120 + + + MultiByte + true + v120 + + + MultiByte + v120 + + + MultiByte + true + v120 + + + MultiByte + v120 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + db1c9e5e + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + false + false + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + false + false + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + true + false + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + false + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false + + + + Disabled + include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;SYNC_PLAYER;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + EditAndContinue + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib;%(AdditionalLibraryDirectories) + true + Console + + MachineX86 + false + + + + + include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;SYNC_PLAYER;%(PreprocessorDefinitions) + MultiThreadedDLL + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib;%(AdditionalLibraryDirectories) + true + Console + true + true + + MachineX86 + + + + + Disabled + include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + EditAndContinue + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib;%(AdditionalLibraryDirectories) + true + Console + + MachineX86 + false + + + + + include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib;%(AdditionalLibraryDirectories) + true + Console + true + true + + MachineX86 + + + + + X64 + + + Disabled + include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;SYNC_PLAYER;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib64;%(AdditionalLibraryDirectories) + true + Console + + MachineX64 + + + + + X64 + + + include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;SYNC_PLAYER;%(PreprocessorDefinitions) + MultiThreadedDLL + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib64;%(AdditionalLibraryDirectories) + true + Console + true + true + + MachineX64 + + + + + X64 + + + Disabled + include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib64;%(AdditionalLibraryDirectories) + true + Console + + MachineX64 + + + + + X64 + + + include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + + ProgramDatabase + Level3 + + + opengl32.lib;glu32.lib;bass.lib;%(AdditionalDependencies) + lib64;%(AdditionalLibraryDirectories) + true + Console + true + true + + MachineX64 + + + + + + + + {5866042c-7fcb-4db1-baad-44df6567511f} + false + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + \ No newline at end of file diff --git a/example_bass/example_bass.vs2013.vcxproj.filters b/example_bass/example_bass.vs2013.vcxproj.filters new file mode 100644 index 0000000..d97473d --- /dev/null +++ b/example_bass/example_bass.vs2013.vcxproj.filters @@ -0,0 +1,25 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + + + + \ No newline at end of file diff --git a/example_bass/packages.config b/example_bass/packages.config new file mode 100644 index 0000000..03fa99d --- /dev/null +++ b/example_bass/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples.sln b/examples.vs2008.sln similarity index 94% rename from examples.sln rename to examples.vs2008.sln index 40b5bef..33c72ed 100644 --- a/examples.sln +++ b/examples.vs2008.sln @@ -1,12 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 10.00 # Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_bass", "example_bass\example_bass.vcproj", "{96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_bass", "example_bass\example_bass.vs2008.vcproj", "{96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}" ProjectSection(ProjectDependencies) = postProject {5866042C-7FCB-4DB1-BAAD-44DF6567511F} = {5866042C-7FCB-4DB1-BAAD-44DF6567511F} EndProjectSection EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sync_player", "sync_player.vcproj", "{5866042C-7FCB-4DB1-BAAD-44DF6567511F}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "librocket", "lib/librocket.vs2008.vcproj", "{5866042C-7FCB-4DB1-BAAD-44DF6567511F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/examples.vs2013.sln b/examples.vs2013.sln new file mode 100644 index 0000000..080e03a --- /dev/null +++ b/examples.vs2013.sln @@ -0,0 +1,58 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "example_bass", "example_bass\example_bass.vs2013.vcxproj", "{96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "librocket", "lib\librocket.vs2013.vcxproj", "{5866042C-7FCB-4DB1-BAAD-44DF6567511F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug Client|Win32 = Debug Client|Win32 + Debug Client|x64 = Debug Client|x64 + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release Client|Win32 = Release Client|Win32 + Release Client|x64 = Release Client|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug Client|Win32.ActiveCfg = Debug Client|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug Client|Win32.Build.0 = Debug Client|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug Client|x64.ActiveCfg = Debug Client|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug Client|x64.Build.0 = Debug Client|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug|Win32.ActiveCfg = Debug|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug|Win32.Build.0 = Debug|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug|x64.ActiveCfg = Debug|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Debug|x64.Build.0 = Debug|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release Client|Win32.ActiveCfg = Release Client|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release Client|Win32.Build.0 = Release Client|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release Client|x64.ActiveCfg = Release Client|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release Client|x64.Build.0 = Release Client|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release|Win32.ActiveCfg = Release|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release|Win32.Build.0 = Release|Win32 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release|x64.ActiveCfg = Release|x64 + {96D91AAD-2F45-4CC6-A923-96B80E1C3CE3}.Release|x64.Build.0 = Release|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug Client|Win32.ActiveCfg = Debug Client|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug Client|Win32.Build.0 = Debug Client|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug Client|x64.ActiveCfg = Debug Client|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug Client|x64.Build.0 = Debug Client|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug|Win32.ActiveCfg = Debug|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug|Win32.Build.0 = Debug|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug|x64.ActiveCfg = Debug|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Debug|x64.Build.0 = Debug|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release Client|Win32.ActiveCfg = Release Client|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release Client|Win32.Build.0 = Release Client|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release Client|x64.ActiveCfg = Release Client|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release Client|x64.Build.0 = Release Client|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release|Win32.ActiveCfg = Release|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release|Win32.Build.0 = Release|Win32 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release|x64.ActiveCfg = Release|x64 + {5866042C-7FCB-4DB1-BAAD-44DF6567511F}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/lib/base.h b/lib/base.h new file mode 100644 index 0000000..71d5727 --- /dev/null +++ b/lib/base.h @@ -0,0 +1,35 @@ +#ifndef SYNC_BASE_H +#define SYNC_BASE_H + +#ifdef _MSC_VER + #define _CRT_SECURE_NO_WARNINGS + #define _CRT_NONSTDC_NO_DEPRECATE +#endif + +#include + +/* configure inline keyword */ +#if (!defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L)) && !defined(__cplusplus) + #if defined(_MSC_VER) || defined(__GNUC__) || defined(__SASC) + #define inline __inline + #else + /* compiler does not support inline */ + #define inline + #endif +#endif + +/* configure lacking CRT features */ +#ifdef _MSC_VER + #if _MSC_VER < 1900 + #define snprintf _snprintf + #endif + /* int is 32-bit for both x86 and x64 */ + typedef unsigned int uint32_t; + #define UINT32_MAX UINT_MAX +#elif defined(__GNUC__) + #include +#elif defined(M68000) + typedef unsigned int uint32_t; +#endif + +#endif /* SYNC_BASE_H */ diff --git a/sync/device.c b/lib/device.c similarity index 59% rename from sync/device.c rename to lib/device.c index e9b75c5..9d74d08 100644 --- a/sync/device.c +++ b/lib/device.c @@ -1,11 +1,18 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - #include "device.h" -#include "sync.h" +#include "track.h" #include #include +#include +#include + +static int find_track(struct sync_device *d, const char *name) +{ + int i; + for (i = 0; i < (int)d->num_tracks; ++i) + if (!strcmp(name, d->tracks[i]->name)) + return i; + return -1; /* not found */ +} static const char *sync_track_path(const char *base, const char *name) { @@ -20,16 +27,70 @@ static const char *sync_track_path(const char *base, const char *name) #ifndef SYNC_PLAYER +#define CLIENT_GREET "hello, synctracker!" +#define SERVER_GREET "hello, demo!" + +enum { + SET_KEY = 0, + DELETE_KEY = 1, + GET_TRACK = 2, + SET_ROW = 3, + PAUSE = 4, + SAVE_TRACKS = 5 +}; + +static inline int socket_poll(SOCKET socket) +{ + struct timeval to = { 0, 0 }; + fd_set fds; + + FD_ZERO(&fds); + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable: 4127) +#endif + FD_SET(socket, &fds); +#ifdef _MSC_VER +#pragma warning(pop) +#endif + + return select((int)socket + 1, &fds, NULL, NULL, &to) > 0; +} + +static inline int xsend(SOCKET s, const void *buf, size_t len, int flags) +{ +#ifdef WIN32 + assert(len <= INT_MAX); + return send(s, (const char *)buf, (int)len, flags) != (int)len; +#else + return send(s, (const char *)buf, len, flags) != (int)len; +#endif +} + +static inline int xrecv(SOCKET s, void *buf, size_t len, int flags) +{ +#ifdef WIN32 + assert(len <= INT_MAX); + return recv(s, (char *)buf, (int)len, flags) != (int)len; +#else + return recv(s, (char *)buf, len, flags) != (int)len; +#endif +} + #ifdef USE_AMITCP static struct Library *socket_base = NULL; #endif static SOCKET server_connect(const char *host, unsigned short nport) { +#ifdef USE_GETADDRINFO + struct addrinfo *addr; + char port[6]; +#else struct hostent *he; - struct sockaddr_in sa; - char greet[128], **ap; - SOCKET sock = INVALID_SOCKET; + char **ap; +#endif #ifdef WIN32 static int need_init = 1; @@ -47,37 +108,58 @@ static SOCKET server_connect(const char *host, unsigned short nport) } #endif +#ifdef USE_GETADDRINFO + + snprintf(port, sizeof(port), "%u", nport); + if (getaddrinfo(host, port, 0, &addr) != 0) + return INVALID_SOCKET; + + for (; addr; addr = addr->ai_next) { + SOCKET sock; + int family = addr->ai_family; + struct sockaddr *sa = addr->ai_addr; + int sa_len = (int) addr->ai_addrlen; /* elim. warning on (at least) Win/x64, size_t vs. int/socklen_t */ + +#else + he = gethostbyname(host); if (!he) return INVALID_SOCKET; for (ap = he->h_addr_list; *ap; ++ap) { - sa.sin_family = he->h_addrtype; - sa.sin_port = htons(nport); - memcpy(&sa.sin_addr, *ap, he->h_length); + SOCKET sock; + int family = he->h_addrtype; + struct sockaddr_in sin; + struct sockaddr *sa = (struct sockaddr *)&sin; + int sa_len = sizeof(*sa); - sock = socket(he->h_addrtype, SOCK_STREAM, 0); + sin.sin_family = he->h_addrtype; + sin.sin_port = htons(nport); + memcpy(&sin.sin_addr, *ap, he->h_length); + memset(&sin.sin_zero, 0, sizeof(sin.sin_zero)); + +#endif + + sock = socket(family, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) continue; - if (connect(sock, (struct sockaddr *)&sa, sizeof(sa)) >= 0) - break; + if (connect(sock, sa, sa_len) >= 0) { + char greet[128]; + + if (xsend(sock, CLIENT_GREET, strlen(CLIENT_GREET), 0) || + xrecv(sock, greet, strlen(SERVER_GREET), 0)) { + closesocket(sock); + continue; + } + + if (!strncmp(SERVER_GREET, greet, strlen(SERVER_GREET))) + return sock; + } closesocket(sock); - sock = INVALID_SOCKET; } - if (sock == INVALID_SOCKET) - return INVALID_SOCKET; - - if (xsend(sock, CLIENT_GREET, strlen(CLIENT_GREET), 0) || - xrecv(sock, greet, strlen(SERVER_GREET), 0)) - return INVALID_SOCKET; - - if (!strncmp(SERVER_GREET, greet, strlen(SERVER_GREET))) - return sock; - - closesocket(sock); return INVALID_SOCKET; } @@ -92,6 +174,17 @@ void sync_set_io_cb(struct sync_device *d, struct sync_io_cb *cb) #endif +#ifdef NEED_STRDUP +static inline char *rocket_strdup(const char *str) +{ + char *ret = malloc(strlen(str) + 1); + if (ret) + strcpy(ret, str); + return ret; +} +#define strdup rocket_strdup +#endif + struct sync_device *sync_create_device(const char *base) { struct sync_device *d = malloc(sizeof(*d)); @@ -104,16 +197,16 @@ struct sync_device *sync_create_device(const char *base) return NULL; } - d->data.tracks = NULL; - d->data.num_tracks = 0; + d->tracks = NULL; + d->num_tracks = 0; #ifndef SYNC_PLAYER d->row = -1; d->sock = INVALID_SOCKET; #else - d->io_cb.open = fopen; - d->io_cb.read = fread; - d->io_cb.close = fclose; + d->io_cb.open = (void *(*)(const char *, const char *))fopen; + d->io_cb.read = (size_t (*)(void *, size_t, size_t, void *))fread; + d->io_cb.close = (int (*)(void *))fclose; #endif return d; @@ -121,8 +214,14 @@ struct sync_device *sync_create_device(const char *base) void sync_destroy_device(struct sync_device *d) { + int i; + for (i = 0; i < (int)d->num_tracks; ++i) { + free(d->tracks[i]->name); + free(d->tracks[i]->keys); + free(d->tracks[i]); + } + free(d->tracks); free(d->base); - sync_data_deinit(&d->data); free(d); #if defined(USE_AMITCP) && !defined(SYNC_PLAYER) @@ -142,7 +241,7 @@ static int get_track_data(struct sync_device *d, struct sync_track *t) if (!fp) return -1; - d->io_cb.read(&t->num_keys, sizeof(size_t), 1, fp); + d->io_cb.read(&t->num_keys, sizeof(int), 1, fp); t->keys = malloc(sizeof(struct track_key) * t->num_keys); if (!t->keys) return -1; @@ -150,7 +249,7 @@ static int get_track_data(struct sync_device *d, struct sync_track *t) for (i = 0; i < (int)t->num_keys; ++i) { struct track_key *key = t->keys + i; char type; - d->io_cb.read(&key->row, sizeof(size_t), 1, fp); + d->io_cb.read(&key->row, sizeof(int), 1, fp); d->io_cb.read(&key->value, sizeof(float), 1, fp); d->io_cb.read(&type, sizeof(char), 1, fp); key->type = (enum key_type)type; @@ -169,7 +268,7 @@ static int save_track(const struct sync_track *t, const char *path) if (!fp) return -1; - fwrite(&t->num_keys, sizeof(size_t), 1, fp); + fwrite(&t->num_keys, sizeof(int), 1, fp); for (i = 0; i < (int)t->num_keys; ++i) { char type = (char)t->keys[i].type; fwrite(&t->keys[i].row, sizeof(int), 1, fp); @@ -184,8 +283,8 @@ static int save_track(const struct sync_track *t, const char *path) void sync_save_tracks(const struct sync_device *d) { int i; - for (i = 0; i < (int)d->data.num_tracks; ++i) { - const struct sync_track *t = d->data.tracks[i]; + for (i = 0; i < (int)d->num_tracks; ++i) { + const struct sync_track *t = d->tracks[i]; save_track(t, sync_track_path(d->base, t->name)); } } @@ -211,7 +310,7 @@ static int get_track_data(struct sync_device *d, struct sync_track *t) return 0; } -static int handle_set_key_cmd(SOCKET sock, struct sync_data *data) +static int handle_set_key_cmd(SOCKET sock, struct sync_device *data) { uint32_t track, row; union { @@ -239,7 +338,7 @@ static int handle_set_key_cmd(SOCKET sock, struct sync_data *data) return sync_set_key(data->tracks[track], &key); } -static int handle_del_key_cmd(SOCKET sock, struct sync_data *data) +static int handle_del_key_cmd(SOCKET sock, struct sync_device *data) { uint32_t track, row; @@ -264,14 +363,14 @@ int sync_connect(struct sync_device *d, const char *host, unsigned short port) if (d->sock == INVALID_SOCKET) return -1; - for (i = 0; i < (int)d->data.num_tracks; ++i) { - free(d->data.tracks[i]->keys); - d->data.tracks[i]->keys = NULL; - d->data.tracks[i]->num_keys = 0; + for (i = 0; i < (int)d->num_tracks; ++i) { + free(d->tracks[i]->keys); + d->tracks[i]->keys = NULL; + d->tracks[i]->num_keys = 0; } - for (i = 0; i < (int)d->data.num_tracks; ++i) { - if (get_track_data(d, d->data.tracks[i])) { + for (i = 0; i < (int)d->num_tracks; ++i) { + if (get_track_data(d, d->tracks[i])) { closesocket(d->sock); d->sock = INVALID_SOCKET; return -1; @@ -295,11 +394,11 @@ int sync_update(struct sync_device *d, int row, struct sync_cb *cb, switch (cmd) { case SET_KEY: - if (handle_set_key_cmd(d->sock, &d->data)) + if (handle_set_key_cmd(d->sock, d)) goto sockerr; break; case DELETE_KEY: - if (handle_del_key_cmd(d->sock, &d->data)) + if (handle_del_key_cmd(d->sock, d)) goto sockerr; break; case SET_ROW: @@ -343,16 +442,33 @@ sockerr: #endif +static int create_track(struct sync_device *d, const char *name) +{ + struct sync_track *t; + assert(find_track(d, name) < 0); + + t = malloc(sizeof(*t)); + t->name = strdup(name); + t->keys = NULL; + t->num_keys = 0; + + d->num_tracks++; + d->tracks = realloc(d->tracks, sizeof(d->tracks[0]) * d->num_tracks); + d->tracks[d->num_tracks - 1] = t; + + return (int)d->num_tracks - 1; +} + const struct sync_track *sync_get_track(struct sync_device *d, const char *name) { struct sync_track *t; - int idx = sync_find_track(&d->data, name); + int idx = find_track(d, name); if (idx >= 0) - return d->data.tracks[idx]; + return d->tracks[idx]; - idx = sync_create_track(&d->data, name); - t = d->data.tracks[idx]; + idx = create_track(d, name); + t = d->tracks[idx]; get_track_data(d, t); return t; diff --git a/lib/device.h b/lib/device.h new file mode 100644 index 0000000..4628724 --- /dev/null +++ b/lib/device.h @@ -0,0 +1,55 @@ +#ifndef SYNC_DEVICE_H +#define SYNC_DEVICE_H + +#include "base.h" +#include "sync.h" + +#ifndef SYNC_PLAYER + +/* configure socket-stack */ +#ifdef _WIN32 + #define WIN32_LEAN_AND_MEAN + #define USE_GETADDRINFO + #ifndef NOMINMAX + #define NOMINMAX + #endif + #include + #include + #include + #include +#elif defined(USE_AMITCP) + #include + #include + #include + #include + #define SOCKET int + #define INVALID_SOCKET -1 + #define select(n,r,w,e,t) WaitSelect(n,r,w,e,t,0) + #define closesocket(x) CloseSocket(x) +#else + #include + #include + #include + #include + #include + #define SOCKET int + #define INVALID_SOCKET -1 + #define closesocket(x) close(x) +#endif + +#endif /* !defined(SYNC_PLAYER) */ + +struct sync_device { + char *base; + struct sync_track **tracks; + size_t num_tracks; + +#ifndef SYNC_PLAYER + int row; + SOCKET sock; +#else + struct sync_io_cb io_cb; +#endif +}; + +#endif /* SYNC_DEVICE_H */ diff --git a/sync_player.vcproj b/lib/librocket.vs2008.vcproj similarity index 88% rename from sync_player.vcproj rename to lib/librocket.vs2008.vcproj index fbe9afe..3f65814 100644 --- a/sync_player.vcproj +++ b/lib/librocket.vs2008.vcproj @@ -2,9 +2,9 @@ @@ -21,7 +21,7 @@ - - @@ -538,23 +537,19 @@ UniqueIdentifier="{93995380-89BD-4b04-88EB-625FBE52EBFB}" > - - diff --git a/lib/librocket.vs2013.vcxproj b/lib/librocket.vs2013.vcxproj new file mode 100644 index 0000000..ca177b6 --- /dev/null +++ b/lib/librocket.vs2013.vcxproj @@ -0,0 +1,292 @@ + + + + + Debug Client + Win32 + + + Debug Client + x64 + + + Debug + Win32 + + + Debug + x64 + + + Release Client + Win32 + + + Release Client + x64 + + + Release + Win32 + + + Release + x64 + + + + {5866042C-7FCB-4DB1-BAAD-44DF6567511F} + librocket + Win32Proj + librocket + + + + StaticLibrary + v120 + MultiByte + true + + + StaticLibrary + v120 + MultiByte + + + StaticLibrary + v120 + MultiByte + true + + + StaticLibrary + v120 + MultiByte + + + StaticLibrary + v120 + MultiByte + true + + + StaticLibrary + v120 + MultiByte + + + StaticLibrary + v120 + MultiByte + true + + + StaticLibrary + v120 + MultiByte + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.30501.0 + + + $(SolutionDir)lib\ + $(Configuration)\ + $(ProjectName)-playerd + + + $(SolutionDir)lib\ + $(Configuration)\ + $(ProjectName)-player + + + $(SolutionDir)lib\ + $(Configuration)\ + $(ProjectName)d + + + $(SolutionDir)lib\ + $(Configuration)\ + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(ProjectName)-playerd + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(ProjectName)-player + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + $(ProjectName)d + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + + + + Disabled + WIN32;_DEBUG;_LIB;SYNC_PLAYER;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + Level3 + EditAndContinue + + + $(OutDir)$(ProjectName)-playerd.lib + + + + + WIN32;NDEBUG;_LIB;SYNC_PLAYER;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level3 + ProgramDatabase + + + $(OutDir)$(ProjectName)-player.lib + + + + + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + Level4 + EditAndContinue + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName)d.lib + + + + + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level3 + ProgramDatabase + + + ws2_32.lib;%(AdditionalDependencies) + + + + + X64 + + + Disabled + WIN32;_DEBUG;_LIB;SYNC_PLAYER;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + Level3 + ProgramDatabase + + + $(OutDir)$(ProjectName)-playerd.lib + + + + + X64 + + + WIN32;NDEBUG;_LIB;SYNC_PLAYER;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level3 + ProgramDatabase + + + $(OutDir)$(ProjectName)-player.lib + + + + + X64 + + + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + Level4 + ProgramDatabase + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(ProjectName)d.lib + + + + + X64 + + + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + MultiThreadedDLL + + Level3 + ProgramDatabase + + + ws2_32.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/librocket.vs2013.vcxproj.filters b/lib/librocket.vs2013.vcxproj.filters new file mode 100644 index 0000000..1cbe57d --- /dev/null +++ b/lib/librocket.vs2013.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/sync/sync.h b/lib/sync.h similarity index 95% rename from sync/sync.h rename to lib/sync.h index 70987ef..f3077ff 100644 --- a/sync/sync.h +++ b/lib/sync.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Erik Faye-Lund and Egbert Teeselink +/* Copyright (C) 2010 Contributors * For conditions of distribution and use, see copyright notice in COPYING */ @@ -9,6 +9,8 @@ extern "C" { #endif +#include + struct sync_device; struct sync_track; diff --git a/sync/track.c b/lib/track.c similarity index 95% rename from sync/track.c rename to lib/track.c index 05f4793..3f6e535 100644 --- a/sync/track.c +++ b/lib/track.c @@ -1,7 +1,3 @@ -/* Copyright (C) 2010 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - #include #include #include diff --git a/sync/track.h b/lib/track.h similarity index 86% rename from sync/track.h rename to lib/track.h index 9c9d24a..fd59ded 100644 --- a/sync/track.h +++ b/lib/track.h @@ -1,7 +1,3 @@ -/* Copyright (C) 2007-2010 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - #ifndef SYNC_TRACK_H #define SYNC_TRACK_H diff --git a/rocket.sln b/rocket.sln deleted file mode 100644 index 6681f8f..0000000 --- a/rocket.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual Studio 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "sync_editor", "editor\editor.vcproj", "{76B44BC8-8BB4-4B6E-B2FA-7738C9E7F80B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {76B44BC8-8BB4-4B6E-B2FA-7738C9E7F80B}.Debug|Win32.ActiveCfg = Debug|Win32 - {76B44BC8-8BB4-4B6E-B2FA-7738C9E7F80B}.Debug|Win32.Build.0 = Debug|Win32 - {76B44BC8-8BB4-4B6E-B2FA-7738C9E7F80B}.Release|Win32.ActiveCfg = Release|Win32 - {76B44BC8-8BB4-4B6E-B2FA-7738C9E7F80B}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/sync/base.h b/sync/base.h deleted file mode 100644 index da1a978..0000000 --- a/sync/base.h +++ /dev/null @@ -1,124 +0,0 @@ -/* Copyright (C) 2007-2010 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - -#ifndef SYNC_BASE_H -#define SYNC_BASE_H - -/* configure inline keyword */ -#if !defined(__STDC_VERSION__) || (__STDC_VERSION__ < 199901L) - #if defined(_MSC_VER) || defined(__GNUC__) || defined(__SASC) -#ifndef inline - #define inline __inline -#endif -#else - /* compiler does not support inline, make function static instead */ - #define inline static - #endif -#endif - -/* configure lacking CRT features */ -#ifdef _MSC_VER - #define strdup _strdup - #define snprintf _snprintf - /* int is 32-bit for both x86 and x64 */ - typedef unsigned int uint32_t; - #define UINT32_MAX UINT_MAX -#elif defined(__GNUC__) - #include -#elif defined(M68000) - typedef unsigned int uint32_t; -#endif - -/* configure socket-stack */ -#ifdef _WIN32 - #define WIN32_LEAN_AND_MEAN - #define NOMINMAX - #include - #include - #include -#elif defined(USE_AMITCP) - #include - #include - #include - #include - #define SOCKET int - #define INVALID_SOCKET -1 - #define select(n,r,w,e,t) WaitSelect(n,r,w,e,t,0) - #define closesocket(x) CloseSocket(x) -#else - #include - #include - #include - #include - #include - #define SOCKET int - #define INVALID_SOCKET -1 - #define closesocket(x) close(x) -#endif - -#define CLIENT_GREET "hello, synctracker!" -#define SERVER_GREET "hello, demo!" - -enum { - SET_KEY = 0, - DELETE_KEY = 1, - GET_TRACK = 2, - SET_ROW = 3, - PAUSE = 4, - SAVE_TRACKS = 5 -}; - -static inline int socket_poll(SOCKET socket) -{ - struct timeval to = { 0, 0 }; - fd_set fds; - - FD_ZERO(&fds); - -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable: 4127) -#endif - FD_SET(socket, &fds); -#ifdef _MSC_VER -#pragma warning(pop) -#endif - - return select((int)socket + 1, &fds, NULL, NULL, &to) > 0; -} - -#include - -static inline int xsend(SOCKET s, const void *buf, size_t len, int flags) -{ -#ifdef WIN32 - assert(len <= INT_MAX); - return send(s, (const char *)buf, (int)len, flags) != (int)len; -#else - return send(s, (const char *)buf, len, flags) != len; -#endif -} - -static inline int xrecv(SOCKET s, void *buf, size_t len, int flags) -{ -#ifdef WIN32 - assert(len <= INT_MAX); - return recv(s, (char *)buf, (int)len, flags) != (int)len; -#else - return recv(s, (char *)buf, len, flags) != len; -#endif -} - -#ifdef NEED_STRDUP -static inline char *rocket_strdup(const char *str) -{ - char *ret = malloc(strlen(str) + 1); - if (ret) - strcpy(ret, str); - return ret; -} -#define strdup rocket_strdup -#endif - -#endif /* SYNC_BASE_H */ diff --git a/sync/data.c b/sync/data.c deleted file mode 100644 index 59faedd..0000000 --- a/sync/data.c +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - -#include "data.h" - -void sync_data_deinit(struct sync_data *d) -{ - int i; - for (i = 0; i < (int)d->num_tracks; ++i) { - free(d->tracks[i]->name); - free(d->tracks[i]->keys); - free(d->tracks[i]); - } - free(d->tracks); -} - -int sync_create_track(struct sync_data *d, const char *name) -{ - struct sync_track *t; - assert(sync_find_track(d, name) < 0); - - t = malloc(sizeof(*t)); - t->name = strdup(name); - t->keys = NULL; - t->num_keys = 0; - - d->num_tracks++; - d->tracks = realloc(d->tracks, sizeof(d->tracks[0]) * d->num_tracks); - d->tracks[d->num_tracks - 1] = t; - - return (int)d->num_tracks - 1; -} diff --git a/sync/data.h b/sync/data.h deleted file mode 100644 index fca7a08..0000000 --- a/sync/data.h +++ /dev/null @@ -1,28 +0,0 @@ -/* Copyright (C) 2007-2010 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - -#ifndef SYNC_DATA_H -#define SYNC_DATA_H - -#include "track.h" - -struct sync_data { - struct sync_track **tracks; - size_t num_tracks; -}; - -static inline int sync_find_track(const struct sync_data *data, - const char *name) -{ - int i; - for (i = 0; i < (int)data->num_tracks; ++i) - if (!strcmp(name, data->tracks[i]->name)) - return i; - return -1; /* not found */ -} - -void sync_data_deinit(struct sync_data *); -int sync_create_track(struct sync_data *, const char *); - -#endif /* SYNC_DATA_H */ diff --git a/sync/device.h b/sync/device.h deleted file mode 100644 index 4dca4bf..0000000 --- a/sync/device.h +++ /dev/null @@ -1,23 +0,0 @@ -/* Copyright (C) 2007-2008 Erik Faye-Lund and Egbert Teeselink - * For conditions of distribution and use, see copyright notice in COPYING - */ - -#ifndef SYNC_DEVICE_H -#define SYNC_DEVICE_H - -#include "data.h" -#include "sync.h" - -struct sync_device { - char *base; - struct sync_data data; - -#ifndef SYNC_PLAYER - int row; - SOCKET sock; -#else - struct sync_io_cb io_cb; -#endif -}; - -#endif /* SYNC_DEVICE_H */