diff options
author | Damien Zammit <damien@zamaudio.com> | 2015-02-17 11:59:35 +1100 |
---|---|---|
committer | Damien Zammit <damien@zamaudio.com> | 2015-02-17 11:59:35 +1100 |
commit | 78883fa7f7ea9d85477a27ed3c569a899ee305fb (patch) | |
tree | f1add63ce6f3af86340d7e4c93092776b5479b14 | |
parent | af7a094a0c0e8b658736d77361a2d8d2f99b0006 (diff) | |
parent | 8da011deb152227714196f61eab26e1af37a2890 (diff) |
Merge pull request #24 from falkTX/master
Update DPF, inital file-browser for ZamSFZ
49 files changed, 4046 insertions, 1603 deletions
@@ -21,6 +21,9 @@ bin/ZamComp bin/ZamCompX2 bin/ZamEQ2 bin/ZamGEQ31 +bin/ZamGEQ31X2 bin/ZamNoise +bin/ZamPiano +bin/ZamSFZ bin/ZamSynth bin/ZamTube diff --git a/Makefile.mk b/Makefile.mk index 64c15b7..e450b8a 100644 --- a/Makefile.mk +++ b/Makefile.mk @@ -26,7 +26,7 @@ BASE_OPTS = -O2 -ffast-math -fdata-sections -ffunction-sections ifneq ($(NOOPT),true) BASE_OPTS += -mtune=generic -msse -msse2 -mfpmath=sse endif -LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1 -Wl,--as-needed -Wl,--gc-sections -Wl,--strip-all $(shell pkg-config --libs sndfile rubberband) +LINK_OPTS = -fdata-sections -ffunction-sections -Wl,-O1 -Wl,--as-needed -Wl,--gc-sections -Wl,--strip-all ifeq ($(MACOS),true) # MacOS linker flags diff --git a/libs/dgl/App.hpp b/libs/dgl/App.hpp index b6fce01..4babf1f 100644 --- a/libs/dgl/App.hpp +++ b/libs/dgl/App.hpp @@ -21,6 +21,9 @@ START_NAMESPACE_DGL +// ----------------------------------------------------------------------- +// Forward class names + class Window; // ----------------------------------------------------------------------- @@ -50,7 +53,7 @@ public: /** Idle function. - This calls all this app Windows' idle functions and idle callbacks. + This runs the application event-loop once. */ void idle(); @@ -69,7 +72,7 @@ public: /** Check if the application is about to quit. - Returning true means there's no event-loop running at the moment. + Returning true means there's no event-loop running at the moment (or it's just about to stop). */ bool isQuiting() const noexcept; diff --git a/libs/dgl/Base.hpp b/libs/dgl/Base.hpp index 0d62d72..edefbcf 100644 --- a/libs/dgl/Base.hpp +++ b/libs/dgl/Base.hpp @@ -127,6 +127,16 @@ enum Char { }; /** + Keyboard modifier flags. + */ +enum Modifier { + MODIFIER_SHIFT = 1 << 0, /**< Shift key */ + MODIFIER_CTRL = 1 << 1, /**< Control key */ + MODIFIER_ALT = 1 << 2, /**< Alt/Option key */ + MODIFIER_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ +}; + +/** Special (non-Unicode) keyboard keys. */ enum Key { @@ -157,16 +167,6 @@ enum Key { KEY_SUPER }; -/** - Keyboard modifier flags. - */ -enum Modifier { - MODIFIER_SHIFT = 1 << 0, /**< Shift key */ - MODIFIER_CTRL = 1 << 1, /**< Control key */ - MODIFIER_ALT = 1 << 2, /**< Alt/Option key */ - MODIFIER_SUPER = 1 << 3 /**< Mod4/Command/Windows key */ -}; - // ----------------------------------------------------------------------- // Base DGL classes diff --git a/libs/dgl/CairoWidget.hpp b/libs/dgl/CairoWidget.hpp deleted file mode 100644 index 9ebfe8f..0000000 --- a/libs/dgl/CairoWidget.hpp +++ /dev/null @@ -1,208 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef DGL_CAIRO_WIDGET_HPP_INCLUDED -#define DGL_CAIRO_WIDGET_HPP_INCLUDED - -#include "Widget.hpp" - -#include <cairo.h> - -#include <cstdio> - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -class CairoWidget : public Widget -{ -public: - CairoWidget(Window& parent) - : Widget(parent), - fContext(nullptr), - fSurface(nullptr), - fTextureId(0) - { - } - - virtual void setWidth(int width) override - { - if (fArea.getWidth() == width) - return; - - Widget::setWidth(width); - _recreateSurface(); - } - - virtual void setHeight(int height) override - { - if (fArea.getHeight() == height) - return; - - Widget::setHeight(height); - _recreateSurface(); - } - - virtual void setSize(const Size<int>& size) override - { - if (fArea.getSize() == size) - return; - - Widget::setSize(size); - _recreateSurface(); - } - - void setSize(int width, int height) - { - setSize(Size<int>(width, height)); - } - -protected: - virtual void cairoDisplay(cairo_t* const context) = 0; - -private: - void onDisplay() override - { - // wait for sizing - if (fSurface == nullptr || fContext == nullptr) - { - printf("invalid surface\n"); - return; - } - - if (fTextureId == 0) - glGenTextures(1, &fTextureId); - if (fTextureId == 0) - { - // TODO: invalidate widget - printf("invalid texture\n"); - return; - } - -#if 1 - const int x = getX(); - const int y = getY(); - const int width = getWidth(); - const int height = getHeight(); - - // draw cairo stuff - cairoDisplay(fContext); - - // get cairo surface data (RGB24) - uchar* const surfaceData = cairo_image_surface_get_data(fSurface); - - // enable GL texture - glEnable(GL_TEXTURE_RECTANGLE_ARB); - - // set texture params - glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); - glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - - // bind texture to surface data - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, fTextureId); - glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, surfaceData); - - // draw the texture - -// glBegin(GL_QUADS); -// glTexCoord2f(0.0f, 0.0f); -// glVertex2i(x, y); -// -// glTexCoord2f(1.0f, 0.0f); -// glVertex2i(x+width, y); -// -// glTexCoord2f(1.0f, 1.0f); -// glVertex2i(x+width, y+height); -// -// glTexCoord2f(0.0f, 1.0f); -// glVertex2i(x, y+height); -// glEnd(); - - glBegin(GL_QUADS); - //glTexCoord2i(x, y); - glTexCoord2i(0, 0); - glVertex2i(x, y); - - //glTexCoord2i(x+width, y); - glTexCoord2i(width, 0); - glVertex2i(x+width, y); - - //glTexCoord2i(x+width, y+height); - glTexCoord2i(width, height); - glVertex2i(x+width, y+height); - - //glTexCoord2i(x, y+height); - glTexCoord2i(0, height); - glVertex2i(x, y+height); - glEnd(); - - // cleanup - glBindTexture(GL_TEXTURE_RECTANGLE_ARB, 0); - glDisable(GL_TEXTURE_RECTANGLE_ARB); -#endif - } - - void onClose() override - { - if (fContext != nullptr) - { - cairo_destroy(fContext); - fContext = nullptr; - } - - if (fSurface != nullptr) - { - cairo_surface_destroy(fSurface); - fSurface = nullptr; - } - - if (fTextureId != 0) - { - glDeleteTextures(1, &fTextureId); - fTextureId = 0; - } - } - - void _recreateSurface() - { - if (fContext != nullptr) - cairo_destroy(fContext); - - if (fSurface != nullptr) - cairo_surface_destroy(fSurface); - - fSurface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, fArea.getWidth(), fArea.getHeight()); - - if (fSurface != nullptr) - fContext = cairo_create(fSurface); - else - fContext = nullptr; - } - -private: - cairo_t* fContext; - cairo_surface_t* fSurface; - GLuint fTextureId; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(CairoWidget) -}; - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL - -#endif // DGL_CAIRO_WIDGET_HPP_INCLUDED diff --git a/libs/dgl/Color.hpp b/libs/dgl/Color.hpp index dbe2cd1..d47f354 100644 --- a/libs/dgl/Color.hpp +++ b/libs/dgl/Color.hpp @@ -28,7 +28,7 @@ START_NAMESPACE_DGL // TODO: create color from "#333" and "#112233" like strings /** - A color made from red, green, blue and alpha floating-point values in [0..1] range. + A color made from red, green, blue and alpha floating-point values in [0..1] range. */ struct Color { /** @@ -48,13 +48,13 @@ struct Color { Create a color from red, green, blue and alpha numeric values. Values must be in [0..255] range. */ - Color(const int red, const int green, const int blue, const int alpha = 255) noexcept; + Color(int red, int green, int blue, int alpha = 255) noexcept; /** Create a color from red, green, blue and alpha floating-point values. Values must in [0..1] range. */ - Color(const float red, const float green, const float blue, const float alpha = 1.0f) noexcept; + Color(float red, float green, float blue, float alpha = 1.0f) noexcept; /** Create a color by copying another color. @@ -65,26 +65,39 @@ struct Color { /** Create a color by linearly interpolating two other colors. */ - Color(const Color& color1, const Color& color2, const float u) noexcept; + Color(const Color& color1, const Color& color2, float u) noexcept; /** - Create a color specified by hue, saturation, lightness and alpha. - HSL values are all in [0..1] range, alpha in [0..255] range. + Create a color specified by hue, saturation and lightness. + Values must in [0..1] range. + */ + static Color fromHSL(float hue, float saturation, float lightness, float alpha = 1.0f); + + /** + Create a color from a HTML string like "#333" or "#112233". */ - static Color HSL(const float hue, const float saturation, const float lightness, const int alpha = 255); + static Color fromHTML(const char* rgb, float alpha = 1.0f); /** Linearly interpolate this color against another. */ - void interpolate(const Color& other, const float u) noexcept; + void interpolate(const Color& other, float u) noexcept; /** Check if this color matches another. + @note: Comparison is forced within 8-bit color values. */ + bool isEqual(const Color& color, bool withAlpha = true) noexcept; + bool isNotEqual(const Color& color, bool withAlpha = true) noexcept; bool operator==(const Color& color) noexcept; bool operator!=(const Color& color) noexcept; /** + Fix color bounds if needed. + */ + void fixBounds() noexcept; + + /** @internal Needed for NanoVG compatibility. */ diff --git a/libs/dgl/Geometry.hpp b/libs/dgl/Geometry.hpp index 117c0a9..725a15e 100644 --- a/libs/dgl/Geometry.hpp +++ b/libs/dgl/Geometry.hpp @@ -30,8 +30,12 @@ template<typename> class Triangle; template<typename> class Rectangle; // ----------------------------------------------------------------------- -// Point +/** + DGL Point class. + + This class describes a single point in space, defined by an X and Y value. + */ template<typename T> class Point { @@ -62,17 +66,17 @@ public: const T& getY() const noexcept; /** - Set X value as @a x. + Set X value to @a x. */ void setX(const T& x) noexcept; /** - Set Y value as @a y. + Set Y value to @a y. */ void setY(const T& y) noexcept; /** - Set X and Y values as @a x and @a y respectively. + Set X and Y values to @a x and @a y respectively. */ void setPos(const T& x, const T& y) noexcept; @@ -96,6 +100,11 @@ public: */ bool isZero() const noexcept; + /** + Return true if point is not (0, 0). + */ + bool isNotZero() const noexcept; + Point<T> operator+(const Point<T>& pos) noexcept; Point<T> operator-(const Point<T>& pos) noexcept; Point<T>& operator=(const Point<T>& pos) noexcept; @@ -113,8 +122,12 @@ private: }; // ----------------------------------------------------------------------- -// Size +/** + DGL Size class. + + This class describes a size, defined by a width and height value. + */ template<typename T> class Size { @@ -155,7 +168,7 @@ public: void setHeight(const T& height) noexcept; /** - Set size using @a width and @a height. + Set size to @a width and @a height. */ void setSize(const T& width, const T& height) noexcept; @@ -167,30 +180,43 @@ public: /** Grow size by @a multiplier. */ - void growBy(const T& multiplier) noexcept; + void growBy(double multiplier) noexcept; /** Shrink size by @a divider. */ - void shrinkBy(const T& divider) noexcept; + void shrinkBy(double divider) noexcept; /** Return true if size is null (0x0). + An null size is also invalid. */ bool isNull() const noexcept; /** Return true if size is not null (0x0). + A non-null size is still invalid if its width or height is negative. */ bool isNotNull() const noexcept; + /** + Return true if size is valid (width and height are higher than zero). + */ + bool isValid() const noexcept; + + /** + Return true if size is invalid (width or height are lower or equal to zero). + An invalid size might not be null under some circumstances. + */ + bool isInvalid() const noexcept; + Size<T> operator+(const Size<T>& size) noexcept; Size<T> operator-(const Size<T>& size) noexcept; Size<T>& operator=(const Size<T>& size) noexcept; Size<T>& operator+=(const Size<T>& size) noexcept; Size<T>& operator-=(const Size<T>& size) noexcept; - Size<T>& operator*=(const T& m) noexcept; - Size<T>& operator/=(const T& d) noexcept; + Size<T>& operator*=(double m) noexcept; + Size<T>& operator/=(double d) noexcept; bool operator==(const Size<T>& size) const noexcept; bool operator!=(const Size<T>& size) const noexcept; @@ -200,14 +226,18 @@ private: }; // ----------------------------------------------------------------------- -// Line +/** + DGL Line class. + + This class describes a line, defined by two points. + */ template<typename T> class Line { public: /** - Constructor for a null line ([0, 0] to [0, 0]). + Constructor for a null line ([0,0] to [0,0]). */ Line() noexcept; @@ -217,7 +247,7 @@ public: Line(const T& startX, const T& startY, const T& endX, const T& endY) noexcept; /** - Constructor using custom start X, start Y, end pos values. + Constructor using custom start X, start Y and end pos values. */ Line(const T& startX, const T& startY, const Point<T>& endPos) noexcept; @@ -267,17 +297,17 @@ public: const Point<T>& getEndPos() const noexcept; /** - Set start X value as @a x. + Set start X value to @a x. */ void setStartX(const T& x) noexcept; /** - Set start Y value as @a y. + Set start Y value to @a y. */ void setStartY(const T& y) noexcept; /** - Set start X and Y values as @a x and @a y respectively. + Set start X and Y values to @a x and @a y respectively. */ void setStartPos(const T& x, const T& y) noexcept; @@ -287,17 +317,17 @@ public: void setStartPos(const Point<T>& pos) noexcept; /** - Set end X value as @a x. + Set end X value to @a x. */ void setEndX(const T& x) noexcept; /** - Set end Y value as @a y. + Set end Y value to @a y. */ void setEndY(const T& y) noexcept; /** - Set end X and Y values as @a x and @a y respectively. + Set end X and Y values to @a x and @a y respectively. */ void setEndPos(const T& x, const T& y) noexcept; @@ -321,6 +351,16 @@ public: */ void draw(); + /** + Return true if line is null (start and end pos are equal). + */ + bool isNull() const noexcept; + + /** + Return true if line is not null (start and end pos are different). + */ + bool isNotNull() const noexcept; + Line<T>& operator=(const Line<T>& line) noexcept; bool operator==(const Line<T>& line) const noexcept; bool operator!=(const Line<T>& line) const noexcept; @@ -330,8 +370,15 @@ private: }; // ----------------------------------------------------------------------- -// Circle +/** + DGL Circle class. + + This class describes a circle, defined by position, size and a minimum of 3 segments. + + TODO: report if circle starts at top-left, bottom-right or center. + and size grows from which point? + */ template<typename T> class Circle { @@ -372,17 +419,17 @@ public: const Point<T>& getPos() const noexcept; /** - Set X value as @a x. + Set X value to @a x. */ void setX(const T& x) noexcept; /** - Set Y value as @a y. + Set Y value to @a y. */ void setY(const T& y) noexcept; /** - Set X and Y values as @a x and @a y respectively. + Set X and Y values to @a x and @a y respectively. */ void setPos(const T& x, const T& y) noexcept; @@ -435,12 +482,16 @@ private: // cached values float fTheta, fCos, fSin; - void _draw(const bool isOutline); + void _draw(const bool outline); }; // ----------------------------------------------------------------------- -// Triangle +/** + DGL Triangle class. + + This class describes a triangle, defined by 3 points. + */ template<typename T> class Triangle { @@ -475,6 +526,29 @@ public: */ void drawOutline(); + /** + Return true if triangle is null (all its points are equal). + An null triangle is also invalid. + */ + bool isNull() const noexcept; + + /** + Return true if triangle is not null (one its points is different from the others). + A non-null triangle is still invalid if two of its points are equal. + */ + bool isNotNull() const noexcept; + + /** + Return true if triangle is valid (all its points are different). + */ + bool isValid() const noexcept; + + /** + Return true if triangle is invalid (one or two of its points are equal). + An invalid triangle might not be null under some circumstances. + */ + bool isInvalid() const noexcept; + Triangle<T>& operator=(const Triangle<T>& tri) noexcept; bool operator==(const Triangle<T>& tri) const noexcept; bool operator!=(const Triangle<T>& tri) const noexcept; @@ -482,12 +556,16 @@ public: private: Point<T> fPos1, fPos2, fPos3; - void _draw(const bool isOutline); + void _draw(const bool outline); }; // ----------------------------------------------------------------------- -// Rectangle +/** + DGL Rectangle class. + + This class describes a rectangle, defined by a starting point and a size. + */ template<typename T> class Rectangle { @@ -605,12 +683,12 @@ public: /** Grow size by @a multiplier. */ - void growBy(const T& multiplier) noexcept; + void growBy(double multiplier) noexcept; /** Shrink size by @a divider. */ - void shrinkBy(const T& divider) noexcept; + void shrinkBy(double divider) noexcept; /** Set rectangle using @a pos and @a size. @@ -653,8 +731,8 @@ public: void drawOutline(); Rectangle<T>& operator=(const Rectangle<T>& rect) noexcept; - Rectangle<T>& operator*=(const T& m) noexcept; - Rectangle<T>& operator/=(const T& d) noexcept; + Rectangle<T>& operator*=(double m) noexcept; + Rectangle<T>& operator/=(double d) noexcept; bool operator==(const Rectangle<T>& size) const noexcept; bool operator!=(const Rectangle<T>& size) const noexcept; @@ -662,7 +740,7 @@ private: Point<T> fPos; Size<T> fSize; - void _draw(const bool isOutline); + void _draw(const bool outline); }; // ----------------------------------------------------------------------- diff --git a/libs/dgl/ImageAboutWindow.hpp b/libs/dgl/ImageAboutWindow.hpp index 5a72ea2..2ffc0de 100644 --- a/libs/dgl/ImageAboutWindow.hpp +++ b/libs/dgl/ImageAboutWindow.hpp @@ -43,7 +43,8 @@ protected: private: Image fImgBackground; - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageAboutWindow) + DISTRHO_DECLARE_NON_COPY_CLASS(ImageAboutWindow) + //DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(ImageAboutWindow) }; // ----------------------------------------------------------------------- diff --git a/libs/dgl/ImageKnob.hpp b/libs/dgl/ImageKnob.hpp index 3563824..2dd2669 100644 --- a/libs/dgl/ImageKnob.hpp +++ b/libs/dgl/ImageKnob.hpp @@ -41,15 +41,12 @@ public: virtual void imageKnobValueChanged(ImageKnob* imageKnob, float value) = 0; }; - explicit ImageKnob(Window& parent, const Image& image, Orientation orientation = Vertical, int id = 0) noexcept; - explicit ImageKnob(Widget* widget, const Image& image, Orientation orientation = Vertical, int id = 0) noexcept; + explicit ImageKnob(Window& parent, const Image& image, Orientation orientation = Vertical) noexcept; + explicit ImageKnob(Widget* widget, const Image& image, Orientation orientation = Vertical) noexcept; explicit ImageKnob(const ImageKnob& imageKnob); ImageKnob& operator=(const ImageKnob& imageKnob); ~ImageKnob() override; - int getId() const noexcept; - void setId(int id) noexcept; - float getValue() const noexcept; void setDefault(float def) noexcept; @@ -70,7 +67,6 @@ protected: private: Image fImage; - int fId; float fMinimum; float fMaximum; float fStep; @@ -89,11 +85,10 @@ private: Callback* fCallback; bool fIsImgVertical; - int fImgLayerSize; - int fImgLayerCount; - Rectangle<int> fKnobArea; - GLuint fTextureId; + uint fImgLayerSize; + uint fImgLayerCount; bool fIsReady; + GLuint fTextureId; float _logscale(float value) const; float _invlogscale(float value) const; diff --git a/libs/dgl/ImageSlider.hpp b/libs/dgl/ImageSlider.hpp index 613b25c..e6f2716 100644 --- a/libs/dgl/ImageSlider.hpp +++ b/libs/dgl/ImageSlider.hpp @@ -36,14 +36,11 @@ public: virtual void imageSliderValueChanged(ImageSlider* imageSlider, float value) = 0; }; - explicit ImageSlider(Window& parent, const Image& image, int id = 0) noexcept; - explicit ImageSlider(Widget* widget, const Image& image, int id = 0) noexcept; + explicit ImageSlider(Window& parent, const Image& image) noexcept; + explicit ImageSlider(Widget* widget, const Image& image) noexcept; explicit ImageSlider(const ImageSlider& imageSlider) noexcept; ImageSlider& operator=(const ImageSlider& imageSlider) noexcept; - int getId() const noexcept; - void setId(int id) noexcept; - float getValue() const noexcept; void setStartPos(const Point<int>& startPos) noexcept; @@ -65,7 +62,6 @@ protected: private: Image fImage; - int fId; float fMinimum; float fMaximum; float fStep; diff --git a/libs/dgl/ImageSwitch.hpp b/libs/dgl/ImageSwitch.hpp index bb963ac..172d61c 100644 --- a/libs/dgl/ImageSwitch.hpp +++ b/libs/dgl/ImageSwitch.hpp @@ -34,14 +34,11 @@ public: virtual void imageSwitchClicked(ImageSwitch* imageButton, bool down) = 0; }; - explicit ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown, int id = 0) noexcept; - explicit ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown, int id = 0) noexcept; + explicit ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown) noexcept; + explicit ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown) noexcept; explicit ImageSwitch(const ImageSwitch& imageSwitch) noexcept; ImageSwitch& operator=(const ImageSwitch& imageSwitch) noexcept; - int getId() const noexcept; - void setId(int id) noexcept; - bool isDown() const noexcept; void setDown(bool down) noexcept; @@ -55,7 +52,6 @@ private: Image fImageNormal; Image fImageDown; bool fIsDown; - int fId; Callback* fCallback; diff --git a/libs/dgl/NanoVG.hpp b/libs/dgl/NanoVG.hpp index 32d467d..1135f55 100644 --- a/libs/dgl/NanoVG.hpp +++ b/libs/dgl/NanoVG.hpp @@ -258,7 +258,7 @@ public: /** Destructor. */ - ~NanoVG(); + virtual ~NanoVG(); /** Get the NanoVG context. @@ -641,7 +641,7 @@ public: Creates font by loading it from the specified memory chunk. Returns handle to the font. */ - FontId createFontMem(const char* name, uchar* data, int ndata, bool freeData); + FontId createFontMem(const char* name, const uchar* data, int ndata, bool freeData); /** Finds a loaded font of specified name, and returns handle to it, or -1 if the font is not found. @@ -761,7 +761,8 @@ public: */ NanoWidget(Window& parent) : Widget(parent), - NanoVG() + NanoVG(), + leakDetector_NanoWidget() { setNeedsScaling(true); } diff --git a/libs/dgl/Widget.hpp b/libs/dgl/Widget.hpp index 86c42d4..faed6b5 100644 --- a/libs/dgl/Widget.hpp +++ b/libs/dgl/Widget.hpp @@ -26,6 +26,7 @@ START_NAMESPACE_DGL class App; class Window; +class StandaloneWindow; // ----------------------------------------------------------------------- @@ -52,12 +53,17 @@ class Widget public: /** Base event data. - @a mod The currently active modifiers. - @a time The timestamp (if any) of the currently-processing event. + @a mod The currently active keyboard modifiers, @see Modifier. + @a time The timestamp (if any). */ struct BaseEvent { - Modifier mod; + uint mod; uint32_t time; + + /** Constuctor */ + BaseEvent() noexcept : mod(0x0), time(0) {} + /** Destuctor */ + virtual ~BaseEvent() noexcept {} }; /** @@ -69,6 +75,12 @@ public: struct KeyboardEvent : BaseEvent { bool press; uint key; + + /** Constuctor */ + KeyboardEvent() noexcept + : BaseEvent(), + press(false), + key(0) {} }; /** @@ -79,7 +91,13 @@ public: */ struct SpecialEvent : BaseEvent { bool press; - Key key; + Key key; + + /** Constuctor */ + SpecialEvent() noexcept + : BaseEvent(), + press(false), + key(Key(0)) {} }; /** @@ -90,9 +108,16 @@ public: @see onMouse */ struct MouseEvent : BaseEvent { - int button; + int button; bool press; Point<int> pos; + + /** Constuctor */ + MouseEvent() noexcept + : BaseEvent(), + button(0), + press(false), + pos(0, 0) {} }; /** @@ -102,6 +127,11 @@ public: */ struct MotionEvent : BaseEvent { Point<int> pos; + + /** Constuctor */ + MotionEvent() noexcept + : BaseEvent(), + pos(0, 0) {} }; /** @@ -113,6 +143,12 @@ public: struct ScrollEvent : BaseEvent { Point<int> pos; Point<float> delta; + + /** Constuctor */ + ScrollEvent() noexcept + : BaseEvent(), + pos(0, 0), + delta(0.0f, 0.0f) {} }; /** @@ -124,6 +160,11 @@ public: struct ResizeEvent { Size<uint> size; Size<uint> oldSize; + + /** Constuctor */ + ResizeEvent() noexcept + : size(0, 0), + oldSize(0, 0) {} }; /** @@ -177,22 +218,22 @@ public: /** Set width. */ - virtual void setWidth(uint width) noexcept; + void setWidth(uint width) noexcept; /** Set height. */ - virtual void setHeight(uint height) noexcept; + void setHeight(uint height) noexcept; /** Set size using @a width and @a height values. */ - virtual void setSize(uint width, uint height) noexcept; + void setSize(uint width, uint height) noexcept; /** Set size. */ - virtual void setSize(const Size<uint>& size) noexcept; + void setSize(const Size<uint>& size) noexcept; /** Get absolute X. @@ -255,6 +296,18 @@ public: */ void repaint() noexcept; + /** + Get the Id associated with this widget. + @see setId + */ + uint getId() const noexcept; + + /** + Set an Id to be associated with this widget. + @see getId + */ + void setId(uint id) noexcept; + protected: /** A function called to draw the view contents with OpenGL. @@ -317,10 +370,10 @@ private: bool fNeedsFullViewport; bool fNeedsScaling; bool fVisible; + uint fId; Point<int> fAbsolutePos; Size<uint> fSize; - friend class CairoWidget; friend class Window; friend class StandaloneWindow; diff --git a/libs/dgl/Window.hpp b/libs/dgl/Window.hpp index 89e91de..37ae278 100644 --- a/libs/dgl/Window.hpp +++ b/libs/dgl/Window.hpp @@ -25,10 +25,48 @@ START_NAMESPACE_DGL class App; class Widget; +class StandaloneWindow; class Window { public: + /** + File browser options. + */ + struct FileBrowserOptions { + const char* startDir; + const char* title; + uint width; + uint height; + + /** + File browser buttons. + + 0 means hidden. + 1 means visible and unchecked. + 2 means visible and checked. + */ + struct Buttons { + uint listAllFiles; + uint showHidden; + uint showPlaces; + + /** Constuctor for default values */ + Buttons() + : listAllFiles(2), + showHidden(1), + showPlaces(1) {} + } buttons; + + /** Constuctor for default values */ + FileBrowserOptions() + : startDir(nullptr), + title(nullptr), + width(0), + height(0), + buttons() {} + }; + explicit Window(App& app); explicit Window(App& app, Window& parent); explicit Window(App& app, intptr_t parentId); @@ -42,6 +80,8 @@ public: void focus(); void repaint() noexcept; + bool openFileBrowser(const FileBrowserOptions& options); + bool isVisible() const noexcept; void setVisible(bool yesNo); @@ -54,9 +94,10 @@ public: void setSize(uint width, uint height); void setSize(Size<uint> size); + const char* getTitle() const noexcept; void setTitle(const char* title); - void setTransientWinId(intptr_t winId); + void setTransientWinId(uintptr_t winId); App& getApp() const noexcept; intptr_t getWindowId() const noexcept; @@ -70,6 +111,8 @@ protected: virtual void onReshape(uint width, uint height); virtual void onClose(); + virtual void fileBrowserSelected(const char* filename); + private: struct PrivateData; PrivateData* const pData; diff --git a/libs/dgl/ntk/NtkApp.hpp b/libs/dgl/ntk/NtkApp.hpp deleted file mode 100644 index 2c32d56..0000000 --- a/libs/dgl/ntk/NtkApp.hpp +++ /dev/null @@ -1,291 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef DGL_NTK_APP_HPP_INCLUDED -#define DGL_NTK_APP_HPP_INCLUDED - -#include "../Base.hpp" -#include "../../distrho/DistrhoUI.hpp" -#include "../../distrho/extra/d_thread.hpp" - -#ifdef override -# define override_defined -# undef override -#endif - -#include <list> -#include <FL/Fl.H> -#include <FL/Fl_Double_Window.H> -#include <FL/Fl_Shared_Image.H> -#include <FL/x.H> - -#ifdef override_defined -# define override -# undef override_defined -#endif - -struct ScopedDisplayLock { - ScopedDisplayLock() - { -#ifdef DISTRHO_OS_LINUX - XLockDisplay(fl_display); -#endif - } - - ~ScopedDisplayLock() - { -#ifdef DISTRHO_OS_LINUX - XUnlockDisplay(fl_display); -#endif - } -}; - -// ----------------------------------------------------------------------- - -namespace DISTRHO_NAMESPACE { - class UI; -} - -START_NAMESPACE_DGL - -class NtkWindow; - -typedef DISTRHO_NAMESPACE::Mutex d_Mutex; -typedef DISTRHO_NAMESPACE::MutexLocker d_MutexLocker; -typedef DISTRHO_NAMESPACE::Thread d_Thread; -typedef DISTRHO_NAMESPACE::UI d_UI; - -// ----------------------------------------------------------------------- - -/** - DGL compatible App class that uses NTK instead of OpenGL. - @see App - */ -class NtkApp : d_Thread -{ -public: - /** - Constructor. - */ - NtkApp() - : d_Thread("NtkApp"), - fWindows(), - fWindowMutex(), - fNextUI(), - fDoNextUI(false), - fInitialized(false) - { -#ifdef DISTRHO_OS_LINUX - //XInitThreads(); -#endif - - startThread(); - - for (; ! fInitialized;) - d_msleep(10); - } - - /** - Destructor. - */ - ~NtkApp() - { - stopThread(-1); - fWindows.clear(); - } - - /** - Idle function. - This calls does nothing. - */ - void idle() {} - - /** - Run the application event-loop until all Windows are closed. - @note: This function is meant for standalones only, *never* call this from plugins. - */ - void exec() - { - while (isThreadRunning() && ! shouldThreadExit()) - d_sleep(1); - } - - /** - Quit the application. - This stops the event-loop and closes all Windows. - */ - void quit() - { - signalThreadShouldExit(); - } - - /** - Check if the application is about to quit. - Returning true means there's no event-loop running at the moment. - */ - bool isQuiting() const noexcept - { - if (isThreadRunning() && ! shouldThreadExit()) - return false; - return true; - } - - // ------------------------------------------------------------------- - - /** - Create UI on our separate thread. - Blocks until the UI is created and returns it. - */ - d_UI* createUI(void* const func) - { - DISTRHO_SAFE_ASSERT_RETURN(isThreadRunning(), nullptr); - DISTRHO_SAFE_ASSERT_RETURN(! fDoNextUI, nullptr); - - fNextUI.create = true; - fNextUI.func = (NextUI::UiFunc)func; - fDoNextUI = true; - - for (; fDoNextUI;) - d_msleep(10); - - return fNextUI.ui; - } - - /** - Delete UI on our separate thread. - Blocks until the UI is deleted. - */ - void deleteUI(d_UI* const ui) - { - DISTRHO_SAFE_ASSERT_RETURN(! fDoNextUI,); - - fNextUI.create = false; - fNextUI.ui = ui; - fDoNextUI = true; - - if (isThreadRunning()) - { - for (; fDoNextUI;) - d_msleep(10); - } - else - { - fNextUI.run(); - fDoNextUI = false; - } - } - - // ------------------------------------------------------------------- - -private: - struct NextUI { - typedef d_UI* (*UiFunc)(); - - bool create; - - union { - UiFunc func; - d_UI* ui; - }; - - NextUI() - : create(false), - func(nullptr) {} - - void run(); - }; - - std::list<Fl_Double_Window*> fWindows; - d_Mutex fWindowMutex; - NextUI fNextUI; - volatile bool fDoNextUI; - volatile bool fInitialized; - - /** @internal used by NtkWindow. */ - void addWindow(Fl_Double_Window* const window) - { - DISTRHO_SAFE_ASSERT_RETURN(window != nullptr,); - - if (fWindows.size() == 0 && ! isThreadRunning()) - startThread(); - - const d_MutexLocker sl(fWindowMutex); - fWindows.push_back(window); - } - - /** @internal used by NtkWindow. */ - void removeWindow(Fl_Double_Window* const window) - { - DISTRHO_SAFE_ASSERT_RETURN(window != nullptr,); - - const d_MutexLocker sl(fWindowMutex); - fWindows.remove(window); - - if (fWindows.size() == 0) - signalThreadShouldExit(); - } - - /** @internal */ - void run() override - { - static bool initialized = false; - - if (! initialized) - { - initialized = true; - fl_register_images(); -#ifdef DISTRHO_OS_LINUX - fl_open_display(); -#endif - } - - fInitialized = true; - - for (; ! shouldThreadExit();) - { - if (fDoNextUI) - { - const ScopedDisplayLock csdl; - fNextUI.run(); - fDoNextUI = false; - } - - const ScopedDisplayLock csdl; - Fl::check(); - Fl::flush(); - - d_msleep(20); - } - - const d_MutexLocker sl(fWindowMutex); - const ScopedDisplayLock csdl; - - for (std::list<Fl_Double_Window*>::reverse_iterator rit = fWindows.rbegin(), rite = fWindows.rend(); rit != rite; ++rit) - { - Fl_Double_Window* const window(*rit); - window->hide(); - } - } - - friend class NtkWindow; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NtkApp) -}; -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL - -#endif // DGL_NTK_APP_HPP_INCLUDED diff --git a/libs/dgl/ntk/NtkWidget.hpp b/libs/dgl/ntk/NtkWidget.hpp deleted file mode 100644 index 2247be3..0000000 --- a/libs/dgl/ntk/NtkWidget.hpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef DGL_NTK_WIDGET_HPP_INCLUDED -#define DGL_NTK_WIDGET_HPP_INCLUDED - -#include "NtkWindow.hpp" - -START_NAMESPACE_DGL - -// ----------------------------------------------------------------------- - -/** - DGL compatible Widget class that uses NTK instead of OpenGL. - @see Widget - */ -class NtkWidget : public Fl_Double_Window -{ -public: - /** - Constructor. - */ - explicit NtkWidget(NtkWindow& parent) - : Fl_Double_Window(100, 100), - fParent(parent) - { - fParent.add(this); - show(); - } - - /** - Destructor. - */ - ~NtkWidget() override - { - hide(); - fParent.remove(this); - } - - /** - Check if this widget is visible within its parent window. - Invisible widgets do not receive events except resize. - */ - bool isVisible() const - { - return visible(); - } - - /** - Set widget visible (or not) according to @a yesNo. - */ - void setVisible(bool yesNo) - { - if (yesNo) - show(); - else - hide(); - } - - /** - Get width. - */ - int getWidth() const - { - return w(); - } - - /** - Get height. - */ - int getHeight() const - { - return h(); - } - - /** - Set width. - */ - void setWidth(int width) - { - resize(x(), y(), width, h()); - } - - /** - Set height. - */ - void setHeight(int height) - { - resize(x(), y(), w(), height); - } - - /** - Set size using @a width and @a height values. - */ - void setSize(int width, int height) - { - resize(x(), y(), width, height); - } - - /** - Get absolute X. - */ - int getAbsoluteX() const - { - return x(); - } - - /** - Get absolute Y. - */ - int getAbsoluteY() const - { - return y(); - } - - /** - Set absolute X. - */ - void setAbsoluteX(int x) - { - resize(x, y(), w(), h()); - } - - /** - Set absolute Y. - */ - void setAbsoluteY(int y) - { - resize(x(), y, w(), h()); - } - - /** - Set absolute position using @a x and @a y values. - */ - void setAbsolutePos(int x, int y) - { - resize(x, y, w(), h()); - } - - /** - Get this widget's window application. - Same as calling getParentWindow().getApp(). - */ - NtkApp& getParentApp() const noexcept - { - return fParent.getApp(); - } - - /** - Get parent window, as passed in the constructor. - */ - NtkWindow& getParentWindow() const noexcept - { - return fParent; - } - - /** - Check if this widget contains the point defined by @a x and @a y. - */ - bool contains(int x, int y) const - { - return (x >= 0 && y >= 0 && x < w() && y < h()); - } - - /** - Tell this widget's window to repaint itself. - */ - void repaint() - { - redraw(); - } - -protected: - /** @internal used for DGL compatibility. */ - void setNeedsFullViewport(bool) noexcept {} - /** @internal used for DGL compatibility. */ - void setNeedsScaling(bool) noexcept {} - -private: - NtkWindow& fParent; - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NtkWidget) -}; - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL - -#endif // DGL_NTK_WIDGET_HPP_INCLUDED diff --git a/libs/dgl/ntk/NtkWindow.hpp b/libs/dgl/ntk/NtkWindow.hpp deleted file mode 100644 index 47e9f10..0000000 --- a/libs/dgl/ntk/NtkWindow.hpp +++ /dev/null @@ -1,215 +0,0 @@ -/* - * DISTRHO Plugin Framework (DPF) - * Copyright (C) 2012-2014 Filipe Coelho <falktx@falktx.com> - * - * Permission to use, copy, modify, and/or distribute this software for any purpose with - * or without fee is hereby granted, provided that the above copyright notice and this - * permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD - * TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN - * NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL - * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER - * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN - * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef DGL_NTK_WINDOW_HPP_INCLUDED -#define DGL_NTK_WINDOW_HPP_INCLUDED - -#include "NtkApp.hpp" - -START_NAMESPACE_DGL - -class NtkWidget; - -// ----------------------------------------------------------------------- - -class NtkWindow : public Fl_Double_Window -{ -public: - explicit NtkWindow(NtkApp& app) - : Fl_Double_Window(100, 100), - fApp(app), - fIsVisible(false), - fUsingEmbed(false), - fParent(nullptr) {} - - explicit NtkWindow(NtkApp& app, NtkWindow& parent) - : Fl_Double_Window(100, 100), - fApp(app), - fIsVisible(false), - fUsingEmbed(false), - fParent(&parent) {} - - explicit NtkWindow(NtkApp& app, intptr_t parentId) - : Fl_Double_Window(100, 100), - fApp(app), - fIsVisible(parentId != 0), - fUsingEmbed(parentId != 0), - fParent(nullptr) - { - if (fUsingEmbed) - { - fl_embed(this, (Window)parentId); - Fl_Double_Window::show(); - fApp.addWindow(this); - } - } - - ~NtkWindow() override - { - if (fUsingEmbed) - { - fApp.removeWindow(this); - Fl_Double_Window::hide(); - } - } - - void show() override - { - if (fUsingEmbed || fIsVisible) - return; - - Fl_Double_Window::show(); - fApp.addWindow(this); - fIsVisible = true; - - if (fParent != nullptr) - setTransientWinId((intptr_t)fl_xid(fParent)); - } - - void hide() override - { - if (fUsingEmbed || ! fIsVisible) - return; - - fIsVisible = false; - fApp.removeWindow(this); - Fl_Double_Window::hide(); - } - - void close() - { - hide(); - } - - bool isVisible() const - { - return visible(); - } - - void setVisible(bool yesNo) - { - if (yesNo) - show(); - else - hide(); - } - - bool isResizable() const - { - // TODO - return false; - } - - void setResizable(bool /*yesNo*/) - { - // TODO - } - - int getWidth() const noexcept - { - return w(); - } - - int getHeight() const noexcept - { - return h(); - } - - void setSize(uint width, uint height) - { - resize(x(), y(), width, height); - } - - void setTitle(const char* title) - { - label(title); - } - - void setTransientWinId(intptr_t winId) - { - DISTRHO_SAFE_ASSERT_RETURN(winId != 0,); - -#ifdef DISTRHO_OS_LINUX - DISTRHO_SAFE_ASSERT_RETURN(fl_display != nullptr,); - - const ::Window ourWindow(fl_xid(this)); - DISTRHO_SAFE_ASSERT_RETURN(ourWindow != 0,); - - XSetTransientForHint(fl_display, ourWindow, winId); -#endif - } - - NtkApp& getApp() const noexcept - { - return fApp; - } - - intptr_t getWindowId() const - { - return (intptr_t)fl_xid(this); - } - - void addIdleCallback(IdleCallback* const callback) - { - DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,); - - if (fIdleCallbacks.size() == 0) - Fl::add_idle(_idleHandler, this); - - fIdleCallbacks.push_back(callback); - } - - void removeIdleCallback(IdleCallback* const callback) - { - DISTRHO_SAFE_ASSERT_RETURN(callback != nullptr,); - - fIdleCallbacks.remove(callback); - - if (fIdleCallbacks.size() == 0) - Fl::remove_idle(_idleHandler, this); - } - -private: - NtkApp& fApp; - bool fIsVisible; - bool fUsingEmbed; - - // transient parent, may be null - NtkWindow* const fParent; - - std::list<IdleCallback*> fIdleCallbacks; - - friend class NtkWidget; - - static void _idleHandler(void* data) - { - NtkWindow* const self((NtkWindow*)data); - - for (std::list<IdleCallback*>::iterator it=self->fIdleCallbacks.begin(), ite=self->fIdleCallbacks.end(); it != ite; ++it) - { - IdleCallback* const idleCallback(*it); - idleCallback->idleCallback(); - } - } - - DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NtkWindow) -}; - -// ----------------------------------------------------------------------- - -END_NAMESPACE_DGL - -#endif // DGL_NTK_WINDOW_HPP_INCLUDED diff --git a/libs/dgl/src/Color.cpp b/libs/dgl/src/Color.cpp index 46b7ad3..57b8c07 100644 --- a/libs/dgl/src/Color.cpp +++ b/libs/dgl/src/Color.cpp @@ -22,51 +22,66 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -Color::Color() noexcept - : red(1.0f), green(1.0f), blue(1.0f), alpha(1.0f) {} - -Color::Color(const int r, const int g, const int b, const int a) noexcept - : red(static_cast<float>(r)/255.0f), green(static_cast<float>(g)/255.0f), blue(static_cast<float>(b)/255.0f), alpha(static_cast<float>(a)/255.0f) {} - -Color::Color(const float r, const float g, const float b, const float a) noexcept - : red(r), green(g), blue(b), alpha(a) {} - -Color::Color(const Color& color) noexcept - : red(color.red), green(color.green), blue(color.blue), alpha(color.alpha) {} - -Color::Color(const Color& color1, const Color& color2, const float u) noexcept - : red(color1.red), green(color1.green), blue(color1.blue), alpha(color1.alpha) +static void fixRange(float& value) { - interpolate(color2, u); + /**/ if (value < 0.0f) + value = 0.0f; + else if (value > 1.0f) + value = 1.0f; } -void Color::interpolate(const Color& other, const float u) noexcept +static float getFixedRange(const float& value) { - const float u2 = (u < 0.0f) ? 0.0f : ((u > 1.0f) ? 1.0f : u); - const float oneMinusU = 1.0f - u; + if (value <= 0.0f) + return 0.0f; + if (value >= 1.0f) + return 1.0f; + return value; +} - red = red * oneMinusU + other.red * u2; - green = green * oneMinusU + other.green * u2; - blue = blue * oneMinusU + other.blue * u2; - alpha = alpha * oneMinusU + other.alpha * u2; +static uchar getFixedRange2(const float& value) +{ + const float value2(getFixedRange(value)*255.0f); + if (value2 <= 0.0f) + return 0; + if (value2 >= 255.0f) + return 255; + return static_cast<uchar>(value2); } -Color Color::HSL(const float hue, const float saturation, const float lightness, const int alpha) +// ----------------------------------------------------------------------- + +Color::Color() noexcept + : red(1.0f), + green(1.0f), + blue(1.0f), + alpha(1.0f) {} + +Color::Color(int r, int g, int b, int a) noexcept + : red(static_cast<float>(r)/255.0f), + green(static_cast<float>(g)/255.0f), + blue(static_cast<float>(b)/255.0f), + alpha(static_cast<float>(a)/255.0f) { - return nvgHSLA(hue, saturation, lightness, alpha); + fixBounds(); } -Color::Color(const NVGcolor& c) noexcept - : red(c.r), green(c.g), blue(c.b), alpha(c.a) {} +Color::Color(float r, float g, float b, float a) noexcept + : red(r), + green(g), + blue(b), + alpha(a) +{ + fixBounds(); +} -Color::operator NVGcolor() const noexcept +Color::Color(const Color& color) noexcept + : red(color.red), + green(color.green), + blue(color.blue), + alpha(color.alpha) { - NVGcolor nc; - nc.r = red; - nc.g = green; - nc.b = blue; - nc.a = alpha; - return nc; + fixBounds(); } Color& Color::operator=(const Color& color) noexcept @@ -75,17 +90,154 @@ Color& Color::operator=(const Color& color) noexcept green = color.green; blue = color.blue; alpha = color.alpha; + fixBounds(); return *this; } +Color::Color(const Color& color1, const Color& color2, float u) noexcept + : red(color1.red), + green(color1.green), + blue(color1.blue), + alpha(color1.alpha) +{ + interpolate(color2, u); +} + +Color Color::fromHSL(float hue, float saturation, float lightness, float alpha) +{ + return nvgHSLA(hue, saturation, lightness, static_cast<uchar>(getFixedRange(alpha)*255.0f)); +} + +Color Color::fromHTML(const char* rgb, float alpha) +{ + Color fallback; + DISTRHO_SAFE_ASSERT_RETURN(rgb != nullptr && rgb[0] != '\0', fallback); + + if (rgb[0] == '#') ++rgb; + DISTRHO_SAFE_ASSERT_RETURN(rgb[0] != '\0', fallback); + + std::size_t rgblen(std::strlen(rgb)); + DISTRHO_SAFE_ASSERT_RETURN(rgblen == 3 || rgblen == 6, fallback); + + char rgbtmp[3] = { '\0', '\0', '\0' }; + int r, g, b; + + if (rgblen == 3) + { + rgbtmp[0] = rgb[0]; + r = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + + rgbtmp[0] = rgb[1]; + g = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + + rgbtmp[0] = rgb[2]; + b = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + } + else + { + rgbtmp[0] = rgb[0]; + rgbtmp[1] = rgb[1]; + r = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + + rgbtmp[0] = rgb[2]; + rgbtmp[1] = rgb[3]; + g = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + + rgbtmp[0] = rgb[4]; + rgbtmp[1] = rgb[5]; + b = static_cast<int>(std::strtol(rgbtmp, nullptr, 16)); + } + + return Color(r, g, b, static_cast<int>(getFixedRange(alpha)*255.0f)); +} + +void Color::interpolate(const Color& other, float u) noexcept +{ + fixRange(u); + const float oneMinusU(1.0f - u); + + red = red * oneMinusU + other.red * u; + green = green * oneMinusU + other.green * u; + blue = blue * oneMinusU + other.blue * u; + alpha = alpha * oneMinusU + other.alpha * u; + + fixBounds(); +} + +// ----------------------------------------------------------------------- + +bool Color::isEqual(const Color& color, bool withAlpha) noexcept +{ + const uchar r1 = getFixedRange2(rgba[0]); + const uchar g1 = getFixedRange2(rgba[1]); + const uchar b1 = getFixedRange2(rgba[2]); + const uchar a1 = getFixedRange2(rgba[3]); + + const uchar r2 = getFixedRange2(color.rgba[0]); + const uchar g2 = getFixedRange2(color.rgba[1]); + const uchar b2 = getFixedRange2(color.rgba[2]); + const uchar a2 = getFixedRange2(color.rgba[3]); + + if (withAlpha) + return (r1 == r2 && g1 == g2 && b1 == b2 && a1 == a2); + else + return (r1 == r2 && g1 == g2 && b1 == b2); +} + +bool Color::isNotEqual(const Color& color, bool withAlpha) noexcept +{ + const uchar r1 = getFixedRange2(rgba[0]); + const uchar g1 = getFixedRange2(rgba[1]); + const uchar b1 = getFixedRange2(rgba[2]); + const uchar a1 = getFixedRange2(rgba[3]); + + const uchar r2 = getFixedRange2(color.rgba[0]); + const uchar g2 = getFixedRange2(color.rgba[1]); + const uchar b2 = getFixedRange2(color.rgba[2]); + const uchar a2 = getFixedRange2(color.rgba[3]); + + if (withAlpha) + return (r1 != r2 || g1 != g2 || b1 != b2 || a1 != a2); + else + return (r1 != r2 || g1 != g2 || b1 != b2); +} + bool Color::operator==(const Color& color) noexcept { - return (red == color.red && green == color.green && blue == color.blue && alpha == color.alpha); + return isEqual(color, true); } bool Color::operator!=(const Color& color) noexcept { - return (red != color.red || green != color.green || blue != color.blue || alpha != color.alpha); + return isNotEqual(color, true); +} + +// ----------------------------------------------------------------------- + +void Color::fixBounds() noexcept +{ + fixRange(red); + fixRange(green); + fixRange(blue); + fixRange(alpha); +} + +// ----------------------------------------------------------------------- + +Color::Color(const NVGcolor& c) noexcept + : red(c.r), green(c.g), blue(c.b), alpha(c.a) +{ + fixBounds(); +} + +Color::operator NVGcolor() const noexcept +{ + NVGcolor nc; + nc.r = red; + nc.g = green; + nc.b = blue; + nc.a = alpha; + return nc; } // ----------------------------------------------------------------------- diff --git a/libs/dgl/src/Geometry.cpp b/libs/dgl/src/Geometry.cpp index a7565fa..6964816 100644 --- a/libs/dgl/src/Geometry.cpp +++ b/libs/dgl/src/Geometry.cpp @@ -99,6 +99,12 @@ bool Point<T>::isZero() const noexcept } template<typename T> +bool Point<T>::isNotZero() const noexcept +{ + return fX != 0 || fY != 0; +} + +template<typename T> Point<T> Point<T>::operator+(const Point<T>& pos) noexcept { return Point<T>(fX+pos.fX, fY+pos.fY); @@ -203,17 +209,17 @@ void Size<T>::setSize(const Size<T>& size) noexcept } template<typename T> -void Size<T>::growBy(const T& multiplier) noexcept +void Size<T>::growBy(double multiplier) noexcept { - fWidth = static_cast<T>(fWidth*multiplier); - fHeight = static_cast<T>(fHeight*multiplier); + fWidth = static_cast<T>(static_cast<double>(fWidth)*multiplier); + fHeight = static_cast<T>(static_cast<double>(fHeight)*multiplier); } template<typename T> -void Size<T>::shrinkBy(const T& divider) noexcept +void Size<T>::shrinkBy(double divider) noexcept { - fWidth = static_cast<T>(fWidth/divider); - fHeight = static_cast<T>(fHeight/divider); + fWidth = static_cast<T>(static_cast<double>(fWidth)/divider); + fHeight = static_cast<T>(static_cast<double>(fHeight)/divider); } template<typename T> @@ -228,6 +234,17 @@ bool Size<T>::isNotNull() const noexcept return fWidth != 0 || fHeight != 0; } +template<typename T> +bool Size<T>::isValid() const noexcept +{ + return fWidth > 1 && fHeight > 1; +} + +template<typename T> +bool Size<T>::isInvalid() const noexcept +{ + return fWidth <= 0 || fHeight <= 0; +} template<typename T> Size<T> Size<T>::operator+(const Size<T>& size) noexcept @@ -266,18 +283,18 @@ Size<T>& Size<T>::operator-=(const Size<T>& size) noexcept } template<typename T> -Size<T>& Size<T>::operator*=(const T& m) noexcept +Size<T>& Size<T>::operator*=(double m) noexcept { - fWidth = static_cast<T>(fWidth*m); - fHeight = static_cast<T>(fHeight*m); + fWidth = static_cast<T>(static_cast<double>(fWidth)*m); + fHeight = static_cast<T>(static_cast<double>(fHeight)*m); return *this; } template<typename T> -Size<T>& Size<T>::operator/=(const T& d) noexcept +Size<T>& Size<T>::operator/=(double d) noexcept { - fWidth = static_cast<T>(fWidth/d); - fHeight = static_cast<T>(fHeight/d); + fWidth = static_cast<T>(static_cast<double>(fWidth)/d); + fHeight = static_cast<T>(static_cast<double>(fHeight)/d); return *this; } @@ -427,17 +444,31 @@ void Line<T>::moveBy(const Point<T>& pos) noexcept template<typename T> void Line<T>::draw() { + DISTRHO_SAFE_ASSERT_RETURN(fPosStart != fPosEnd,); + glBegin(GL_LINES); { - glVertex2i(fPosStart.fX, fPosStart.fY); - glVertex2i(fPosEnd.fX, fPosEnd.fY); + glVertex2d(fPosStart.fX, fPosStart.fY); + glVertex2d(fPosEnd.fX, fPosEnd.fY); } glEnd(); } template<typename T> +bool Line<T>::isNull() const noexcept +{ + return fPosStart == fPosEnd; +} + +template<typename T> +bool Line<T>::isNotNull() const noexcept +{ + return fPosStart != fPosEnd; +} + +template<typename T> Line<T>& Line<T>::operator=(const Line<T>& line) noexcept { fPosStart = line.fPosStart; @@ -610,24 +641,23 @@ Circle<T>& Circle<T>::operator=(const Circle<T>& cir) noexcept template<typename T> bool Circle<T>::operator==(const Circle<T>& cir) const noexcept { - return (fPos == cir.fPos && fSize == cir.fSize && fNumSegments == cir.fNumSegments); + return (fPos == cir.fPos && d_isEqual(fSize, cir.fSize) && fNumSegments == cir.fNumSegments); } template<typename T> bool Circle<T>::operator!=(const Circle<T>& cir) const noexcept { - return (fPos != cir.fPos || fSize != cir.fSize || fNumSegments != cir.fNumSegments); + return (fPos != cir.fPos || d_isNotEqual(fSize, cir.fSize) || fNumSegments != cir.fNumSegments); } template<typename T> -void Circle<T>::_draw(const bool isOutline) +void Circle<T>::_draw(const bool outline) { - if (fNumSegments < 3 || fSize <= 0.0f) - return; + DISTRHO_SAFE_ASSERT_RETURN(fNumSegments >= 3 && fSize > 0.0f,); - float t, x = fSize, y = 0; + float t, x = fSize, y = 0.0f; - glBegin(isOutline ? GL_LINE_LOOP : GL_POLYGON); + glBegin(outline ? GL_LINE_LOOP : GL_POLYGON); for (uint i=0; i<fNumSegments; ++i) { @@ -681,6 +711,30 @@ void Triangle<T>::drawOutline() } template<typename T> +bool Triangle<T>::isNull() const noexcept +{ + return fPos1 == fPos2 && fPos1 == fPos3; +} + +template<typename T> +bool Triangle<T>::isNotNull() const noexcept +{ + return fPos1 != fPos2 || fPos1 != fPos3; +} + +template<typename T> +bool Triangle<T>::isValid() const noexcept +{ + return fPos1 != fPos2 && fPos1 != fPos3; +} + +template<typename T> +bool Triangle<T>::isInvalid() const noexcept +{ + return fPos1 == fPos2 || fPos1 == fPos3; +} + +template<typename T> Triangle<T>& Triangle<T>::operator=(const Triangle<T>& tri) noexcept { fPos1 = tri.fPos1; @@ -702,14 +756,16 @@ bool Triangle<T>::operator!=(const Triangle<T>& tri) const noexcept } template<typename T> -void Triangle<T>::_draw(const bool isOutline) +void Triangle<T>::_draw(const bool outline) { - glBegin(isOutline ? GL_LINE_LOOP : GL_TRIANGLES); + DISTRHO_SAFE_ASSERT_RETURN(fPos1 != fPos2 && fPos1 != fPos3,); + + glBegin(outline ? GL_LINE_LOOP : GL_TRIANGLES); { - glVertex2i(fPos1.fX, fPos1.fY); - glVertex2i(fPos2.fX, fPos2.fY); - glVertex2i(fPos3.fX, fPos3.fY); + glVertex2d(fPos1.fX, fPos1.fY); + glVertex2d(fPos2.fX, fPos2.fY); + glVertex2d(fPos3.fX, fPos3.fY); } glEnd(); @@ -847,13 +903,13 @@ void Rectangle<T>::setSize(const Size<T>& size) noexcept } template<typename T> -void Rectangle<T>::growBy(const T& multiplier) noexcept +void Rectangle<T>::growBy(double multiplier) noexcept { fSize.growBy(multiplier); } template<typename T> -void Rectangle<T>::shrinkBy(const T& divider) noexcept +void Rectangle<T>::shrinkBy(double divider) noexcept { fSize.shrinkBy(divider); } @@ -917,14 +973,14 @@ Rectangle<T>& Rectangle<T>::operator=(const Rectangle<T>& rect) noexcept } template<typename T> -Rectangle<T>& Rectangle<T>::operator*=(const T& m) noexcept +Rectangle<T>& Rectangle<T>::operator*=(double m) noexcept { fSize *= m; return *this; } template<typename T> -Rectangle<T>& Rectangle<T>::operator/=(const T& d) noexcept +Rectangle<T>& Rectangle<T>::operator/=(double d) noexcept { fSize /= d; return *this; @@ -943,22 +999,24 @@ bool Rectangle<T>::operator!=(const Rectangle<T>& rect) const noexcept } template<typename T> -void Rectangle<T>::_draw(const bool isOutline) +void Rectangle<T>::_draw(const bool outline) { - glBegin(isOutline ? GL_LINE_LOOP : GL_QUADS); + DISTRHO_SAFE_ASSERT_RETURN(fSize.isValid(),); + + glBegin(outline ? GL_LINE_LOOP : GL_QUADS); { glTexCoord2f(0.0f, 0.0f); - glVertex2i(fPos.fX, fPos.fY); + glVertex2d(fPos.fX, fPos.fY); glTexCoord2f(1.0f, 0.0f); - glVertex2i(fPos.fX+fSize.fWidth, fPos.fY); + glVertex2d(fPos.fX+fSize.fWidth, fPos.fY); glTexCoord2f(1.0f, 1.0f); - glVertex2i(fPos.fX+fSize.fWidth, fPos.fY+fSize.fHeight); + glVertex2d(fPos.fX+fSize.fWidth, fPos.fY+fSize.fHeight); glTexCoord2f(0.0f, 1.0f); - glVertex2i(fPos.fX, fPos.fY+fSize.fHeight); + glVertex2d(fPos.fX, fPos.fY+fSize.fHeight); } glEnd(); diff --git a/libs/dgl/src/Image.cpp b/libs/dgl/src/Image.cpp index 55ee0af..74dceed 100644 --- a/libs/dgl/src/Image.cpp +++ b/libs/dgl/src/Image.cpp @@ -152,12 +152,14 @@ void Image::drawAt(const Point<int>& pos) glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fSize.getWidth(), fSize.getHeight(), 0, fFormat, fType, fRawData); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + static_cast<GLsizei>(fSize.getWidth()), static_cast<GLsizei>(fSize.getHeight()), 0, + fFormat, fType, fRawData); fIsReady = true; } - Rectangle<int>(pos, fSize.getWidth(), fSize.getHeight()).draw(); + Rectangle<int>(pos, static_cast<int>(fSize.getWidth()), static_cast<int>(fSize.getHeight())).draw(); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); diff --git a/libs/dgl/src/ImageAboutWindow.cpp b/libs/dgl/src/ImageAboutWindow.cpp index a12aa57..9e90f29 100644 --- a/libs/dgl/src/ImageAboutWindow.cpp +++ b/libs/dgl/src/ImageAboutWindow.cpp @@ -23,8 +23,8 @@ START_NAMESPACE_DGL ImageAboutWindow::ImageAboutWindow(Window& parent, const Image& image) : Window(parent.getApp(), parent), Widget((Window&)*this), - fImgBackground(image), - leakDetector_ImageAboutWindow() + fImgBackground(image)/*, + leakDetector_ImageAboutWindow()*/ { Window::setResizable(false); Window::setSize(static_cast<uint>(image.getWidth()), static_cast<uint>(image.getHeight())); @@ -34,8 +34,8 @@ ImageAboutWindow::ImageAboutWindow(Window& parent, const Image& image) ImageAboutWindow::ImageAboutWindow(Widget* widget, const Image& image) : Window(widget->getParentApp(), widget->getParentWindow()), Widget((Window&)*this), - fImgBackground(image), - leakDetector_ImageAboutWindow() + fImgBackground(image)/*, + leakDetector_ImageAboutWindow()*/ { Window::setResizable(false); Window::setSize(static_cast<uint>(image.getWidth()), static_cast<uint>(image.getHeight())); diff --git a/libs/dgl/src/ImageKnob.cpp b/libs/dgl/src/ImageKnob.cpp index 0fc8c69..57dae6a 100644 --- a/libs/dgl/src/ImageKnob.cpp +++ b/libs/dgl/src/ImageKnob.cpp @@ -22,10 +22,9 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -ImageKnob::ImageKnob(Window& parent, const Image& image, Orientation orientation, int id) noexcept +ImageKnob::ImageKnob(Window& parent, const Image& image, Orientation orientation) noexcept : Widget(parent), fImage(image), - fId(id), fMinimum(0.0f), fMaximum(1.0f), fStep(0.0f), @@ -43,19 +42,17 @@ ImageKnob::ImageKnob(Window& parent, const Image& image, Orientation orientation fIsImgVertical(image.getHeight() > image.getWidth()), fImgLayerSize(fIsImgVertical ? image.getWidth() : image.getHeight()), fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerSize : image.getWidth()/fImgLayerSize), - fKnobArea(0, 0, fImgLayerSize, fImgLayerSize), - fTextureId(0), fIsReady(false), + fTextureId(0), leakDetector_ImageKnob() { glGenTextures(1, &fTextureId); setSize(fImgLayerSize, fImgLayerSize); } -ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation, int id) noexcept +ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation) noexcept : Widget(widget->getParentWindow()), fImage(image), - fId(id), fMinimum(0.0f), fMaximum(1.0f), fStep(0.0f), @@ -73,9 +70,8 @@ ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation fIsImgVertical(image.getHeight() > image.getWidth()), fImgLayerSize(fIsImgVertical ? image.getWidth() : image.getHeight()), fImgLayerCount(fIsImgVertical ? image.getHeight()/fImgLayerSize : image.getWidth()/fImgLayerSize), - fKnobArea(0, 0, fImgLayerSize, fImgLayerSize), - fTextureId(0), fIsReady(false), + fTextureId(0), leakDetector_ImageKnob() { glGenTextures(1, &fTextureId); @@ -85,7 +81,6 @@ ImageKnob::ImageKnob(Widget* widget, const Image& image, Orientation orientation ImageKnob::ImageKnob(const ImageKnob& imageKnob) : Widget(imageKnob.getParentWindow()), fImage(imageKnob.fImage), - fId(imageKnob.fId), fMinimum(imageKnob.fMinimum), fMaximum(imageKnob.fMaximum), fStep(imageKnob.fStep), @@ -103,9 +98,8 @@ ImageKnob::ImageKnob(const ImageKnob& imageKnob) fIsImgVertical(imageKnob.fIsImgVertical), fImgLayerSize(imageKnob.fImgLayerSize), fImgLayerCount(imageKnob.fImgLayerCount), - fKnobArea(imageKnob.fKnobArea), - fTextureId(0), fIsReady(false), + fTextureId(0), leakDetector_ImageKnob() { glGenTextures(1, &fTextureId); @@ -115,7 +109,6 @@ ImageKnob::ImageKnob(const ImageKnob& imageKnob) ImageKnob& ImageKnob::operator=(const ImageKnob& imageKnob) { fImage = imageKnob.fImage; - fId = imageKnob.fId; fMinimum = imageKnob.fMinimum; fMaximum = imageKnob.fMaximum; fStep = imageKnob.fStep; @@ -133,7 +126,6 @@ ImageKnob& ImageKnob::operator=(const ImageKnob& imageKnob) fIsImgVertical = imageKnob.fIsImgVertical; fImgLayerSize = imageKnob.fImgLayerSize; fImgLayerCount = imageKnob.fImgLayerCount; - fKnobArea = imageKnob.fKnobArea; fIsReady = false; if (fTextureId != 0) @@ -157,16 +149,6 @@ ImageKnob::~ImageKnob() } } -int ImageKnob::getId() const noexcept -{ - return fId; -} - -void ImageKnob::setId(int id) noexcept -{ - fId = id; -} - float ImageKnob::getValue() const noexcept { return fValue; @@ -220,12 +202,12 @@ void ImageKnob::setStep(float step) noexcept // NOTE: value is assumed to be scaled if using log void ImageKnob::setValue(float value, bool sendCallback) noexcept { - if (fValue == value) + if (d_isEqual(fValue, value)) return; fValue = value; - if (fStep == 0.0f) + if (d_isZero(fStep)) fValueTmp = value; if (fRotationAngle == 0) @@ -288,36 +270,44 @@ void ImageKnob::onDisplay() glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - int imageDataOffset = 0; + uint imageDataOffset = 0; if (fRotationAngle == 0) { - int layerDataSize = fImgLayerSize * fImgLayerSize * ((fImage.getFormat() == GL_BGRA || fImage.getFormat() == GL_RGBA) ? 4 : 3); - imageDataOffset = layerDataSize * int(normValue * float(fImgLayerCount-1)); + DISTRHO_SAFE_ASSERT_RETURN(fImgLayerCount > 0,); + DISTRHO_SAFE_ASSERT_RETURN(normValue >= 0.0f,); + + const uint layerDataSize = fImgLayerSize * fImgLayerSize * ((fImage.getFormat() == GL_BGRA || fImage.getFormat() == GL_RGBA) ? 4 : 3); + /* */ imageDataOffset = layerDataSize * uint(normValue * float(fImgLayerCount-1)); } - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, getWidth(), getHeight(), 0, fImage.getFormat(), fImage.getType(), fImage.getRawData() + imageDataOffset); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, + static_cast<GLsizei>(getWidth()), static_cast<GLsizei>(getHeight()), 0, + fImage.getFormat(), fImage.getType(), fImage.getRawData() + imageDataOffset); fIsReady = true; } + const int w = static_cast<int>(getWidth()); + const int h = static_cast<int>(getHeight()); + if (fRotationAngle != 0) { glPushMatrix(); - const GLint w2 = getWidth()/2; - const GLint h2 = getHeight()/2; + const int w2 = w/2; + const int h2 = h/2; glTranslatef(static_cast<float>(w2), static_cast<float>(h2), 0.0f); glRotatef(normValue*static_cast<float>(fRotationAngle), 0.0f, 0.0f, 1.0f); - Rectangle<int>(-w2, -h2, getWidth(), getHeight()).draw(); + Rectangle<int>(-w2, -h2, w, h).draw(); glPopMatrix(); } else { - Rectangle<int>(0, 0, getWidth(), getHeight()).draw(); + Rectangle<int>(0, 0, w, h).draw(); } glBindTexture(GL_TEXTURE_2D, 0); @@ -403,7 +393,7 @@ bool ImageKnob::onMotion(const MotionEvent& ev) { fValueTmp = value = fMaximum; } - else if (fStep != 0.0f) + else if (d_isNotZero(fStep)) { fValueTmp = value; const float rest = std::fmod(value, fStep); @@ -437,7 +427,7 @@ bool ImageKnob::onScroll(const ScrollEvent& ev) { fValueTmp = value = fMaximum; } - else if (fStep != 0.0f) + else if (d_isNotZero(fStep)) { fValueTmp = value; const float rest = std::fmod(value, fStep); diff --git a/libs/dgl/src/ImageSlider.cpp b/libs/dgl/src/ImageSlider.cpp index b9ae84e..12f7b37 100644 --- a/libs/dgl/src/ImageSlider.cpp +++ b/libs/dgl/src/ImageSlider.cpp @@ -22,10 +22,9 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -ImageSlider::ImageSlider(Window& parent, const Image& image, int id) noexcept +ImageSlider::ImageSlider(Window& parent, const Image& image) noexcept : Widget(parent), fImage(image), - fId(id), fMinimum(0.0f), fMaximum(1.0f), fStep(0.0f), @@ -44,10 +43,9 @@ ImageSlider::ImageSlider(Window& parent, const Image& image, int id) noexcept Widget::setNeedsFullViewport(true); } -ImageSlider::ImageSlider(Widget* widget, const Image& image, int id) noexcept +ImageSlider::ImageSlider(Widget* widget, const Image& image) noexcept : Widget(widget->getParentWindow()), fImage(image), - fId(id), fMinimum(0.0f), fMaximum(1.0f), fStep(0.0f), @@ -69,7 +67,6 @@ ImageSlider::ImageSlider(Widget* widget, const Image& image, int id) noexcept ImageSlider::ImageSlider(const ImageSlider& imageSlider) noexcept : Widget(imageSlider.getParentWindow()), fImage(imageSlider.fImage), - fId(imageSlider.fId), fMinimum(imageSlider.fMinimum), fMaximum(imageSlider.fMaximum), fStep(imageSlider.fStep), @@ -91,7 +88,6 @@ ImageSlider::ImageSlider(const ImageSlider& imageSlider) noexcept ImageSlider& ImageSlider::operator=(const ImageSlider& imageSlider) noexcept { fImage = imageSlider.fImage; - fId = imageSlider.fId; fMinimum = imageSlider.fMinimum; fMaximum = imageSlider.fMaximum; fStep = imageSlider.fStep; @@ -109,16 +105,6 @@ ImageSlider& ImageSlider::operator=(const ImageSlider& imageSlider) noexcept return *this; } -int ImageSlider::getId() const noexcept -{ - return fId; -} - -void ImageSlider::setId(int id) noexcept -{ - fId = id; -} - float ImageSlider::getValue() const noexcept { return fValue; @@ -193,12 +179,12 @@ void ImageSlider::setStep(float step) noexcept void ImageSlider::setValue(float value, bool sendCallback) noexcept { - if (fValue == value) + if (d_isEqual(fValue, value)) return; fValue = value; - if (fStep == 0.0f) + if (d_isZero(fStep)) fValueTmp = value; repaint(); @@ -224,7 +210,7 @@ void ImageSlider::onDisplay() glColor4f(1.0f, 1.0f, 1.0f, 1.0f); #endif - float normValue = (fValue - fMinimum) / (fMaximum - fMinimum); + const float normValue = (fValue - fMinimum) / (fMaximum - fMinimum); int x, y; @@ -292,7 +278,7 @@ bool ImageSlider::onMouse(const MouseEvent& ev) { fValueTmp = value = fMaximum; } - else if (fStep != 0.0f) + else if (d_isNotZero(fStep)) { fValueTmp = value; const float rest = std::fmod(value, fStep); @@ -361,7 +347,7 @@ bool ImageSlider::onMotion(const MotionEvent& ev) { fValueTmp = value = fMaximum; } - else if (fStep != 0.0f) + else if (d_isNotZero(fStep)) { fValueTmp = value; const float rest = std::fmod(value, fStep); @@ -395,16 +381,16 @@ void ImageSlider::_recheckArea() noexcept // horizontal fSliderArea = Rectangle<int>(fStartPos.getX(), fStartPos.getY(), - fEndPos.getX() + fImage.getWidth() - fStartPos.getX(), - fImage.getHeight()); + fEndPos.getX() + static_cast<int>(fImage.getWidth()) - fStartPos.getX(), + static_cast<int>(fImage.getHeight())); } else { // vertical fSliderArea = Rectangle<int>(fStartPos.getX(), fStartPos.getY(), - fImage.getWidth(), - fEndPos.getY() + fImage.getHeight() - fStartPos.getY()); + static_cast<int>(fImage.getWidth()), + fEndPos.getY() + static_cast<int>(fImage.getHeight()) - fStartPos.getY()); } } diff --git a/libs/dgl/src/ImageSwitch.cpp b/libs/dgl/src/ImageSwitch.cpp index bd99786..37b3e56 100644 --- a/libs/dgl/src/ImageSwitch.cpp +++ b/libs/dgl/src/ImageSwitch.cpp @@ -20,12 +20,11 @@ START_NAMESPACE_DGL // ----------------------------------------------------------------------- -ImageSwitch::ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown, int id) noexcept +ImageSwitch::ImageSwitch(Window& parent, const Image& imageNormal, const Image& imageDown) noexcept : Widget(parent), fImageNormal(imageNormal), fImageDown(imageDown), fIsDown(false), - fId(id), fCallback(nullptr), leakDetector_ImageSwitch() { @@ -34,12 +33,11 @@ ImageSwitch::ImageSwitch(Window& parent, const Image& imageNormal, const Image& setSize(fImageNormal.getSize()); } -ImageSwitch::ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown, int id) noexcept +ImageSwitch::ImageSwitch(Widget* widget, const Image& imageNormal, const Image& imageDown) noexcept : Widget(widget->getParentWindow()), fImageNormal(imageNormal), fImageDown(imageDown), fIsDown(false), - fId(id), fCallback(nullptr), leakDetector_ImageSwitch() { @@ -53,7 +51,6 @@ ImageSwitch::ImageSwitch(const ImageSwitch& imageSwitch) noexcept fImageNormal(imageSwitch.fImageNormal), fImageDown(imageSwitch.fImageDown), fIsDown(imageSwitch.fIsDown), - fId(imageSwitch.fId), fCallback(imageSwitch.fCallback), leakDetector_ImageSwitch() { @@ -67,7 +64,6 @@ ImageSwitch& ImageSwitch::operator=(const ImageSwitch& imageSwitch) noexcept fImageNormal = imageSwitch.fImageNormal; fImageDown = imageSwitch.fImageDown; fIsDown = imageSwitch.fIsDown; - fId = imageSwitch.fId; fCallback = imageSwitch.fCallback; DISTRHO_SAFE_ASSERT(fImageNormal.getSize() == fImageDown.getSize()); @@ -77,16 +73,6 @@ ImageSwitch& ImageSwitch::operator=(const ImageSwitch& imageSwitch) noexcept return *this; } -int ImageSwitch::getId() const noexcept -{ - return fId; -} - -void ImageSwitch::setId(int id) noexcept -{ - fId = id;; -} - bool ImageSwitch::isDown() const noexcept { return fIsDown; diff --git a/libs/dgl/src/NanoVG.cpp b/libs/dgl/src/NanoVG.cpp index bb7b9f1..a4c357b 100644 --- a/libs/dgl/src/NanoVG.cpp +++ b/libs/dgl/src/NanoVG.cpp @@ -18,8 +18,32 @@ #include "../Window.hpp" // ----------------------------------------------------------------------- +// Ignore some warnings if debugging + +#if 0 //def DEBUG +# define NANOVG_GL3 0 +# define NANOVG_GLES2 0 +# define NANOVG_GLES3 0 +# define NANOVG_GL_USE_UNIFORMBUFFER 0 +# if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Weverything" +# elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wall" +# pragma GCC diagnostic ignored "-Wextra" +# pragma GCC diagnostic ignored "-Wconversion" +# pragma GCC diagnostic ignored "-Weffc++" +# pragma GCC diagnostic ignored "-Wsign-conversion" +# pragma GCC diagnostic ignored "-Wundef" +# pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" +# endif +#endif + +// ----------------------------------------------------------------------- +// Include NanoVG OpenGL implementation -#define NANOVG_GL2_IMPLEMENTATION +#define NANOVG_GL2_IMPLEMENTATION 1 #include "nanovg/nanovg_gl.h" #if defined(NANOVG_GL2) @@ -36,6 +60,19 @@ # define nvgDeleteGL nvgDeleteGLES3 #endif +// ----------------------------------------------------------------------- +// Restore normal state if debugging + +#if 0//def DEBUG +# if defined(__clang__) +# pragma clang diagnostic pop +# elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +# endif +#endif + +// ----------------------------------------------------------------------- + START_NAMESPACE_DGL // ----------------------------------------------------------------------- @@ -75,7 +112,8 @@ NanoVG::Paint::operator NVGpaint() const noexcept NanoImage::NanoImage(NVGcontext* const context, const int imageId) noexcept : fContext(context), fImageId(imageId), - fSize() + fSize(), + leakDetector_NanoImage() { _updateSize(); } @@ -114,7 +152,7 @@ void NanoImage::_updateSize() if (h < 0) h = 0; } - fSize.setSize(w, h); + fSize.setSize(static_cast<uint>(w), static_cast<uint>(h)); } // ----------------------------------------------------------------------- @@ -122,14 +160,16 @@ void NanoImage::_updateSize() NanoVG::NanoVG() : fContext(nvgCreateGL(512, 512, NVG_ANTIALIAS)), - fInFrame(false) + fInFrame(false), + leakDetector_NanoVG() { DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr,); } NanoVG::NanoVG(const int textAtlasWidth, const int textAtlasHeight) : fContext(nvgCreateGL(textAtlasWidth, textAtlasHeight, NVG_ANTIALIAS)), - fInFrame(false) + fInFrame(false), + leakDetector_NanoVG() { DISTRHO_SAFE_ASSERT_RETURN(fContext != nullptr,); } @@ -151,7 +191,7 @@ void NanoVG::beginFrame(const uint width, const uint height, const float scaleFa DISTRHO_SAFE_ASSERT_RETURN(! fInFrame,); fInFrame = true; - nvgBeginFrame(fContext, width, height, scaleFactor, static_cast<NVGalpha>(alpha)); + nvgBeginFrame(fContext, static_cast<int>(width), static_cast<int>(height), scaleFactor, static_cast<NVGalpha>(alpha)); } void NanoVG::beginFrame(Widget* const widget) @@ -163,7 +203,7 @@ void NanoVG::beginFrame(Widget* const widget) Window& window(widget->getParentWindow()); fInFrame = true; - nvgBeginFrame(fContext, window.getWidth(), window.getHeight(), 1.0f, NVG_PREMULTIPLIED_ALPHA); + nvgBeginFrame(fContext, static_cast<int>(window.getWidth()), static_cast<int>(window.getHeight()), 1.0f, NVG_PREMULTIPLIED_ALPHA); } void NanoVG::endFrame() @@ -209,7 +249,17 @@ void NanoVG::strokeColor(const Color& color) void NanoVG::strokeColor(const int red, const int green, const int blue, const int alpha) { if (fContext != nullptr) - nvgStrokeColor(fContext, nvgRGBA(red, green, blue, alpha)); + { + DISTRHO_SAFE_ASSERT_RETURN(red >= 0 && red <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(green >= 0 && green <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(blue >= 0 && blue <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(alpha >= 0 && alpha <= 255,); + + nvgStrokeColor(fContext, nvgRGBA(static_cast<uchar>(red), + static_cast<uchar>(green), + static_cast<uchar>(blue), + static_cast<uchar>(alpha))); + } } void NanoVG::strokeColor(const float red, const float green, const float blue, const float alpha) @@ -233,7 +283,17 @@ void NanoVG::fillColor(const Color& color) void NanoVG::fillColor(const int red, const int green, const int blue, const int alpha) { if (fContext != nullptr) - nvgFillColor(fContext, nvgRGBA(red, green, blue, alpha)); + { + DISTRHO_SAFE_ASSERT_RETURN(red >= 0 && red <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(green >= 0 && green <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(blue >= 0 && blue <= 255,); + DISTRHO_SAFE_ASSERT_RETURN(alpha >= 0 && alpha <= 255,); + + nvgFillColor(fContext, nvgRGBA(static_cast<uchar>(red), + static_cast<uchar>(green), + static_cast<uchar>(blue), + static_cast<uchar>(alpha))); + } } void NanoVG::fillColor(const float red, const float green, const float blue, const float alpha) @@ -427,7 +487,7 @@ NanoImage* NanoVG::createImageRGBA(uint w, uint h, const uchar* data) if (fContext == nullptr) return nullptr; DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, nullptr); - if (const int imageId = nvgCreateImageRGBA(fContext, w, h, data)) + if (const int imageId = nvgCreateImageRGBA(fContext, static_cast<int>(w), static_cast<int>(h), data)) return new NanoImage(fContext, imageId); return nullptr; @@ -576,13 +636,13 @@ NanoVG::FontId NanoVG::createFont(const char* name, const char* filename) return nvgCreateFont(fContext, name, filename); } -NanoVG::FontId NanoVG::createFontMem(const char* name, uchar* data, int ndata, bool freeData) +NanoVG::FontId NanoVG::createFontMem(const char* name, const uchar* data, int ndata, bool freeData) { if (fContext == nullptr) return -1; DISTRHO_SAFE_ASSERT_RETURN(name != nullptr && name[0] != '\0', -1); DISTRHO_SAFE_ASSERT_RETURN(data != nullptr, -1); - return nvgCreateFontMem(fContext, name, data, ndata, freeData); + return nvgCreateFontMem(fContext, name, const_cast<uchar*>(data), ndata, freeData); } NanoVG::FontId NanoVG::findFont(const char* name) diff --git a/libs/dgl/src/Widget.cpp b/libs/dgl/src/Widget.cpp index 6013ea2..d2898b3 100644 --- a/libs/dgl/src/Widget.cpp +++ b/libs/dgl/src/Widget.cpp @@ -27,6 +27,7 @@ Widget::Widget(Window& parent) fNeedsFullViewport(false), fNeedsScaling(false), fVisible(true), + fId(0), fAbsolutePos(0, 0), fSize(0, 0), leakDetector_Widget() @@ -200,6 +201,16 @@ void Widget::repaint() noexcept fParent.repaint(); } +uint Widget::getId() const noexcept +{ + return fId; +} + +void Widget::setId(uint id) noexcept +{ + fId = id; +} + bool Widget::onKeyboard(const KeyboardEvent&) { return false; diff --git a/libs/dgl/src/Window.cpp b/libs/dgl/src/Window.cpp index e31b75c..c901ff2 100644 --- a/libs/dgl/src/Window.cpp +++ b/libs/dgl/src/Window.cpp @@ -15,11 +15,12 @@ */ // we need this for now -#define PUGL_GRAB_FOCUS 1 +//#define PUGL_GRAB_FOCUS 1 #include "AppPrivateData.hpp" #include "../Widget.hpp" #include "../Window.hpp" +#include "../../distrho/extra/d_string.hpp" #include "pugl/pugl.h" @@ -62,13 +63,15 @@ struct Window::PrivateData { PrivateData(App& app, Window* const self) : fApp(app), fSelf(self), - fView(puglInit(nullptr, nullptr)), + fView(puglInit()), fFirstInit(true), fVisible(false), fResizable(true), fUsingEmbed(false), fWidth(1), fHeight(1), + fTitle(nullptr), + fWidgets(), fModal(), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), @@ -89,13 +92,15 @@ struct Window::PrivateData { PrivateData(App& app, Window* const self, Window& parent) : fApp(app), fSelf(self), - fView(puglInit(nullptr, nullptr)), + fView(puglInit()), fFirstInit(true), fVisible(false), fResizable(true), fUsingEmbed(false), fWidth(1), fHeight(1), + fTitle(nullptr), + fWidgets(), fModal(parent.pData), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), @@ -112,23 +117,29 @@ struct Window::PrivateData { DBG("Creating window with parent..."); DBGF; init(); -#ifdef DISTRHO_OS_LINUX const PuglInternals* const parentImpl(parent.pData->fView->impl); - +#if defined(DISTRHO_OS_LINUX) XSetTransientForHint(xDisplay, xWindow, parentImpl->win); +//#elif defined(DISTRHO_OS_MAC) +// [parentImpl->window orderWindow:NSWindowBelow relativeTo:[[mView window] windowNumber]]; +#else + // unused + return; (void)parentImpl; #endif } PrivateData(App& app, Window* const self, const intptr_t parentId) : fApp(app), fSelf(self), - fView(puglInit(nullptr, nullptr)), + fView(puglInit()), fFirstInit(true), fVisible(parentId != 0), fResizable(parentId == 0), fUsingEmbed(parentId != 0), fWidth(1), fHeight(1), + fTitle(nullptr), + fWidgets(), fModal(), #if defined(DISTRHO_OS_WINDOWS) hwnd(0), @@ -136,7 +147,7 @@ struct Window::PrivateData { xDisplay(nullptr), xWindow(0), #elif defined(DISTRHO_OS_MAC) - fNeedsIdle(false), + fNeedsIdle(parentId == 0), mView(nullptr), mWindow(nullptr), #endif @@ -172,7 +183,7 @@ struct Window::PrivateData { } puglInitResizable(fView, fResizable); - puglInitWindowSize(fView, fWidth, fHeight); + puglInitWindowSize(fView, static_cast<int>(fWidth), static_cast<int>(fHeight)); puglSetHandle(fView, this); puglSetDisplayFunc(fView, onDisplayCallback); @@ -183,6 +194,7 @@ struct Window::PrivateData { puglSetSpecialFunc(fView, onSpecialCallback); puglSetReshapeFunc(fView, onReshapeCallback); puglSetCloseFunc(fView, onCloseCallback); + puglSetFileSelectedFunc(fView, fileBrowserSelectedCallback); puglCreateWindow(fView, nullptr); @@ -221,7 +233,12 @@ struct Window::PrivateData { { DBG("Destroying window..."); DBGF; - //fOnModal = false; + if (fModal.enabled) + { + exec_fini(); + close(); + } + fWidgets.clear(); if (fUsingEmbed) @@ -444,7 +461,11 @@ struct Window::PrivateData { fResizable = yesNo; -#ifdef CARLA_OS_MAC +#if defined(DISTRHO_OS_WINDOWS) + const int winFlags = fResizable ? GetWindowLong(hwnd, GWL_STYLE) | WS_SIZEBOX + : GetWindowLong(hwnd, GWL_STYLE) & ~WS_SIZEBOX; + SetWindowLong(hwnd, GWL_STYLE, winFlags); +#elif defined(DISTRHO_OS_MAC) // FIXME? const uint flags(yesNo ? (NSViewWidthSizable|NSViewHeightSizable) : 0x0); [mView setAutoresizingMask:flags]; @@ -457,7 +478,7 @@ struct Window::PrivateData { void setSize(uint width, uint height, const bool forced = false) { - if (width == 0 || height == 0) + if (width <= 1 || height <= 1) { DBGp("Window setSize called with invalid value(s) %i %i, ignoring request\n", width, height); return; @@ -472,18 +493,15 @@ struct Window::PrivateData { fWidth = width; fHeight = height; - DBGp("Window setSize called %s, size %i %i\n", forced ? "(forced)" : "(not forced)", width, height); + DBGp("Window setSize called %s, size %i %i, resizable %s\n", forced ? "(forced)" : "(not forced)", width, height, fResizable?"true":"false"); #if defined(DISTRHO_OS_WINDOWS) - int winFlags = WS_POPUPWINDOW | WS_CAPTION; - - if (fResizable) - winFlags |= WS_SIZEBOX; - + const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (fResizable ? WS_SIZEBOX : 0x0); RECT wr = { 0, 0, static_cast<long>(width), static_cast<long>(height) }; - AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST); + AdjustWindowRectEx(&wr, fUsingEmbed ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); - SetWindowPos(hwnd, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); + SetWindowPos(hwnd, 0, 0, 0, wr.right-wr.left, wr.bottom-wr.top, + SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER|SWP_NOZORDER); if (! forced) UpdateWindow(hwnd); @@ -522,10 +540,22 @@ struct Window::PrivateData { // ------------------------------------------------------------------- + const char* getTitle() const noexcept + { + static const char* const kFallback = ""; + + return fTitle != nullptr ? fTitle : kFallback; + } + void setTitle(const char* const title) { DBGp("Window setTitle \"%s\"\n", title); + if (fTitle != nullptr) + std::free(fTitle); + + fTitle = strdup(title); + #if defined(DISTRHO_OS_WINDOWS) SetWindowTextA(hwnd, title); #elif defined(DISTRHO_OS_MAC) @@ -543,7 +573,7 @@ struct Window::PrivateData { #endif } - void setTransientWinId(const intptr_t winId) + void setTransientWinId(const uintptr_t winId) { #if defined(DISTRHO_OS_LINUX) XSetTransientForHint(xDisplay, xWindow, static_cast< ::Window>(winId)); @@ -600,7 +630,7 @@ struct Window::PrivateData { // ------------------------------------------------------------------- - void onDisplay() + void onPuglDisplay() { fSelf->onDisplayBefore(); @@ -618,22 +648,35 @@ struct Window::PrivateData { if (widget->fNeedsFullViewport || (widget->fAbsolutePos.isZero() && widget->fSize == Size<uint>(fWidth, fHeight))) { // full viewport size - glViewport(0, 0, fWidth, fHeight); + glViewport(0, + 0, + static_cast<GLsizei>(fWidth), + static_cast<GLsizei>(fHeight)); } else if (! widget->fNeedsScaling) { // only set viewport pos - glViewport(widget->getAbsoluteX(), /*fView->height - widget->getHeight()*/ - widget->getAbsoluteY(), fWidth, fHeight); + glViewport(widget->getAbsoluteX(), + /*fView->height - static_cast<int>(widget->getHeight())*/ - widget->getAbsoluteY(), + static_cast<GLsizei>(fWidth), + static_cast<GLsizei>(fHeight)); // then cut the outer bounds - glScissor(widget->getAbsoluteX(), fView->height - widget->getHeight() - widget->getAbsoluteY(), widget->getWidth(), widget->getHeight()); + glScissor(widget->getAbsoluteX(), + fView->height - static_cast<int>(widget->getHeight()) - widget->getAbsoluteY(), + static_cast<GLsizei>(widget->getWidth()), + static_cast<GLsizei>(widget->getHeight())); + glEnable(GL_SCISSOR_TEST); needsDisableScissor = true; } else { // limit viewport to widget bounds - glViewport(widget->getAbsoluteX(), fView->height - widget->getHeight() - widget->getAbsoluteY(), widget->getWidth(), widget->getHeight()); + glViewport(widget->getAbsoluteX(), + fView->height - static_cast<int>(widget->getHeight()) - widget->getAbsoluteY(), + static_cast<GLsizei>(widget->getWidth()), + static_cast<GLsizei>(widget->getHeight())); } // display widget @@ -650,7 +693,7 @@ struct Window::PrivateData { fSelf->onDisplayAfter(); } - void onKeyboard(const bool press, const uint key) + void onPuglKeyboard(const bool press, const uint key) { DBGp("PUGL: onKeyboard : %i %i\n", press, key); @@ -672,7 +715,7 @@ struct Window::PrivateData { } } - void onSpecial(const bool press, const Key key) + void onPuglSpecial(const bool press, const Key key) { DBGp("PUGL: onSpecial : %i %i\n", press, key); @@ -681,9 +724,9 @@ struct Window::PrivateData { Widget::SpecialEvent ev; ev.press = press; - ev.key = key; - ev.mod = static_cast<Modifier>(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); + ev.key = key; + ev.mod = static_cast<Modifier>(puglGetModifiers(fView)); + ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { @@ -694,18 +737,21 @@ struct Window::PrivateData { } } - void onMouse(const int button, const bool press, const int x, const int y) + void onPuglMouse(const int button, const bool press, const int x, const int y) { DBGp("PUGL: onMouse : %i %i %i %i\n", button, press, x, y); + // FIXME - pugl sends 2 of these for each window on init, don't ask me why. we'll ignore it + if (press && button == 0 && x == 0 && y == 0) return; + if (fModal.childFocus != nullptr) return fModal.childFocus->focus(); Widget::MouseEvent ev; ev.button = button; - ev.press = press; - ev.mod = static_cast<Modifier>(puglGetModifiers(fView)); - ev.time = puglGetEventTimestamp(fView); + ev.press = press; + ev.mod = static_cast<Modifier>(puglGetModifiers(fView)); + ev.time = puglGetEventTimestamp(fView); FOR_EACH_WIDGET_INV(rit) { @@ -718,7 +764,7 @@ struct Window::PrivateData { } } - void onMotion(const int x, const int y) + void onPuglMotion(const int x, const int y) { DBGp("PUGL: onMotion : %i %i\n", x, y); @@ -740,7 +786,7 @@ struct Window::PrivateData { } } - void onScroll(const int x, const int y, const float dx, const float dy) + void onPuglScroll(const int x, const int y, const float dx, const float dy) { DBGp("PUGL: onScroll : %i %i %f %f\n", x, y, dx, dy); @@ -763,38 +809,38 @@ struct Window::PrivateData { } } - void onReshape(const int width, const int height) + void onPuglReshape(const int width, const int height) { DBGp("PUGL: onReshape : %i %i\n", width, height); - if (width == 1 && height == 1) + if (width <= 1 && height <= 1) return; - fWidth = width; - fHeight = height; + fWidth = static_cast<uint>(width); + fHeight = static_cast<uint>(height); - fSelf->onReshape(width, height); + fSelf->onReshape(fWidth, fHeight); FOR_EACH_WIDGET(it) { Widget* const widget(*it); if (widget->fNeedsFullViewport) - widget->setSize(width, height); + widget->setSize(fWidth, fHeight); } } - void onClose() + void onPuglClose() { DBG("PUGL: onClose\n"); - if (fModal.enabled && fModal.parent != nullptr) + if (fModal.enabled) exec_fini(); fSelf->onClose(); if (fModal.childFocus != nullptr) - fModal.childFocus->onClose(); + fModal.childFocus->fSelf->onClose(); close(); } @@ -811,6 +857,7 @@ struct Window::PrivateData { bool fUsingEmbed; uint fWidth; uint fHeight; + char* fTitle; std::list<Widget*> fWidgets; struct Modal { @@ -833,6 +880,8 @@ struct Window::PrivateData { DISTRHO_SAFE_ASSERT(! enabled); DISTRHO_SAFE_ASSERT(childFocus == nullptr); } + + DISTRHO_DECLARE_NON_COPY_STRUCT(Modal) } fModal; #if defined(DISTRHO_OS_WINDOWS) @@ -853,42 +902,47 @@ struct Window::PrivateData { static void onDisplayCallback(PuglView* view) { - handlePtr->onDisplay(); + handlePtr->onPuglDisplay(); } static void onKeyboardCallback(PuglView* view, bool press, uint32_t key) { - handlePtr->onKeyboard(press, key); + handlePtr->onPuglKeyboard(press, key); } static void onSpecialCallback(PuglView* view, bool press, PuglKey key) { - handlePtr->onSpecial(press, static_cast<Key>(key)); + handlePtr->onPuglSpecial(press, static_cast<Key>(key)); } static void onMouseCallback(PuglView* view, int button, bool press, int x, int y) { - handlePtr->onMouse(button, press, x, y); + handlePtr->onPuglMouse(button, press, x, y); } static void onMotionCallback(PuglView* view, int x, int y) { - handlePtr->onMotion(x, y); + handlePtr->onPuglMotion(x, y); } static void onScrollCallback(PuglView* view, int x, int y, float dx, float dy) { - handlePtr->onScroll(x, y, dx, dy); + handlePtr->onPuglScroll(x, y, dx, dy); } static void onReshapeCallback(PuglView* view, int width, int height) { - handlePtr->onReshape(width, height); + handlePtr->onPuglReshape(width, height); } static void onCloseCallback(PuglView* view) { - handlePtr->onClose(); + handlePtr->onPuglClose(); + } + + static void fileBrowserSelectedCallback(PuglView* view, const char* filename) + { + handlePtr->fSelf->fileBrowserSelected(filename); } #undef handlePtr @@ -900,13 +954,16 @@ struct Window::PrivateData { // Window Window::Window(App& app) - : pData(new PrivateData(app, this)) {} + : pData(new PrivateData(app, this)), + leakDetector_Window() {} Window::Window(App& app, Window& parent) - : pData(new PrivateData(app, this, parent)) {} + : pData(new PrivateData(app, this, parent)), + leakDetector_Window() {} Window::Window(App& app, intptr_t parentId) - : pData(new PrivateData(app, this, parentId)) {} + : pData(new PrivateData(app, this, parentId)), + leakDetector_Window() {} Window::~Window() { @@ -943,6 +1000,73 @@ void Window::repaint() noexcept puglPostRedisplay(pData->fView); } +// static int fib_filter_filename_filter(const char* const name) +// { +// return 1; +// (void)name; +// } + +bool Window::openFileBrowser(const FileBrowserOptions& options) +{ + using DISTRHO_NAMESPACE::d_string; + + // -------------------------------------------------------------------------- + // configure start dir + + // TODO: get abspath if needed + // TODO: cross-platform + + d_string startDir(options.startDir); + + if (startDir.isEmpty()) + { + if (char* const dir_name = get_current_dir_name()) + { + startDir = dir_name; + std::free(dir_name); + } + } + + DISTRHO_SAFE_ASSERT_RETURN(startDir.isNotEmpty(), false); + + if (! startDir.endsWith('/')) + startDir += "/"; + + DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(0, startDir) == 0, false); + + // -------------------------------------------------------------------------- + // configure title + + d_string title(options.title); + + if (title.isEmpty()) + { + title = pData->getTitle(); + + if (title.isEmpty()) + title = "FileBrowser"; + } + + DISTRHO_SAFE_ASSERT_RETURN(x_fib_configure(1, title) == 0, false); + + // -------------------------------------------------------------------------- + // configure filters + + x_fib_cfg_filter_callback(nullptr); //fib_filter_filename_filter); + + // -------------------------------------------------------------------------- + // configure buttons + + x_fib_cfg_buttons(3, options.buttons.listAllFiles-1); + x_fib_cfg_buttons(1, options.buttons.showHidden-1); + x_fib_cfg_buttons(2, options.buttons.showPlaces-1); + + // -------------------------------------------------------------------------- + // show + + return (x_fib_show(pData->xDisplay, pData->xWindow, /*options.width*/0, /*options.height*/0) == 0); +} + bool Window::isVisible() const noexcept { return pData->fVisible; @@ -988,12 +1112,17 @@ void Window::setSize(Size<uint> size) pData->setSize(size.getWidth(), size.getHeight()); } +const char* Window::getTitle() const noexcept +{ + return pData->getTitle(); +} + void Window::setTitle(const char* title) { pData->setTitle(title); } -void Window::setTransientWinId(intptr_t winId) +void Window::setTransientWinId(uintptr_t winId) { pData->setTransientWinId(winId); } @@ -1057,8 +1186,8 @@ void Window::onReshape(uint width, uint height) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, width, height, 0, 0.0f, 1.0f); - glViewport(0, 0, width, height); + glOrtho(0.0, static_cast<GLdouble>(width), static_cast<GLdouble>(height), 0.0, 0.0, 1.0); + glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height)); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } @@ -1067,6 +1196,10 @@ void Window::onClose() { } +void Window::fileBrowserSelected(const char*) +{ +} + // ----------------------------------------------------------------------- END_NAMESPACE_DGL diff --git a/libs/dgl/src/nanovg/fontstash.h b/libs/dgl/src/nanovg/fontstash.h index 7fb8955..a97ce53 100644 --- a/libs/dgl/src/nanovg/fontstash.h +++ b/libs/dgl/src/nanovg/fontstash.h @@ -41,7 +41,7 @@ enum FONSalign { enum FONSerrorCode { // Font atlas is full. FONS_ATLAS_FULL = 1, - // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. + // Scratch memory used to render glyphs is full, requested size reported in 'val', you may need to bump up FONS_SCRATCH_BUF_SIZE. FONS_SCRATCH_FULL = 2, // Calls to fonsPushState has craeted too large stack, if you need deep state stack bump up FONS_MAX_STATES. FONS_STATES_OVERFLOW = 3, @@ -85,7 +85,7 @@ void fonsDeleteInternal(struct FONScontext* s); void fonsSetErrorCallback(struct FONScontext* s, void (*callback)(void* uptr, int error, int val), void* uptr); // Returns current atlas size. void fonsGetAtlasSize(struct FONScontext* s, int* width, int* height); -// Expands the atlas size. +// Expands the atlas size. int fonsExpandAtlas(struct FONScontext* s, int width, int height); // Reseta the whole stash. int fonsResetAtlas(struct FONScontext* stash, int width, int height); @@ -1388,7 +1388,7 @@ void fonsDrawDebug(struct FONScontext* stash, float x, float y) } float fonsTextBounds(struct FONScontext* stash, - float x, float y, + float x, float y, const char* str, const char* end, float* bounds) { @@ -1576,7 +1576,7 @@ int fonsExpandAtlas(struct FONScontext* stash, int width, int height) height = fons__maxi(height, stash->params.height); if (width == stash->params.width && height == stash->params.height) - return 1; + return 1; // Flush pending glyphs. fons__flush(stash); diff --git a/libs/dgl/src/pugl/pugl.h b/libs/dgl/src/pugl/pugl.h index 360f8e8..7cd8b84 100644 --- a/libs/dgl/src/pugl/pugl.h +++ b/libs/dgl/src/pugl/pugl.h @@ -51,7 +51,11 @@ # define PUGL_API PUGL_LIB_IMPORT # endif #else -# define PUGL_API +# ifdef _WIN32 +# define PUGL_API +# else +# define PUGL_API __attribute__((visibility("hidden"))) +# endif #endif #ifdef __cplusplus @@ -196,19 +200,18 @@ typedef void (*PuglReshapeFunc)(PuglView* view, int width, int height); so programs should handle any value gracefully. @param view The view being scrolled. + @param x The window-relative x coordinate of the pointer. + @param y The window-relative y coordinate of the pointer. @param dx The scroll x distance. @param dx The scroll y distance. */ -typedef void (*PuglScrollFunc)(PuglView* view, - int x, - int y, - float dx, - float dy); +typedef void (*PuglScrollFunc)(PuglView* view, int x, int y, float dx, float dy); /** A function called when a special key is pressed or released. This callback allows the use of keys that do not have unicode points. + Note that some are non-printable keys. @param view The view the event occured in. @param press True if the key was pressed, false if released. @@ -217,16 +220,21 @@ typedef void (*PuglScrollFunc)(PuglView* view, typedef void (*PuglSpecialFunc)(PuglView* view, bool press, PuglKey key); /** + A function called when a filename is selected via file-browser. + + @param view The view the event occured in. + @param filename The selected file name or NULL if the dialog was canceled. +*/ +typedef void (*PuglFileSelectedFunc)(PuglView* view, const char* filename); + +/** Create a Pugl context. To create a window, call the various puglInit* functions as necessary, then call puglCreateWindow(). - - @param pargc Pointer to argument count (unused, for GLUT compatibility). - @param argv Arguments (unused, for GLUT compatibility). */ PUGL_API PuglView* -puglInit(int* pargc, char** argv); +puglInit(void); /** Set the parent window before creating a window (for embedding). @@ -244,7 +252,7 @@ puglInitWindowSize(PuglView* view, int width, int height); Enable or disable resizing before creating a window. */ PUGL_API void -puglInitResizable(PuglView* view, bool resizable); +puglInitUserResizable(PuglView* view, bool resizable); /** Create a window with the settings given by the various puglInit functions. @@ -353,6 +361,12 @@ PUGL_API void puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); /** + Set the function to call on file-browser selections. +*/ +PUGL_API void +puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc); + +/** Return the native window handle. */ PUGL_API PuglNativeWindow diff --git a/libs/dgl/src/pugl/pugl_internal.h b/libs/dgl/src/pugl/pugl_internal.h index 1f75830..7301091 100644 --- a/libs/dgl/src/pugl/pugl_internal.h +++ b/libs/dgl/src/pugl/pugl_internal.h @@ -51,9 +51,9 @@ struct PuglViewImpl { PuglReshapeFunc reshapeFunc; PuglScrollFunc scrollFunc; PuglSpecialFunc specialFunc; + PuglFileSelectedFunc fileSelectedFunc; PuglInternals* impl; - PuglNativeWindow parent; int width; @@ -66,12 +66,10 @@ struct PuglViewImpl { uint32_t event_timestamp_ms; }; -PuglInternals* puglInitInternals(); - -void puglDefaultReshape(PuglView* view, int width, int height); +PuglInternals* puglInitInternals(void); PuglView* -puglInit(int* pargc, char** argv) +puglInit(void) { PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); if (!view) { @@ -80,6 +78,7 @@ puglInit(int* pargc, char** argv) PuglInternals* impl = puglInitInternals(); if (!impl) { + free(view); return NULL; } @@ -88,9 +87,6 @@ puglInit(int* pargc, char** argv) view->height = 480; return view; - - // unused - (void)pargc; (void)argv; } void @@ -136,7 +132,7 @@ puglGetModifiers(PuglView* view) return view->mods; } -void +static void puglDefaultReshape(PuglView* view, int width, int height) { glMatrixMode(GL_PROJECTION); @@ -205,3 +201,9 @@ puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) { view->specialFunc = specialFunc; } + +void +puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc) +{ + view->fileSelectedFunc = fileSelectedFunc; +} diff --git a/libs/dgl/src/pugl/pugl_osx.m b/libs/dgl/src/pugl/pugl_osx.m index 96aa57a..a99d8d0 100644 --- a/libs/dgl/src/pugl/pugl_osx.m +++ b/libs/dgl/src/pugl/pugl_osx.m @@ -35,6 +35,7 @@ backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag; - (void) setPuglview:(PuglView*)view; +- (BOOL) canBecomeKeyWindow; - (BOOL) windowShouldClose:(id)sender; @end @@ -54,7 +55,7 @@ [result setAcceptsMouseMovedEvents:YES]; [result setLevel: CGShieldingWindowLevel() + 1]; - return result; + return (PuglWindow*)result; // unused (void)aStyle; (void)bufferingType; (void)flag; @@ -66,6 +67,11 @@ [self setContentSize:NSMakeSize(view->width, view->height)]; } +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + - (BOOL)windowShouldClose:(id)sender { if (puglview->closeFunc) @@ -81,6 +87,7 @@ static void puglDisplay(PuglView* view) { + view->redisplay = false; if (view->displayFunc) { view->displayFunc(view); } @@ -88,28 +95,33 @@ puglDisplay(PuglView* view) @interface PuglOpenGLView : NSOpenGLView { - int colorBits; - int depthBits; @public PuglView* puglview; - NSTrackingArea* trackingArea; + bool doubleBuffered; } -- (id) initWithFrame:(NSRect)frame - colorBits:(int)numColorBits - depthBits:(int)numDepthBits; +- (BOOL) acceptsFirstMouse:(NSEvent*)e; +- (BOOL) acceptsFirstResponder; +- (BOOL) isFlipped; +- (BOOL) isOpaque; +- (BOOL) preservesContentInLiveResize; +- (id) initWithFrame:(NSRect)frame; - (void) reshape; -- (void) drawRect:(NSRect)rect; -- (void) mouseEntered:(NSEvent*)event; -- (void) mouseExited:(NSEvent*)event; +- (void) drawRect:(NSRect)r; +- (void) cursorUpdate:(NSEvent*)e; +- (void) updateTrackingAreas; +- (void) viewWillMoveToWindow:(NSWindow*)newWindow; - (void) mouseMoved:(NSEvent*)event; - (void) mouseDragged:(NSEvent*)event; - (void) rightMouseDragged:(NSEvent*)event; +- (void) otherMouseDragged:(NSEvent*)event; - (void) mouseDown:(NSEvent*)event; -- (void) mouseUp:(NSEvent*)event; - (void) rightMouseDown:(NSEvent*)event; +- (void) otherMouseDown:(NSEvent*)event; +- (void) mouseUp:(NSEvent*)event; - (void) rightMouseUp:(NSEvent*)event; +- (void) otherMouseUp:(NSEvent*)event; - (void) scrollWheel:(NSEvent*)event; - (void) keyDown:(NSEvent*)event; - (void) keyUp:(NSEvent*)event; @@ -119,22 +131,46 @@ puglDisplay(PuglView* view) @implementation PuglOpenGLView +- (BOOL) acceptsFirstMouse:(NSEvent*)e +{ + return YES; + + // unused + (void)e; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (BOOL) isFlipped +{ + return YES; +} + +- (BOOL) isOpaque +{ + return YES; +} + +- (BOOL) preservesContentInLiveResize +{ + return NO; +} + - (id) initWithFrame:(NSRect)frame - colorBits:(int)numColorBits - depthBits:(int)numDepthBits { - colorBits = numColorBits; - depthBits = numDepthBits; - puglview = nil; - trackingArea = nil; + puglview = nil; + trackingArea = nil; + doubleBuffered = true; - NSOpenGLPixelFormatAttribute pixelAttribs[16] = { + NSOpenGLPixelFormatAttribute pixelAttribs[] = { + NSOpenGLPFAColorSize, 24, + NSOpenGLPFAAlphaSize, 8, + NSOpenGLPFADepthSize, 16, NSOpenGLPFADoubleBuffer, NSOpenGLPFAAccelerated, - NSOpenGLPFAColorSize, - colorBits, - NSOpenGLPFADepthSize, - depthBits, 0 }; @@ -144,12 +180,21 @@ puglDisplay(PuglView* view) if (pixelFormat) { self = [super initWithFrame:frame pixelFormat:pixelFormat]; [pixelFormat release]; - if (self) { - [[self openGLContext] makeCurrentContext]; - [self reshape]; - } + printf("Is doubleBuffered? TRUE\n"); } else { - self = nil; + self = [super initWithFrame:frame]; + doubleBuffered = false; + printf("Is doubleBuffered? FALSE\n"); + } + + if (self) { + NSOpenGLContext* context = [self openGLContext]; + [context makeCurrentContext]; + + GLint swapInterval = 1; + [context setValues:&swapInterval forParameter:NSOpenGLCPSwapInterval]; + + [self reshape]; } return self; @@ -159,156 +204,180 @@ puglDisplay(PuglView* view) { [[self openGLContext] update]; - NSRect bounds = [self bounds]; - int width = bounds.size.width; - int height = bounds.size.height; - - if (puglview) { + if (!puglview) { /* NOTE: Apparently reshape gets called when the GC gets around to deleting the view (?), so we must have reset puglview to NULL when this comes around. */ - if (puglview->reshapeFunc) { - puglview->reshapeFunc(puglview, width, height); - } else { - puglDefaultReshape(puglview, width, height); - } + return; + } + + NSRect bounds = [self bounds]; + int width = bounds.size.width; + int height = bounds.size.height; - puglview->width = width; - puglview->height = height; + if (puglview->reshapeFunc) { + puglview->reshapeFunc(puglview, width, height); + } else { + puglDefaultReshape(puglview, width, height); } + + puglview->width = width; + puglview->height = height; } -- (void) drawRect:(NSRect)rect +- (void) drawRect:(NSRect)r { puglDisplay(puglview); - glFlush(); - glSwapAPPLE(); + + if (doubleBuffered) { + [[self openGLContext] flushBuffer]; + } else { + glFlush(); + //glSwapAPPLE(); + } // unused - return; (void)rect; + return; (void)r; } -static unsigned -getModifiers(PuglView* view, NSEvent* ev) +- (void) cursorUpdate:(NSEvent*)e { - const unsigned modifierFlags = [ev modifierFlags]; + [[NSCursor arrowCursor] set]; - view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); - - unsigned mods = 0; - mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; - mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; - mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; - mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0; - return mods; + // unused + return; (void)e; } --(void)updateTrackingAreas +- (void) updateTrackingAreas { + static const int opts = NSTrackingMouseEnteredAndExited + | NSTrackingMouseMoved + | NSTrackingEnabledDuringMouseDrag + | NSTrackingInVisibleRect + | NSTrackingActiveAlways + | NSTrackingCursorUpdate; + if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; [trackingArea release]; } - const int opts = (NSTrackingMouseEnteredAndExited | - NSTrackingMouseMoved | - NSTrackingActiveAlways); - trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] - options:opts - owner:self - userInfo:nil]; + trackingArea = [[NSTrackingArea alloc] initWithRect:[self bounds] + options:opts + owner:self + userInfo:nil]; [self addTrackingArea:trackingArea]; + [super updateTrackingAreas]; } -- (void)mouseEntered:(NSEvent*)theEvent +- (void) viewWillMoveToWindow:(NSWindow*)newWindow { - [self updateTrackingAreas]; + if (newWindow != nil) { + [newWindow setAcceptsMouseMovedEvents:YES]; + [newWindow makeFirstResponder:self]; + } - // unused - return; (void)theEvent; + [super viewWillMoveToWindow:newWindow]; } -- (void)mouseExited:(NSEvent*)theEvent +static unsigned +getModifiers(PuglView* view, NSEvent* ev) { - // unused - return; (void)theEvent; + const unsigned modifierFlags = [ev modifierFlags]; + + view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); + + unsigned mods = 0; + mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; + mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; + mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; + mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0; + return mods; +} + +static int +getFixedAppKitButton(NSInteger button) +{ + switch (button) { + case 0: return 1; + case 1: return 3; + case 2: return 2; + default: return button; + } } - (void) mouseMoved:(NSEvent*)event { if (puglview->motionFunc) { - NSPoint loc = [event locationInWindow]; + NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); - puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); + puglview->motionFunc(puglview, loc.x, loc.y); } } - (void) mouseDragged:(NSEvent*)event { - if (puglview->motionFunc) { - NSPoint loc = [event locationInWindow]; - puglview->mods = getModifiers(puglview, event); - puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); - } + [self mouseMoved:event]; } - (void) rightMouseDragged:(NSEvent*)event { - if (puglview->motionFunc) { - NSPoint loc = [event locationInWindow]; - puglview->mods = getModifiers(puglview, event); - puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); - } + [self mouseDragged:event]; } -- (void) mouseDown:(NSEvent*)event +- (void) otherMouseDragged:(NSEvent*)event { - if (puglview->mouseFunc) { - NSPoint loc = [event locationInWindow]; - puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, 1, true, loc.x, puglview->height - loc.y); - } + [self mouseDragged:event]; } -- (void) mouseUp:(NSEvent*)event +- (void) mouseDown:(NSEvent*)event { if (puglview->mouseFunc) { - NSPoint loc = [event locationInWindow]; + NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, 1, false, loc.x, puglview->height - loc.y); + puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), true, loc.x, loc.y); } - [self updateTrackingAreas]; } - (void) rightMouseDown:(NSEvent*)event { + [self mouseDown:event]; +} + +- (void) otherMouseDown:(NSEvent*)event +{ + [self mouseDown:event]; +} + +- (void) mouseUp:(NSEvent*)event +{ if (puglview->mouseFunc) { - NSPoint loc = [event locationInWindow]; + NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, 3, true, loc.x, puglview->height - loc.y); + puglview->mouseFunc(puglview, getFixedAppKitButton([event buttonNumber]), false, loc.x, loc.y); } } - (void) rightMouseUp:(NSEvent*)event { - if (puglview->mouseFunc) { - NSPoint loc = [event locationInWindow]; - puglview->mods = getModifiers(puglview, event); - puglview->mouseFunc(puglview, 3, false, loc.x, puglview->height - loc.y); - } + [self mouseUp:event]; +} + +- (void) otherMouseUp:(NSEvent*)event +{ + [self mouseUp:event]; } - (void) scrollWheel:(NSEvent*)event { if (puglview->scrollFunc) { - NSPoint loc = [event locationInWindow]; + NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->scrollFunc(puglview, - loc.x, puglview->height - loc.y, + loc.x, loc.y, [event deltaX], [event deltaY]); } - [self updateTrackingAreas]; } - (void) keyDown:(NSEvent*)event @@ -368,6 +437,11 @@ puglCreateWindow(PuglView* view, const char* title) [NSApplication sharedApplication]; impl->glview = [PuglOpenGLView new]; + + if (!impl->glview) { + return 1; + } + impl->glview->puglview = view; if (view->resizable) { @@ -375,6 +449,7 @@ puglCreateWindow(PuglView* view, const char* title) } if (view->parent) { + [impl->glview retain]; NSView* pview = (NSView*)view->parent; [pview addSubview:impl->glview]; return 0; @@ -451,15 +526,17 @@ puglDestroy(PuglView* view) PuglStatus puglProcessEvents(PuglView* view) { - [view->impl->glview setNeedsDisplay: YES]; - return PUGL_SUCCESS; + + // unused + (void)view; } void puglPostRedisplay(PuglView* view) { view->redisplay = true; + [view->impl->glview setNeedsDisplay:YES]; } PuglNativeWindow diff --git a/libs/dgl/src/pugl/pugl_win.cpp b/libs/dgl/src/pugl/pugl_win.cpp index 9837671..0837ac1 100644 --- a/libs/dgl/src/pugl/pugl_win.cpp +++ b/libs/dgl/src/pugl/pugl_win.cpp @@ -22,6 +22,7 @@ #include <windowsx.h> #include <GL/gl.h> +#include <ctime> #include <stdio.h> #include <stdlib.h> @@ -37,7 +38,7 @@ # define WHEEL_DELTA 120 #endif -#define PUGL_LOCAL_CLOSE_MSG (WM_USER + 50) +const int LOCAL_CLOSE_MSG = WM_USER + 50; HINSTANCE hInstance = NULL; @@ -51,6 +52,7 @@ struct PuglInternalsImpl { LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); +#if 0 extern "C" { BOOL WINAPI DllMain(HINSTANCE hInst, DWORD, LPVOID) @@ -59,6 +61,7 @@ DllMain(HINSTANCE hInst, DWORD, LPVOID) return 1; } } // extern "C" +#endif PuglInternals* puglInitInternals() @@ -79,7 +82,9 @@ puglCreateWindow(PuglView* view, const char* title) // Should class be a parameter? Does this make sense on other platforms? static int wc_count = 0; char classNameBuf[256]; - _snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d\n", title, wc_count++); + std::srand((std::time(NULL))); + _snprintf(classNameBuf, sizeof(classNameBuf), "%s_%d-%d", title, std::rand(), ++wc_count); + classNameBuf[sizeof(classNameBuf)-1] = '\0'; impl->wc.style = CS_OWNDC; impl->wc.lpfnWndProc = wndProc; @@ -90,36 +95,36 @@ puglCreateWindow(PuglView* view, const char* title) impl->wc.hCursor = LoadCursor(hInstance, IDC_ARROW); impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); impl->wc.lpszMenuName = NULL; - impl->wc.lpszClassName = classNameBuf; - RegisterClass(&impl->wc); + impl->wc.lpszClassName = strdup(classNameBuf); - int winFlags = WS_POPUPWINDOW | WS_CAPTION; - if (view->resizable) { - winFlags |= WS_SIZEBOX; + if (!RegisterClass(&impl->wc)) { + free((void*)impl->wc.lpszClassName); + free(impl); + free(view); + return 1; } // Adjust the overall window size to accomodate our requested client size + const int winFlags = WS_POPUPWINDOW | WS_CAPTION | (view->resizable ? WS_SIZEBOX : 0x0); RECT wr = { 0, 0, view->width, view->height }; - AdjustWindowRectEx(&wr, winFlags, FALSE, WS_EX_TOPMOST); + AdjustWindowRectEx(&wr, view->parent ? WS_CHILD : winFlags, FALSE, WS_EX_TOPMOST); impl->hwnd = CreateWindowEx( WS_EX_TOPMOST, classNameBuf, title, - view->parent ? WS_CHILD : winFlags, + view->parent ? (WS_CHILD | WS_VISIBLE) : winFlags, CW_USEDEFAULT, CW_USEDEFAULT, wr.right-wr.left, wr.bottom-wr.top, (HWND)view->parent, NULL, hInstance, NULL); if (!impl->hwnd) { + UnregisterClass(impl->wc.lpszClassName, NULL); + free((void*)impl->wc.lpszClassName); free(impl); free(view); return 1; } -#ifdef _WIN64 SetWindowLongPtr(impl->hwnd, GWLP_USERDATA, (LONG_PTR)view); -#else - SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG)view); -#endif impl->hdc = GetDC(impl->hwnd); @@ -137,6 +142,16 @@ puglCreateWindow(PuglView* view, const char* title) SetPixelFormat(impl->hdc, format, &pfd); impl->hglrc = wglCreateContext(impl->hdc); + if (!impl->hglrc) { + ReleaseDC (impl->hwnd, impl->hdc); + DestroyWindow (impl->hwnd); + UnregisterClass (impl->wc.lpszClassName, NULL); + free((void*)impl->wc.lpszClassName); + free(impl); + free(view); + return 1; + } + wglMakeCurrent(impl->hdc, impl->hglrc); return 0; @@ -166,6 +181,7 @@ puglDestroy(PuglView* view) ReleaseDC(view->impl->hwnd, view->impl->hdc); DestroyWindow(view->impl->hwnd); UnregisterClass(view->impl->wc.lpszClassName, NULL); + free((void*)view->impl->wc.lpszClassName); free(view->impl); free(view); } @@ -190,13 +206,13 @@ puglDisplay(PuglView* view) { wglMakeCurrent(view->impl->hdc, view->impl->hglrc); + view->redisplay = false; if (view->displayFunc) { view->displayFunc(view); } glFlush(); SwapBuffers(view->impl->hdc); - view->redisplay = false; } static PuglKey @@ -309,17 +325,21 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) case WM_MOUSEWHEEL: if (view->scrollFunc) { view->event_timestamp_ms = GetMessageTime(); + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(view->impl->hwnd, &pt); view->scrollFunc( - view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), - 0.0f, (int16_t)HIWORD(wParam) / (float)WHEEL_DELTA); + view, pt.x, pt.y, + 0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); } break; case WM_MOUSEHWHEEL: if (view->scrollFunc) { view->event_timestamp_ms = GetMessageTime(); + POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + ScreenToClient(view->impl->hwnd, &pt); view->scrollFunc( - view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), - (int16_t)HIWORD(wParam) / float(WHEEL_DELTA), 0.0f); + view, pt.x, pt.y, + GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0); } break; case WM_KEYDOWN: @@ -333,11 +353,18 @@ handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) view->specialFunc(view, message == WM_KEYDOWN, key); } } else if (view->keyboardFunc) { - view->keyboardFunc(view, message == WM_KEYDOWN, wParam); + static BYTE kbs[256]; + if (GetKeyboardState(kbs) != FALSE) { + char lb[2]; + UINT scanCode = (lParam >> 8) & 0xFFFFFF00; + if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) { + view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]); + } + } } break; case WM_QUIT: - case PUGL_LOCAL_CLOSE_MSG: + case LOCAL_CLOSE_MSG: if (view->closeFunc) { view->closeFunc(view); } @@ -358,7 +385,6 @@ puglProcessEvents(PuglView* view) handleMessage(view, msg.message, msg.wParam, msg.lParam); } - if (view->redisplay) { InvalidateRect(view->impl->hwnd, NULL, FALSE); } @@ -369,23 +395,19 @@ puglProcessEvents(PuglView* view) LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { -#ifdef _WIN64 PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWLP_USERDATA); -#else - PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA); -#endif switch (message) { case WM_CREATE: PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); return 0; case WM_CLOSE: - PostMessage(hwnd, PUGL_LOCAL_CLOSE_MSG, wParam, lParam); + PostMessage(hwnd, LOCAL_CLOSE_MSG, wParam, lParam); return 0; case WM_DESTROY: return 0; default: - if (view) { + if (view && hwnd == view->impl->hwnd) { return handleMessage(view, message, wParam, lParam); } else { return DefWindowProc(hwnd, message, wParam, lParam); diff --git a/libs/dgl/src/pugl/pugl_x11.c b/libs/dgl/src/pugl/pugl_x11.c index fdba1b6..eac570c 100644 --- a/libs/dgl/src/pugl/pugl_x11.c +++ b/libs/dgl/src/pugl/pugl_x11.c @@ -1,6 +1,7 @@ /* Copyright 2012-2014 David Robillard <http://drobilla.net> Copyright 2011-2012 Ben Loftis, Harrison Consoles + Copyright 2013 Robin Gareus <robin@gareus.org> Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above @@ -31,6 +32,10 @@ #include "pugl_internal.h" +#define SOFD_HAVE_X11 +#include "../sofd/libsofd.h" +#include "../sofd/libsofd.c" + struct PuglInternalsImpl { Display* display; int screen; @@ -49,6 +54,7 @@ static int attrListSgl[] = { GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, + GLX_ARB_multisample, 1, None }; @@ -62,11 +68,29 @@ static int attrListDbl[] = { GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, + GLX_ARB_multisample, 1, + None +}; + +/** + Attributes for double-buffered RGBA with multi-sampling + (antialiasing) +*/ +static int attrListDblMS[] = { + GLX_RGBA, + GLX_DOUBLEBUFFER , True, + GLX_RED_SIZE , 4, + GLX_GREEN_SIZE , 4, + GLX_BLUE_SIZE , 4, + GLX_ALPHA_SIZE , 4, + GLX_DEPTH_SIZE , 16, + GLX_SAMPLE_BUFFERS , 1, + GLX_SAMPLES , 4, None }; PuglInternals* -puglInitInternals() +puglInitInternals(void) { return (PuglInternals*)calloc(1, sizeof(PuglInternals)); } @@ -76,17 +100,21 @@ puglCreateWindow(PuglView* view, const char* title) { PuglInternals* impl = view->impl; - impl->display = XOpenDisplay(0); + impl->display = XOpenDisplay(NULL); impl->screen = DefaultScreen(impl->display); + impl->doubleBuffered = True; + + XVisualInfo* vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS); + + if (!vi) { + vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); + PUGL_LOG("multisampling (antialiasing) is not available\n"); + } - XVisualInfo* vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); if (!vi) { vi = glXChooseVisual(impl->display, impl->screen, attrListSgl); impl->doubleBuffered = False; - PUGL_LOG("No double buffering available\n"); - } else { - impl->doubleBuffered = True; - PUGL_LOG("Double buffered rendering enabled\n"); + PUGL_LOG("singlebuffered rendering will be used, no doublebuffering available\n"); } int glxMajor, glxMinor; @@ -202,16 +230,17 @@ puglDisplay(PuglView* view) { glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); + view->redisplay = false; + if (view->displayFunc) { view->displayFunc(view); } glFlush(); + if (view->impl->doubleBuffered) { glXSwapBuffers(view->impl->display, view->impl->win); } - - view->redisplay = false; } static PuglKey @@ -269,9 +298,16 @@ dispatchKey(PuglView* view, XEvent* event, bool press) KeySym sym; char str[5]; const int n = XLookupString(&event->xkey, str, 4, &sym, NULL); + + if (sym == XK_Escape && view->closeFunc && !press && !view->parent) { + view->closeFunc(view); + view->redisplay = false; + return; + } if (n == 0) { return; - } else if (n > 1) { + } + if (n > 1) { fprintf(stderr, "warning: Unsupported multi-byte key %X\n", (int)sym); return; } @@ -290,6 +326,26 @@ puglProcessEvents(PuglView* view) XEvent event; while (XPending(view->impl->display) > 0) { XNextEvent(view->impl->display, &event); + + if (x_fib_handle_events(view->impl->display, &event)) { + const int status = x_fib_status(); + + if (status > 0) { + char* const filename = x_fib_filename(); + x_fib_close(view->impl->display); + if (view->fileSelectedFunc) { + view->fileSelectedFunc(view, filename); + } + free(filename); + } else if (status < 0) { + x_fib_close(view->impl->display); + if (view->fileSelectedFunc) { + view->fileSelectedFunc(view, NULL); + } + } + break; + } + switch (event.type) { case MapNotify: puglReshape(view, view->width, view->height); @@ -325,9 +381,7 @@ puglProcessEvents(PuglView* view) case 6: dx = -1.0f; break; case 7: dx = 1.0f; break; } - view->scrollFunc(view, - event.xbutton.x, event.xbutton.y, - dx, dy); + view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy); } break; } @@ -345,8 +399,9 @@ puglProcessEvents(PuglView* view) setModifiers(view, event.xkey.state, event.xkey.time); dispatchKey(view, &event, true); break; - case KeyRelease: + case KeyRelease: { setModifiers(view, event.xkey.state, event.xkey.time); + bool repeated = false; if (view->ignoreKeyRepeat && XEventsQueued(view->impl->display, QueuedAfterReading)) { XEvent next; @@ -355,27 +410,27 @@ puglProcessEvents(PuglView* view) next.xkey.time == event.xkey.time && next.xkey.keycode == event.xkey.keycode) { XNextEvent(view->impl->display, &event); - break; + repeated = true; } } - dispatchKey(view, &event, false); - break; + if (!repeated) { + dispatchKey(view, &event, false); + } + } break; case ClientMessage: { char* type = XGetAtomName(view->impl->display, event.xclient.message_type); if (!strcmp(type, "WM_PROTOCOLS")) { if (view->closeFunc) { view->closeFunc(view); + view->redisplay = false; } } XFree(type); - } break; + } break; #ifdef PUGL_GRAB_FOCUS case EnterNotify: - XSetInputFocus(view->impl->display, - view->impl->win, - RevertToPointerRoot, - CurrentTime); + XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); break; #endif default: diff --git a/libs/dgl/src/sofd/libsofd.c b/libs/dgl/src/sofd/libsofd.c new file mode 100644 index 0000000..bcca9f7 --- /dev/null +++ b/libs/dgl/src/sofd/libsofd.c @@ -0,0 +1,2431 @@ +/* libSOFD - Simple Open File Dialog [for X11 without toolkit] + * + * Copyright (C) 2014 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* Test and example: + * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11 + * + * public API documentation and example code at the bottom of this file + * + * This small lib may one day include openGL rendering and + * wayland window support, but not today. Today we celebrate + * 30 years of X11. + */ + +#ifdef SOFD_TEST +#define SOFD_HAVE_X11 +#include "libsofd.h" +#endif + +#include <stdio.h> +#include <stdint.h> +#include <string.h> +#include <stdlib.h> +#include <unistd.h> +#include <libgen.h> +#include <time.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <assert.h> + +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wnarrowing" +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wnarrowing" +#endif + +// shared 'recently used' implementation +// sadly, xbel does not qualify as simple. +// hence we use a simple format alike the +// gtk-bookmark list (one file per line) + +#define MAX_RECENT_ENTRIES 24 +#define MAX_RECENT_AGE (15552000) // 180 days (in sec) + +typedef struct { + char path[1024]; + time_t atime; +} FibRecentFile; + +static FibRecentFile *_recentlist = NULL; +static unsigned int _recentcnt = 0; +static uint8_t _recentlock = 0; + +static int fib_isxdigit (const char x) { + if ( + (x >= '0' && x <= '9') + || + (x >= 'a' && x <= 'f') + || + (x >= 'A' && x <= 'F') + ) return 1; + return 0; +} + +static void decode_3986 (char *str) { + int len = strlen (str); + int idx = 0; + while (idx + 2 < len) { + char *in = &str[idx]; + if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) { + char hexstr[3]; + hexstr[0] = in[1]; + hexstr[1] = in[2]; + hexstr[2] = 0; + long hex = strtol (hexstr, NULL, 16); + *in = hex; + memmove (&str[idx+1], &str[idx + 3], len - idx - 2); + len -= 2; + } + ++idx; + } +} + +static char *encode_3986 (const char *str) { + size_t alloc, newlen; + char *ns = NULL; + unsigned char in; + size_t i = 0; + size_t length; + + if (!str) return strdup (""); + + alloc = strlen (str) + 1; + newlen = alloc; + + ns = (char*) malloc (alloc); + + length = alloc; + while (--length) { + in = *str; + + switch (in) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case 'a': case 'b': case 'c': case 'd': case 'e': + case 'f': case 'g': case 'h': case 'i': case 'j': + case 'k': case 'l': case 'm': case 'n': case 'o': + case 'p': case 'q': case 'r': case 's': case 't': + case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': + case 'F': case 'G': case 'H': case 'I': case 'J': + case 'K': case 'L': case 'M': case 'N': case 'O': + case 'P': case 'Q': case 'R': case 'S': case 'T': + case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '~': case '.': case '-': + case '/': case ',': // XXX not in RFC3986 + ns[i++] = in; + break; + default: + newlen += 2; /* this'll become a %XX */ + if (newlen > alloc) { + alloc *= 2; + ns = (char*) realloc (ns, alloc); + } + snprintf (&ns[i], 4, "%%%02X", in); + i += 3; + break; + } + ++str; + } + ns[i] = 0; + return ns; +} + +void x_fib_free_recent () { + free (_recentlist); + _recentlist = NULL; + _recentcnt = 0; +} + +static int cmp_recent (const void *p1, const void *p2) { + FibRecentFile *a = (FibRecentFile*) p1; + FibRecentFile *b = (FibRecentFile*) p2; + if (a->atime == b->atime) return 0; + return a->atime < b->atime; +} + +int x_fib_add_recent (const char *path, time_t atime) { + unsigned int i; + struct stat fs; + if (_recentlock) { return -1; } + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISREG (fs.st_mode)) { + return -1; + } + if (atime == 0) atime = time (NULL); + if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) { + return -1; + } + + for (i = 0; i < _recentcnt; ++i) { + if (!strcmp (_recentlist[i].path, path)) { + if (_recentlist[i].atime < atime) { + _recentlist[i].atime = atime; + } + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + return _recentcnt; + } + } + _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile)); + _recentlist[_recentcnt].atime = atime; + strcpy (_recentlist[_recentcnt].path, path); + qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent); + + if (_recentcnt >= MAX_RECENT_ENTRIES) { + return (_recentcnt); + } + return (++_recentcnt); +} + +#ifdef PATHSEP +#undef PATHSEP +#endif + +#ifdef PLATFORM_WINDOWS +#define DIRSEP '\\' +#else +#define DIRSEP '/' +#endif + +static void mkpath(const char *dir) { + char tmp[1024]; + char *p; + size_t len; + + snprintf (tmp, sizeof(tmp), "%s", dir); + len = strlen(tmp); + if (tmp[len - 1] == '/') + tmp[len - 1] = 0; + for (p = tmp + 1; *p; ++p) + if(*p == DIRSEP) { + *p = 0; +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif + *p = DIRSEP; + } +#ifdef PLATFORM_WINDOWS + mkdir(tmp); +#else + mkdir(tmp, 0755); +#endif +} + +int x_fib_save_recent (const char *fn) { + if (_recentlock) { return -1; } + if (!fn) { return -1; } + if (_recentcnt < 1 || !_recentlist) { return -1; } + unsigned int i; + char *dn = strdup (fn); + mkpath (dirname (dn)); + free (dn); + + FILE *rf = fopen (fn, "w"); + if (!rf) return -1; + + qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); + for (i = 0; i < _recentcnt; ++i) { + char *n = encode_3986 (_recentlist[i].path); + fprintf (rf, "%s %lu\n", n, _recentlist[i].atime); + free (n); + } + fclose (rf); + return 0; +} + +int x_fib_load_recent (const char *fn) { + char tmp[1024]; + if (_recentlock) { return -1; } + if (!fn) { return -1; } + x_fib_free_recent (); + if (access (fn, R_OK)) { + return -1; + } + FILE *rf = fopen (fn, "r"); + if (!rf) return -1; + while (fgets (tmp, sizeof(tmp), rf) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if (!(s = strchr (tmp, ' '))) { // find name <> atime sep + continue; + } + *s = '\0'; + time_t t = atol (++s); + decode_3986 (tmp); + x_fib_add_recent (tmp, t); + } + fclose (rf); + return 0; +} + +unsigned int x_fib_recent_count () { + return _recentcnt; +} + +const char *x_fib_recent_at (unsigned int i) { + if (i >= _recentcnt) + return NULL; + return _recentlist[i].path; +} + +#ifdef PLATFORM_WINDOWS +#define PATHSEP "\\" +#else +#define PATHSEP "/" +#endif + +const char *x_fib_recent_file(const char *appname) { + static char recent_file[1024]; + assert(!strchr(appname, '/')); + const char *xdg = getenv("XDG_DATA_HOME"); + if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) { + sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname); + return recent_file; + } +#ifdef PLATFORM_WINDOWS + const char * homedrive = getenv("HOMEDRIVE"); + const char * homepath = getenv("HOMEPATH"); + if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) { + sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname); + return recent_file; + } +#elif defined PLATFORM_OSX + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) { + sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname); + return recent_file; + } +#else + const char *home = getenv("HOME"); + if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) { + sprintf(recent_file, "%s/.local/share/%s/recent", home, appname); + return recent_file; + } +#endif + return NULL; +} + +#ifdef SOFD_HAVE_X11 +#include <mntent.h> +#include <dirent.h> + +#include <X11/Xlib.h> +#include <X11/Xatom.h> +#include <X11/Xutil.h> +#include <X11/keysym.h> +#include <X11/Xos.h> + +#ifndef MIN +#define MIN(A,B) ( (A) < (B) ? (A) : (B) ) +#endif + +#ifndef MAX +#define MAX(A,B) ( (A) < (B) ? (B) : (A) ) +#endif + +static Window _fib_win = 0; +static GC _fib_gc = 0; +static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5, _c_gray6; +static Font _fibfont = 0; +static Pixmap _pixbuffer = None; + +static int _fib_width = 100; +static int _fib_height = 100; +static int _btn_w = 0; +static int _btn_span = 0; + +static int _fib_font_height = 0; +static int _fib_dir_indent = 0; +static int _fib_spc_norm = 0; +static int _fib_font_ascent = 0; +static int _fib_font_vsep = 0; +static int _fib_font_size_width = 0; +static int _fib_font_time_width = 0; +static int _fib_place_width = 0; + +static int _scrl_f = 0; +static int _scrl_y0 = -1; +static int _scrl_y1 = -1; +static int _scrl_my = -1; +static int _scrl_mf = -1; +static int _view_p = -1; + +static int _fsel = -1; +static int _hov_b = -1; +static int _hov_f = -1; +static int _hov_p = -1; +static int _hov_h = -1; +static int _hov_l = -1; +static int _hov_s = -1; +static int _sort = 0; +static int _columns = 0; +static int _fib_filter_fn = 1; +static int _fib_hidden_fn = 0; +static int _fib_show_places = 0; + +static uint8_t _fib_mapped = 0; +static uint8_t _fib_resized = 0; +static unsigned long _dblclk = 0; + +static int _status = -2; +static char _rv_open[1024] = ""; + +static char _fib_cfg_custom_places[1024] = ""; +static char _fib_cfg_custom_font[256] = ""; +static char _fib_cfg_title[128] = "xjadeo - Open Video File"; + +typedef struct { + char name[256]; + int x0; + int xw; +} FibPathButton; + +typedef struct { + char name[256]; + char strtime[32]; + char strsize[32]; + int ssizew; + off_t size; + time_t mtime; + uint8_t flags; // 2: selected, 4: isdir 8: recent-entry + FibRecentFile *rfp; +} FibFileEntry; + +typedef struct { + char text[24]; + uint8_t flags; // 2: selected, 4: toggle, 8 disable + int x0; + int tw; + int xw; + void (*callback)(Display*); +} FibButton; + +typedef struct { + char name[256]; + char path[1024]; + uint8_t flags; // 1: hover, 2: selected, 4:add sep +} FibPlace; + +static char _cur_path[1024] = ""; +static FibFileEntry *_dirlist = NULL; +static FibPathButton *_pathbtn = NULL; +static FibPlace *_placelist = NULL; +static int _dircount = 0; +static int _pathparts = 0; +static int _placecnt = 0; + +static FibButton _btn_ok; +static FibButton _btn_cancel; +static FibButton _btn_filter; +static FibButton _btn_places; +static FibButton _btn_hidden; +static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok}; + +static int (*_fib_filter_function)(const char *filename); + +/* hardcoded layout */ +#define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list +#define PSEP 4 // px; horiz space beween paths +#define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column +#define LISTTOP 2.7 //em; top of the file-browser list +#define LISTBOT 4.75 //em; bottom of the file-browers list +#define BTNBTMMARGIN 0.75 //em; height/margin of the button row +#define BTNPADDING 2 // px - only used for open/cancel buttons +#define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3) +#define SCROLLBOXH 10 //px; arrow box top+bottom +#define PLACESW _fib_place_width //px; +#define PLACESWMAX (15 *_fib_spc_norm) //px; +#define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent); +#define FAREAMRGB 3 //px; base L+R margin +#define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width) +#define FAREAMRGL (_fib_show_places ? PLACESW + FAREAMRGB : FAREAMRGB) //px; left margin of file-area +#define TEXTSEP 4 //px; +#define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP +#define SORTBTNOFF -10 //px; + +#define DBLCLKTME 400 //msec; double click time +#define DRAW_OUTLINE +#define DOUBLE_BUFFER + +static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) { + XCharStruct text_structure; + int font_direction, font_ascent, font_descent; + XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc)); + + if (!fontinfo) { return -1; } + XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure); + if (w) *w = XTextWidth (fontinfo, txt, strlen (txt)); + if (h) *h = text_structure.ascent + text_structure.descent; + if (a) *a = text_structure.ascent; + if (d) *d = text_structure.descent; + XFreeFontInfo (NULL, fontinfo, 1); + return 0; +} + +static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) { + const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); +#ifdef DRAW_OUTLINE + XSetForeground (dpy, gc, _c_gray5.pixel); + XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h); + XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h); + + XSetForeground (dpy, gc, blackColor); + XDrawLine (dpy, d, gc, x + 1, y, x + w, y); + XDrawLine (dpy, d, gc, x, y + 1, x, y + h); +#else + XSetForeground (dpy, _fib_gc, blackColor); + XDrawRectangle (dpy, d, gc, x, y, w, h); +#endif +} + +static void fib_expose (Display *dpy, Window realwin) { + int i; + XID win; + const unsigned long whiteColor = WhitePixel (dpy, DefaultScreen (dpy)); + const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); + if (!_fib_mapped) return; + + if (_fib_resized +#ifdef DOUBLE_BUFFER + || !_pixbuffer +#endif + ) + { +#ifdef DOUBLE_BUFFER + unsigned int w = 0, h = 0; + if (_pixbuffer != None) { + Window ignored_w; + int ignored_i; + unsigned int ignored_u; + XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u); + if (_fib_width != (int)w || _fib_height != (int)h) { + XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + } + } + if (_pixbuffer == None) { + XWindowAttributes wa; + XGetWindowAttributes (dpy, realwin, &wa); + _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth); + } +#endif + if (_pixbuffer != None) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height); + } + _fib_resized = 0; + } + + if (_pixbuffer == None) { + win = realwin; + } else { + win = _pixbuffer; + } + + // Top Row: dirs and up navigation + + int ppw = 0; + int ppx = FAREAMRGB; + + for (i = _pathparts - 1; i >= 0; --i) { + ppw += _pathbtn[i].xw + PSEP; + if (ppw >= _fib_width - PSEP - _pathbtn[0].xw - FAREAMRGB) break; // XXX, first change is from "/" to "<", NOOP + } + ++i; + // border-less "<" parent/up, IFF space is limited + if (i > 0) { + if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, blackColor); + } + XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1); + ppx += _pathbtn[0].xw + PSEP; + if (i == _pathparts) --i; + } + + _view_p = i; + + while (i < _pathparts) { + if (i == _hov_p) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + ppx + 1, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw - 1, _fib_font_height); + VDrawRectangle (dpy, win, _fib_gc, + ppx, PATHBTNTOP - _fib_font_ascent, + _pathbtn[i].xw, _fib_font_height); + XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP, + _pathbtn[i].name, strlen (_pathbtn[i].name)); + _pathbtn[i].x0 = ppx; // current position + ppx += _pathbtn[i].xw + PSEP; + ++i; + } + + // middle, scroll list of file names + const int ltop = LISTTOP * _fib_font_vsep; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 + llen * _fib_font_vsep; + const int fsel_width = _fib_width - FAREAMRGL - FAREAMRGR - (llen < _dircount ? SCROLLBARW : 0); + const int t_x = FAREATEXTL; + int t_s = FAREATEXTL + fsel_width; + int t_t = FAREATEXTL + fsel_width; + + // check which colums can be visible + // depending on available width of window. + _columns = 0; + if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) { + _columns |= 2; + t_s = FAREAMRGL + fsel_width - _fib_font_time_width - TEXTSEP; + } + if (fsel_width > FILECOLUMN + _fib_font_size_width) { + _columns |= 1; + t_t = t_s - _fib_font_size_width - TEXTSEP; + } + + int fstop = _scrl_f; // first entry in scroll position + const int ttop = ltop - _fib_font_height + _fib_font_ascent; + + if (fstop > 0 && fstop + llen > _dircount) { + fstop = MAX (0, _dircount - llen); + _scrl_f = fstop; + } + + // list header + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep); + + // draw background of file list + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop, fsel_width, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop - _fib_font_vsep -1, _fib_width - FAREAMRGL - FAREAMRGR, fsel_height + _fib_font_vsep + 1); +#endif + + switch (_hov_h) { + case 1: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, t_x + _fib_dir_indent - TEXTSEP + 1, ltop - _fib_font_vsep, t_t - t_x - _fib_dir_indent - 1, _fib_font_vsep); + break; + case 2: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, t_t - TEXTSEP + 1, ltop - _fib_font_vsep, _fib_font_size_width + TEXTSEP - 1, _fib_font_vsep); + break; + case 3: + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + XFillRectangle (dpy, win, _fib_gc, t_s - TEXTSEP + 1, ltop - _fib_font_vsep, TEXTSEP + TEXTSEP + _fib_font_time_width - 1, _fib_font_vsep); + break; + default: + break; + } + + // column headings and sort order + int arp = MAX (2, _fib_font_height / 5); // arrow scale + const int trioff = _fib_font_height - _fib_font_ascent - arp + 1; + XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1}, {-arp - arp, 0}, {arp, arp + arp + 1}}; + if (_sort & 1) { + ptri[0].y = ttop -arp - arp - 1; + ptri[1].y *= -1; + ptri[3].y *= -1; + } + switch (_sort) { + case 0: + case 1: + ptri[0].x = t_t + SORTBTNOFF + 2 - arp; + XSetForeground (dpy, _fib_gc, _c_gray6.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + break; + case 2: + case 3: + if (_columns & 1) { + ptri[0].x = t_s + SORTBTNOFF + 2 - arp; + XSetForeground (dpy, _fib_gc, _c_gray6.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + case 4: + case 5: + if (_columns & 2) { + ptri[0].x = FAREATEXTL + fsel_width + SORTBTNOFF + 2 - arp; + XSetForeground (dpy, _fib_gc, _c_gray6.pixel); + XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); + } + break; + } + +#if 0 // bottom header bottom border + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, + FAREAMRGL + 1, ltop, + FAREAMRGL + fsel_width, ltop); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); +#endif + + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + t_x + _fib_dir_indent - TEXTSEP, ltop - _fib_font_vsep + 3, + t_x + _fib_dir_indent - TEXTSEP, ltop - 3); + + XSetForeground (dpy, _fib_gc, blackColor); + XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4); + + if (_columns & 1) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + t_t - TEXTSEP, ltop - _fib_font_vsep + 3, + t_t - TEXTSEP, ltop - 3); + XSetForeground (dpy, _fib_gc, blackColor); + XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4); + } + + if (_columns & 2) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + t_s - TEXTSEP, ltop - _fib_font_vsep + 3, + t_s - TEXTSEP, ltop - 3); + XSetForeground (dpy, _fib_gc, blackColor); + if (_pathparts > 0) + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13); + else + XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9); + } + + // scrollbar sep + if (llen < _dircount) { + const int sx0 = _fib_width - SCROLLBARW - FAREAMRGR; + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + XDrawLine (dpy, win, _fib_gc, + sx0 - 1, ltop - _fib_font_vsep, +#ifdef DRAW_OUTLINE + sx0 - 1, ltop + fsel_height +#else + sx0 - 1, ltop - 1 +#endif + ); + } + + // clip area for file-name + XRectangle clp = {FAREAMRGL + 1, ltop, t_t - FAREAMRGL - TEXTSEP - TEXTSEP - 1, fsel_height}; + + // list files in view + for (i = 0; i < llen; ++i) { + const int j = i + fstop; + if (j >= _dircount) break; + + const int t_y = ltop + (i+1) * _fib_font_vsep - 4; + + XSetForeground (dpy, _fib_gc, blackColor); + if (_dirlist[j].flags & 2) { + XSetForeground (dpy, _fib_gc, blackColor); + XFillRectangle (dpy, win, _fib_gc, + FAREAMRGL, t_y - _fib_font_ascent, fsel_width, _fib_font_height); + XSetForeground (dpy, _fib_gc, whiteColor); + } + if (_hov_f == j && !(_dirlist[j].flags & 2)) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } + if (_dirlist[j].flags & 4) { + XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1); + } + XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted); + XDrawString (dpy, win, _fib_gc, + t_x + _fib_dir_indent, t_y, + _dirlist[j].name, strlen (_dirlist[j].name)); + XSetClipMask (dpy, _fib_gc, None); + + if (_columns & 1) // right-aligned 'size' + XDrawString (dpy, win, _fib_gc, + t_s - TEXTSEP - 2 - _dirlist[j].ssizew, t_y, + _dirlist[j].strsize, strlen (_dirlist[j].strsize)); + if (_columns & 2) + XDrawString (dpy, win, _fib_gc, + t_s, t_y, + _dirlist[j].strtime, strlen (_dirlist[j].strtime)); + } + + // scrollbar + if (llen < _dircount) { + float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; + sl = MAX ((8. / llen), sl); // 8px min height of scroller + const int sy1 = llen * sl; + const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) - sy1) / (float)(_dircount - llen); + const int sy0 = fstop * mx; + const int sx0 = _fib_width - SCROLLBARW - FAREAMRGR; + const int stop = ltop - _fib_font_vsep; + + _scrl_y0 = stop + SCROLLBOXH + sy0; + _scrl_y1 = _scrl_y0 + sy1; + + assert (fstop + llen <= _dircount); + // scroll-bar background + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW, fsel_height + _fib_font_vsep); + + // scroller + if (_hov_s == 0) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } + XFillRectangle (dpy, win, _fib_gc, sx0 + 1, stop + SCROLLBOXH + sy0, SCROLLBARW - 2, sy1); + + int scrw = (SCROLLBARW -3) / 2; + // arrows top and bottom + if (_hov_s == 1) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } + XPoint ptst[4] = { {sx0 + 1, stop + 8}, {scrw, -7}, {scrw, 7}, {-2 * scrw, 0}}; + XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious); + + if (_hov_s == 2) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } + XPoint ptsb[4] = { {sx0 + 1, ltop + fsel_height - 9}, {2*scrw, 0}, {-scrw, 7}, {-scrw, -7}}; + XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious); + XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious); + } else { + _scrl_y0 = _scrl_y1 = -1; + } + + if (_fib_show_places) { + assert (_placecnt > 0); + + // heading + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop - _fib_font_vsep, PLACESW - TEXTSEP, _fib_font_vsep); + + // body + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + XFillRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop, PLACESW - TEXTSEP, fsel_height); + +#ifdef DRAW_OUTLINE + VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP, fsel_height + _fib_font_vsep + 1); +#endif + + XSetForeground (dpy, _fib_gc, blackColor); + XDrawString (dpy, win, _fib_gc, FAREAMRGB + TEXTSEP, ttop, "Places", 6); + + XRectangle pclip = {FAREAMRGB + 1, ltop, PLACESW - TEXTSEP -1, fsel_height}; + XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted); + const int plx = FAREAMRGB + TEXTSEP; + for (i = 0; i < llen && i < _placecnt; ++i) { + const int ply = ltop + (i+1) * _fib_font_vsep - 4; + if (i == _hov_l) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, blackColor); + } + XDrawString (dpy, win, _fib_gc, + plx, ply, + _placelist[i].name, strlen (_placelist[i].name)); + if (_placelist[i].flags & 4) { + XSetForeground (dpy, _fib_gc, _c_gray3.pixel); + const int plly = ply - _fib_font_ascent + _fib_font_height; + const int pllx0 = FAREAMRGB; + const int pllx1 = FAREAMRGB + (PLACESW - TEXTSEP); + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + } + } + XSetClipMask (dpy, _fib_gc, None); + + if (_placecnt > llen) { + const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent; + const int pllx0 = FAREAMRGB + (PLACESW - TEXTSEP) * .75; + const int pllx1 = FAREAMRGB + (PLACESW - TEXTSEP - TEXTSEP); + + XSetForeground (dpy, _fib_gc, blackColor); + XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); + XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + } + } + + // Bottom Buttons + const int numb = sizeof(_btns) / sizeof(FibButton*); + int xtra = _fib_width - _btn_span; + const int cbox = _fib_font_ascent - 2; + const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING; + const int cblw = cbox > 20 ? 5 : ( cbox > 9 ? 3 : 1); + + int bx = FAREAMRGB; + for (i = 0; i < numb; ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + // checkbutton + const int cby0 = bbase - cbox + 1 + BTNPADDING; + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray4.pixel); + } else { + XSetForeground (dpy, _fib_gc, blackColor); + } + XDrawRectangle (dpy, win, _fib_gc, + bx, cby0 - 1, cbox + 1, cbox + 1); + + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray5.pixel); + } else { + XSetForeground (dpy, _fib_gc, blackColor); + } + XDrawString (dpy, win, _fib_gc, BTNPADDING + bx + _fib_font_ascent, 1 + bbase + BTNPADDING, + _btns[i]->text, strlen (_btns[i]->text)); + + if (i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + if (_btns[i]->flags & 2) { + XSetForeground (dpy, _fib_gc, _c_gray1.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + } + XFillRectangle (dpy, win, _fib_gc, + bx+1, cby0, cbox, cbox); + + if (_btns[i]->flags & 2) { + XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter); + XSetForeground (dpy, _fib_gc, _c_gray6.pixel); + XDrawLine (dpy, win, _fib_gc, + bx + 2, cby0 + 1, + bx + cbox - 1, cby0 + cbox - 2); + XDrawLine (dpy, win, _fib_gc, + bx + cbox - 1, cby0 + 1, + bx + 2, cby0 + cbox - 2); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + } + } else { + if (xtra > 0) { + bx += xtra; + xtra = 0; + } + // pushbutton + + uint8_t can_hover = 1; // special case + if (_btns[i] == &_btn_ok) { + if (_fsel < 0 || _fsel >= _dircount) { + can_hover = 0; + } + } + + if (can_hover && i == _hov_b) { + XSetForeground (dpy, _fib_gc, _c_gray0.pixel); + } else { + XSetForeground (dpy, _fib_gc, _c_gray2.pixel); + } + XFillRectangle (dpy, win, _fib_gc, + bx + 1, bbase - _fib_font_ascent, + _btn_w - 1, _fib_font_height + BTNPADDING + BTNPADDING); + VDrawRectangle (dpy, win, _fib_gc, + bx, bbase - _fib_font_ascent, + _btn_w, _fib_font_height + BTNPADDING + BTNPADDING); + XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING, + _btns[i]->text, strlen (_btns[i]->text)); + } + _btns[i]->x0 = bx; + bx += _btns[i]->xw + DSEP; + } + + if (_pixbuffer != None) { + XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0); + } + XFlush (dpy); +} + +static void fib_reset () { + _hov_p = _hov_f = _hov_h = _hov_l = -1; + _scrl_f = 0; + _fib_resized = 1; +} + +static int cmp_n_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (a->name, b->name); +} + +static int cmp_n_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + return strcmp (b->name, a->name); +} + +static int cmp_t_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? -1 : 1; +} + +static int cmp_t_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->mtime == b->mtime) return 0; + return a->mtime > b->mtime ? 1 : -1; +} + +static int cmp_s_up (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? -1 : 1; +} + +static int cmp_s_down (const void *p1, const void *p2) { + FibFileEntry *a = (FibFileEntry*) p1; + FibFileEntry *b = (FibFileEntry*) p2; + if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order + if ((a->flags & 4) && !(b->flags & 4)) return -1; + if (!(a->flags & 4) && (b->flags & 4)) return 1; + if (a->size == b->size) return 0; + return a->size > b->size ? 1 : -1; +} + +static void fmt_size (Display *dpy, FibFileEntry *f) { + if (f->size > 10995116277760) { + sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f); + } + if (f->size > 1099511627776) { + sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f); + } + else if (f->size > 10737418240) { + sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f); + } + else if (f->size > 1073741824) { + sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f); + } + else if (f->size > 10485760) { + sprintf (f->strsize, "%.0f MB", f->size / 1048576.f); + } + else if (f->size > 1048576) { + sprintf (f->strsize, "%.1f MB", f->size / 1048576.f); + } + else if (f->size > 10240) { + sprintf (f->strsize, "%.0f KB", f->size / 1024.f); + } + else if (f->size >= 1000) { + sprintf (f->strsize, "%.1f KB", f->size / 1024.f); + } + else { + sprintf (f->strsize, "%.0f B", f->size / 1.f); + } + int sw = 0; + query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL); + if (sw > _fib_font_size_width) { + _fib_font_size_width = sw; + } + f->ssizew = sw; +} + +static void fmt_time (Display *dpy, FibFileEntry *f) { + struct tm *tmp; + tmp = localtime (&f->mtime); + if (!tmp) { + return; + } + strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp); + + int tw = 0; + query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL); + if (tw > _fib_font_time_width) { + _fib_font_time_width = tw; + } +} + +static void fib_resort (const char * sel) { + if (_dircount < 1) { return; } + int (*sortfn)(const void *p1, const void *p2); + switch (_sort) { + case 1: sortfn = &cmp_n_down; break; + case 2: sortfn = &cmp_s_down; break; + case 3: sortfn = &cmp_s_up; break; + case 4: sortfn = &cmp_t_down; break; + case 5: sortfn = &cmp_t_up; break; + default: + sortfn = &cmp_n_up; + break; + } + qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn); + int i; + for (i = 0; i < _dircount && sel; ++i) { + if (!strcmp (_dirlist[i].name, sel)) { + _fsel = i; + break; + } + } +} + +static void fib_select (Display *dpy, int item) { + if (_fsel >= 0) { + _dirlist[_fsel].flags &= ~2; + } + _fsel = item; + if (_fsel >= 0 && _fsel < _dircount) { + _dirlist[_fsel].flags |= 2; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (_fsel < _scrl_f) { + _scrl_f = _fsel; + } + else if (_fsel >= _scrl_f + llen) { + _scrl_f = 1 + _fsel - llen; + } + } else { + _fsel = -1; + } + + fib_expose (dpy, _fib_win); +} + +static inline int fib_filter (const char *name) { + if (_fib_filter_function) { + return _fib_filter_function (name); + } else { + return 1; + } +} + +static void fib_pre_opendir (Display *dpy) { + if (_dirlist) free (_dirlist); + if (_pathbtn) free (_pathbtn); + _dirlist = NULL; + _pathbtn = NULL; + _dircount = 0; + _pathparts = 0; + query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL); + fib_reset (); + _fsel = -1; +} + +static void fib_post_opendir (Display *dpy, const char *sel) { + if (_dircount > 0) + _fsel = 0; // select first + else + _fsel = -1; + fib_resort (sel); + + if (_dircount > 0 && _fsel >= 0) { + fib_select (dpy, _fsel); + } else { + fib_expose (dpy, _fib_win); + } +} + +static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) { + char tp[1024]; + struct stat fs; + if (!_fib_hidden_fn && name[0] == '.') return -1; + if (!strcmp (name, ".")) return -1; + if (!strcmp (name, "..")) return -1; + strcpy (tp, path); + strcat (tp, name); + if (access (tp, R_OK)) { + return -1; + } + if (stat (tp, &fs)) { + return -1; + } + assert (i < _dircount); // could happen if dir changes while we're reading. + if (i >= _dircount) return -1; + if (S_ISDIR (fs.st_mode)) { + _dirlist[i].flags |= 4; + } + else if (S_ISREG (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#if 0 // only needed with lstat() + else if (S_ISLNK (fs.st_mode)) { + if (!fib_filter (name)) return -1; + } +#endif + else { + return -1; + } + strcpy (_dirlist[i].name, name); + _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime; + _dirlist[i].size = fs.st_size; + if (!(_dirlist[i].flags & 4)) + fmt_size (dpy, &_dirlist[i]); + fmt_time (dpy, &_dirlist[i]); + return 0; +} + +static int fib_openrecent (Display *dpy, const char *sel) { + int i; + unsigned int j; + assert (_recentcnt > 0); + fib_pre_opendir (dpy); + query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL); + _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry)); + _dircount = _recentcnt; + for (j = 0, i = 0; j < _recentcnt; ++j) { + char base[1024]; + char *s = strrchr (_recentlist[j].path, '/'); + if (!s || !*++s) continue; + size_t len = (s - _recentlist[j].path); + strncpy (base, _recentlist[j].path, len); + base[len] = '\0'; + if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) { + _dirlist[i].rfp = &_recentlist[j]; + _dirlist[i].flags |= 8; + ++i; + } + } + _dircount = i; + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_opendir (Display *dpy, const char* path, const char *sel) { + char *t0, *t1; + int i; + + assert (path); + + if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this + strcpy (_cur_path, ""); + return fib_openrecent (dpy, sel); + } + + assert (strlen (path) < sizeof(_cur_path) -1); + assert (strlen (path) > 0); + assert (strstr (path, "//") == NULL); + assert (path[0] == '/'); + + fib_pre_opendir (dpy); + + query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL); + DIR *dir = opendir (path); + if (!dir) { + strcpy (_cur_path, "/"); + } else { + int i; + struct dirent *de; + strcpy (_cur_path, path); + + if (_cur_path[strlen (_cur_path) -1] != '/') + strcat (_cur_path, "/"); + + while ((de = readdir (dir))) { + if (!_fib_hidden_fn && de->d_name[0] == '.') continue; + ++_dircount; + } + + if (_dircount > 0) + _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry)); + + rewinddir (dir); + + i = 0; + while ((de = readdir (dir))) { + if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0)) + ++i; + } + _dircount = i; + closedir (dir); + } + + t0 = _cur_path; + while (*t0 && (t0 = strchr (t0, '/'))) { + ++_pathparts; + ++t0; + } + assert (_pathparts > 0); + _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton)); + + t1 = _cur_path; + i = 0; + while (*t1 && (t0 = strchr (t1, '/'))) { + if (i == 0) { + strcpy (_pathbtn[i].name, "/"); + } else { + *t0 = 0; + strcpy (_pathbtn[i].name, t1); + } + query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL); + _pathbtn[i].xw += BTNPADDING + BTNPADDING; + *t0 = '/'; + t1 = t0 + 1; + ++i; + } + fib_post_opendir (dpy, sel); + return _dircount; +} + +static int fib_open (Display *dpy, int item) { + char tp[1024]; + if (_dirlist[item].flags & 8) { + assert (_dirlist[item].rfp); + strcpy (_rv_open, _dirlist[item].rfp->path); + _status = 1; + return 0; + } + strcpy (tp, _cur_path); + strcat (tp, _dirlist[item].name); + if (_dirlist[item].flags & 4) { + fib_opendir (dpy, tp, NULL); + return 0; + } else { + _status = 1; + strcpy (_rv_open, tp); + } + return 0; +} + +static void cb_cancel (Display *dpy) { + _status = -1; + + // unused + return; (void)dpy; +} + +static void cb_open (Display *dpy) { + if (_fsel >= 0 && _fsel < _dircount) { + fib_open (dpy, _fsel); + } +} + +static void sync_button_states () { + if (_fib_show_places) + _btn_places.flags |= 2; + else + _btn_places.flags &= ~2; + if (_fib_filter_fn) // inverse -> show all + _btn_filter.flags &= ~2; + else + _btn_filter.flags |= 2; + if (_fib_hidden_fn) + _btn_hidden.flags |= 2; + else + _btn_hidden.flags &= ~2; +} + +static void cb_places (Display *dpy) { + _fib_show_places = ! _fib_show_places; + if (_placecnt < 1) + _fib_show_places = 0; + sync_button_states (); + _fib_resized = 1; + fib_expose (dpy, _fib_win); +} + +static void cb_filter (Display *dpy) { + _fib_filter_fn = ! _fib_filter_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static void cb_hidden (Display *dpy) { + _fib_hidden_fn = ! _fib_hidden_fn; + sync_button_states (); + char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; + fib_opendir (dpy, _cur_path, sel); + free (sel); +} + +static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) { + const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING; + const int bbot = btop + _fib_font_height + BTNPADDING + BTNPADDING; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int ltop = LISTTOP * _fib_font_vsep; + const int fbot = ltop + 4 + llen * _fib_font_vsep; + const int ptop = PATHBTNTOP - _fib_font_ascent; + assert (it); + + // paths at top + if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) { + int i = _view_p; + *it = -1; + if (i > 0) { // special case '<' + if (x > FAREAMRGB && x <= FAREAMRGB + _pathbtn[0].xw) { + *it = _view_p - 1; + i = _pathparts; + } + } + while (i < _pathparts) { + if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) { + *it = i; + break; + } + ++i; + } + assert (*it < _pathparts); + if (*it >= 0) return 1; + else return 0; + } + + // buttons at bottom + if (y > btop && y < bbot) { + size_t i; + *it = -1; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + const int bx = _btns[i]->x0; + if (_btns[i]->flags & 8) { continue; } + if (x > bx && x < bx + _btns[i]->xw) { + *it = i; + } + } + if (*it >= 0) return 3; + else return 0; + } + + // main file area + if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL && x < _fib_width - FAREAMRGR) { + // scrollbar + if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) && x <= _fib_width - FAREAMRGR) { + if (y >= _scrl_y0 && y < _scrl_y1) { + *it = 0; + } else if (y >= _scrl_y1) { + *it = 2; + } else { + *it = 1; + } + return 4; + } + // file-list + else if (y >= ltop) { + const int item = (y - ltop) / _fib_font_vsep + _scrl_f; + *it = -1; + if (item >= 0 && item < _dircount) { + *it = item; + } + if (*it >= 0) return 2; + else return 0; + } + else { + *it = -1; + const int fsel_width = _fib_width - FAREAMRGL - FAREAMRGR - (llen < _dircount ? SCROLLBARW : 0); + const int t_s = FAREAMRGL + fsel_width - _fib_font_time_width - TEXTSEP - TEXTSEP; + const int t_t = FAREAMRGL + fsel_width - TEXTSEP - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP + TEXTSEP) : 0); + if (x >= fsel_width + FAREAMRGL) ; + else if ((_columns & 2) && x >= t_s) *it = 3; + else if ((_columns & 1) && x >= t_t) *it = 2; + else if (x >= FAREATEXTL + _fib_dir_indent - TEXTSEP) *it = 1; + if (*it >= 0) return 5; + else return 0; + } + } + + // places list + if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB && x < FAREAMRGL - FAREAMRGB) { + const int item = (y - ltop) / _fib_font_vsep; + *it = -1; + if (item >= 0 && item < _placecnt) { + *it = item; + } + if (*it >= 0) return 6; + else return 0; + } + + return 0; + + // unused + (void)dpy; +} + +static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) { + int hov_p = -1; + int hov_b = -1; + int hov_h = -1; + int hov_s = -1; +#ifdef LIST_ENTRY_HOVER + int hov_f = -1; + int hov_l = -1; +#endif + + switch (type) { + case 1: hov_p = item; break; + case 3: hov_b = item; break; + case 4: hov_s = item; break; + case 5: hov_h = item; break; +#ifdef LIST_ENTRY_HOVER + case 6: hov_l = item; break; + case 2: hov_f = item; break; +#endif + default: break; + } +#ifdef LIST_ENTRY_HOVER + if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; } + if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; } +#endif + if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; } + if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; } + if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; } + if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; } + + if (need_expose) { + fib_expose (dpy, _fib_win); + } +} + +static void fib_motion (Display *dpy, int x, int y) { + int it = -1; + + if (_scrl_my >= 0) { + const int sdiff = y - _scrl_my; + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + const int fsel_height = 4 + llen * _fib_font_vsep; + const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; + + int news = _scrl_mf + sdiff / sl; + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f) { + _scrl_f = news; + fib_expose (dpy, _fib_win); + } + return; + } + + const int type = fib_widget_at_pos (dpy, x, y, &it); + fib_update_hover (dpy, 0, type, it); +} + +static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) { + int it; + switch (fib_widget_at_pos (dpy, x, y, &it)) { + case 4: // scrollbar + if (btn == 1) { + _dblclk = 0; + if (it == 0) { + _scrl_my = y; + _scrl_mf = _scrl_f; + } else { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 2) llen = 2; + int news = _scrl_f; + if (it == 1) { + news -= llen - 1; + } else { + news += llen - 1; + } + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 4, it); + } + } + } + break; + case 2: // file-list + if (btn == 4 || btn == 5) { + const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + int news = _scrl_f + ((btn == 4) ? - 1 : 1); + if (news < 0) news = 0; + if (news >= (_dircount - llen)) news = _dircount - llen; + if (news != _scrl_f && _scrl_y0 >= 0) { + assert (news >=0); + _scrl_f = news; + fib_update_hover (dpy, 1, 0, 0); + } + _dblclk = 0; + } + else if (btn == 1 && it >= 0 && it < _dircount) { + if (_fsel == it) { + if (time - _dblclk < DBLCLKTME) { + fib_open (dpy, it); + _dblclk = 0; + } + _dblclk = time; + } else { + fib_select (dpy, it); + _dblclk = time; + } + /*if (_fsel >= 0) { + if (!(_dirlist[_fsel].flags & 4)); + }*/ + } + break; + case 1: // paths + assert (_fsel < _dircount); + assert (it >= 0 && it < _pathparts); + { + int i = 0; + char path[1024] = "/"; + while (++i <= it) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = NULL; + if (i < _pathparts) + sel = strdup (_pathbtn[i].name); + else if (i == _pathparts && _fsel >= 0) + sel = strdup (_dirlist[_fsel].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case 3: // btn + if (btn == 1 && _btns[it]->callback) { + _btns[it]->callback (dpy); + } + break; + case 5: // sort + if (btn == 1) { + switch (it) { + case 1: if (_sort == 0) _sort = 1; else _sort = 0; break; + case 2: if (_sort == 2) _sort = 3; else _sort = 2; break; + case 3: if (_sort == 4) _sort = 5; else _sort = 4; break; + } + if (_fsel >= 0) { + assert (_dirlist && _dircount >= _fsel); + _dirlist[_fsel].flags &= ~2; + char *sel = strdup (_dirlist[_fsel].name); + fib_resort (sel); + free (sel); + } else { + fib_resort (NULL); + _fsel = -1; + } + fib_reset (); + _hov_h = it; + fib_select (dpy, _fsel); + } + break; + case 6: + if (btn == 1 && it >= 0 && it < _placecnt) { + fib_opendir (dpy, _placelist[it].path, NULL); + } + break; + default: + break; + } +} + +static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) { + _scrl_my = -1; + + // unused + return; (void)dpy; (void)x; (void)y; (void)btn; (void)time; +} + +static void add_place_raw (Display *dpy, const char *name, const char *path) { + _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace)); + strcpy (_placelist[_placecnt].path, path); + strcpy (_placelist[_placecnt].name, name); + _placelist[_placecnt].flags = 0; + + int sw; + query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL); + if (sw > _fib_place_width) { + _fib_place_width = sw; + } + ++_placecnt; +} + +static int add_place_places (Display *dpy, const char *name, const char *url) { + char const * path; + struct stat fs; + int i; + if (!url || strlen (url) < 1) return -1; + if (!name || strlen (name) < 1) return -1; + if (url[0] == '/') { + path = url; + } + else if (!strncmp (url, "file:///", 8)) { + path = &url[7]; + } + else { + return -1; + } + + if (access (path, R_OK)) { + return -1; + } + if (stat (path, &fs)) { + return -1; + } + if (!S_ISDIR (fs.st_mode)) { + return -1; + } + + for (i = 0; i < _placecnt; ++i) { + if (!strcmp (path, _placelist[i].path)) { + return -1; + } + } + add_place_raw (dpy, name, path); + return 0; +} + +static int parse_gtk_bookmarks (Display *dpy, const char *fn) { + char tmp[1024]; + if (access (fn, R_OK)) { + return -1; + } + FILE *bm = fopen (fn, "r"); + if (!bm) return -1; + int found = 0; + while (fgets (tmp, sizeof(tmp), bm) + && strlen (tmp) > 1 + && strlen (tmp) < sizeof(tmp)) + { + char *s, *n; + tmp[strlen (tmp) - 1] = '\0'; // strip newline + if ((s = strchr (tmp, ' '))) { + *s = '\0'; + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } else if ((s = strrchr (tmp, '/'))) { + n = strdup (++s); + decode_3986 (tmp); + if (!add_place_places (dpy, n, tmp)) { + ++found; + } + free (n); + } + } + fclose (bm); + return found; +} + +static const char *ignore_mountpoints[] = { + "/bin", "/boot", "/dev", "/etc", + "/lib", "/live", "/mnt", "/opt", + "/root", "/sbin", "/srv", "/tmp", + "/usr", "/var", "/proc", "/sbin", + "/net", "/sys" +}; + +static const char *ignore_fs[] = { + "auto", "autofs", + "debugfs", "devfs", + "devpts", "ecryptfs", + "fusectl", "kernfs", + "linprocfs", "proc", + "ptyfs", "rootfs", + "selinuxfs", "sysfs", + "tmpfs", "usbfs", + "nfsd", "rpc_pipefs", +}; + +static const char *ignore_devices[] = { + "binfmt_", "devpts", + "gvfs", "none", + "nfsd", "sunrpc", + "/dev/loop", "/dev/vn" +}; + +static int check_mount (const char *mountpoint, const char *fs, const char *device) { + size_t i; + if (!mountpoint || !fs || !device) return -1; + //printf("%s %s %s\n", mountpoint, fs, device); + for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) { + if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) { + return 1; + } + } + if (!strncmp (mountpoint, "/home", 5)) { + return 1; + } + for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) { + if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) { + return 1; + } + } + for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) { + if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) { + return 1; + } + } + return 0; +} + +static int read_mtab (Display *dpy, const char *mtab) { + FILE *mt = fopen (mtab, "r"); + if (!mt) return -1; + int found = 0; + struct mntent *mntent; + while ((mntent = getmntent (mt)) != NULL) { + char *s; + if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname)) + continue; + + if ((s = strrchr (mntent->mnt_dir, '/'))) { + ++s; + } else { + s = mntent->mnt_dir; + } + if (!add_place_places (dpy, s, mntent->mnt_dir)) { + ++found; + } + } + fclose (mt); + return found; +} + +static void populate_places (Display *dpy) { + char tmp[1024]; + int spacer = -1; + if (_placecnt > 0) return; + _fib_place_width = 0; + + if (_recentcnt > 0) { + add_place_raw (dpy, "Recently Used", ""); + _placelist[0].flags |= 4; + } + + add_place_places (dpy, "Home", getenv ("HOME")); + + if (getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/Desktop"); + add_place_places (dpy, "Desktop", tmp); + } + + add_place_places (dpy, "Filesystem", "/"); + + if (_placecnt > 0) spacer = _placecnt -1; + + if (strlen (_fib_cfg_custom_places) > 0) { + parse_gtk_bookmarks (dpy, _fib_cfg_custom_places); + } + + if (read_mtab (dpy, "/proc/mounts") < 1) { + read_mtab (dpy, "/etc/mtab"); + } + + int parsed_bookmarks = 0; + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.gtk-bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) { + strcpy (tmp, getenv ("XDG_CONFIG_HOME")); + strcat (tmp, "/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (!parsed_bookmarks && getenv ("HOME")) { + strcpy (tmp, getenv ("HOME")); + strcat (tmp, "/.config/gtk-3.0/bookmarks"); + if (parse_gtk_bookmarks (dpy, tmp) > 0) { + parsed_bookmarks = 1; + } + } + if (_fib_place_width > 0) { + _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX); + } + if (spacer > 0 && spacer < _placecnt -1) { + _placelist[ spacer ].flags |= 4; + } +} + +static uint8_t font_err = 0; +static int x_error_handler (Display *d, XErrorEvent *e) { + font_err = 1; + return 0; + + // unused + (void)d; (void)e; +} + +int x_fib_show (Display *dpy, Window parent, int x, int y) { + if (_fib_win) { + XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime); + return -1; + } + + _status = 0; + _rv_open[0] = '\0'; + + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + _c_gray1.flags= DoRed | DoGreen | DoBlue; + _c_gray0.red = _c_gray0.green = _c_gray0.blue = 61710; // 95% hover prelight + _c_gray1.red = _c_gray1.green = _c_gray1.blue = 60416; // 93% window bg, scrollbar-fg + _c_gray2.red = _c_gray2.green = _c_gray2.blue = 54016; // 83% button & list bg + _c_gray3.red = _c_gray3.green = _c_gray3.blue = 48640; // 75% heading + scrollbar-bg + _c_gray4.red = _c_gray4.green = _c_gray4.blue = 26112; // 40% prelight text, sep lines + _c_gray5.red = _c_gray5.green = _c_gray5.blue = 12800; // 20% 3D border + _c_gray6.red = _c_gray6.green = _c_gray6.blue = 6400; // 10% checkbox cross, sort triangles + + if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1; + if (!XAllocColor (dpy, colormap, &_c_gray6)) return -1; + + XSetWindowAttributes attr; + memset (&attr, 0, sizeof(XSetWindowAttributes)); + attr.border_pixel = _c_gray2.pixel; + + attr.event_mask = ExposureMask | KeyPressMask + | ButtonPressMask | ButtonReleaseMask + | ConfigureNotify | StructureNotifyMask + | PointerMotionMask | LeaveWindowMask; + + _fib_win = XCreateWindow ( + dpy, DefaultRootWindow (dpy), + x, y, _fib_width, _fib_height, + 1, CopyFromParent, InputOutput, CopyFromParent, + CWEventMask | CWBorderPixel, &attr); + + if (!_fib_win) { return 1; } + + if (parent) + XSetTransientForHint (dpy, _fib_win, parent); + + XStoreName (dpy, _fib_win, "Select File"); + + Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True); + XSetWMProtocols (dpy, _fib_win, &wmDelete, 1); + + _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL); + XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); + const char dl[1] = {1}; + XSetDashes (dpy, _fib_gc, 0, dl, 1); + + int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler); + +#define _XTESTFONT(FN) \ + { \ + font_err = 0; \ + _fibfont = XLoadFont (dpy, FN); \ + XSetFont (dpy, _fib_gc, _fibfont); \ + XSync (dpy, False); \ + } + + font_err = 1; + if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT")); + if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font); + if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"); + if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*"); + if (font_err) _fibfont = None; + XSync (dpy, False); + XSetErrorHandler (handler); + + if (_fib_font_height == 0) { // 1st time only + query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL); + query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL); + if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) { + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + return -1; + } + _fib_font_height += 3; + _fib_font_ascent += 2; + _fib_font_vsep = _fib_font_height + 2; + } + + populate_places (dpy); + + strcpy (_btn_ok.text, "Open"); + strcpy (_btn_cancel.text, "Cancel"); + strcpy (_btn_filter.text, "List All Files"); + strcpy (_btn_places.text, "Show Places"); + strcpy (_btn_hidden.text, "Show Hidden"); + + _btn_ok.callback = &cb_open; + _btn_cancel.callback = &cb_cancel; + _btn_filter.callback = &cb_filter; + _btn_places.callback = &cb_places; + _btn_hidden.callback = &cb_hidden; + _btn_filter.flags |= 4; + _btn_places.flags |= 4; + _btn_hidden.flags |= 4; + + if (!_fib_filter_function) { + _btn_filter.flags |= 8; + } + + size_t i; + int btncnt = 0; + _btn_w = 0; + _btn_span = 0; + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL); + if (_btns[i]->flags & 4) { + _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP; + } else { + ++btncnt; + if (_btns[i]->tw > _btn_w) + _btn_w = _btns[i]->tw; + } + } + + _btn_w += BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP; + _btn_span += _btn_w * btncnt + DSEP * (i - 1) + FAREAMRGR + FAREAMRGB; + + for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { + if (_btns[i]->flags & 8) { continue; } + if (_btns[i]->flags & 4) { + _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP; + } else { + _btns[i]->xw = _btn_w; + } + } + + sync_button_states () ; + + _fib_height = _fib_font_vsep * (15.8); + _fib_width = MAX (_btn_span, 440); + + XResizeWindow (dpy, _fib_win, _fib_width, _fib_height); + + XTextProperty x_wname, x_iname; + XSizeHints hints; + XWMHints wmhints; + + hints.flags = PSize | PMinSize; + hints.min_width = _btn_span; + hints.min_height = 8 * _fib_font_vsep; + + char *w_name = & _fib_cfg_title[0]; + + wmhints.input = True; + wmhints.flags = InputHint; + if (XStringListToTextProperty (&w_name, 1, &x_wname) && + XStringListToTextProperty (&w_name, 1, &x_iname)) + { + XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL); + XFree (x_wname.value); + XFree (x_iname.value); + } + + XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel); + + _fib_mapped = 0; + XMapRaised (dpy, _fib_win); + + if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) { + fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL); + } + +#if 0 + XGrabPointer (dpy, _fib_win, True, + ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask, + GrabModeAsync, GrabModeAsync, None, None, CurrentTime); + XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); + //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime); +#endif + _recentlock = 1; + return 0; +} + +void x_fib_close (Display *dpy) { + if (!_fib_win) return; + XFreeGC (dpy, _fib_gc); + XDestroyWindow (dpy, _fib_win); + _fib_win = 0; + free (_dirlist); + _dirlist = NULL; + free (_pathbtn); + _pathbtn = NULL; + if (_fibfont != None) XUnloadFont (dpy, _fibfont); + _fibfont = None; + free (_placelist); + _placelist = NULL; + _dircount = 0; + _pathparts = 0; + _placecnt = 0; + if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer); + _pixbuffer = None; + Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); + XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0); + XFreeColors (dpy, colormap, &_c_gray6.pixel, 1, 0); + _recentlock = 0; +} + +int x_fib_handle_events (Display *dpy, XEvent *event) { + if (!_fib_win) return 0; + if (_status) return 0; + if (event->xany.window != _fib_win) { + return 0; + } + + switch (event->type) { + case MapNotify: + _fib_mapped = 1; + break; + case UnmapNotify: + _fib_mapped = 0; + break; + case LeaveNotify: + fib_update_hover (dpy, 1, 0, 0); + break; + case ClientMessage: + if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) { + _status = -1; + } + case ConfigureNotify: + if ( + (event->xconfigure.width > 1 && event->xconfigure.height > 1) + && + (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height) + ) + { + _fib_width = event->xconfigure.width; + _fib_height = event->xconfigure.height; + _fib_resized = 1; + } + break; + case Expose: + if (event->xexpose.count == 0) { + fib_expose (dpy, event->xany.window); + } + break; + case MotionNotify: + fib_motion (dpy, event->xmotion.x, event->xmotion.y); + if (event->xmotion.is_hint == NotifyHint) { + XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL); + } + break; + case ButtonPress: + fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case ButtonRelease: + fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); + break; + case KeyRelease: + break; + case KeyPress: + { + KeySym key; + char buf[100]; + static XComposeStatus stat; + XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat); + switch (key) { + case XK_Escape: + _status = -1; + break; + case XK_Up: + if (_fsel > 0) { + fib_select (dpy, _fsel - 1); + } + break; + case XK_Down: + if (_fsel < _dircount -1) { + fib_select ( dpy, _fsel + 1); + } + break; + case XK_Page_Up: + if (_fsel > 0) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MAX (0, _fsel - llen); + fib_select ( dpy, fs); + } + break; + case XK_Page_Down: + if (_fsel < _dircount) { + int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; + if (llen < 1) llen = 1; else --llen; + int fs = MIN (_dircount - 1, _fsel + llen); + fib_select ( dpy, fs); + } + break; + case XK_Left: + if (_pathparts > 1) { + int i = 0; + char path[1024] = "/"; + while (++i < _pathparts - 1) { + strcat (path, _pathbtn[i].name); + strcat (path, "/"); + } + char *sel = strdup (_pathbtn[_pathparts-1].name); + fib_opendir (dpy, path, sel); + free (sel); + } + break; + case XK_Right: + if (_fsel >= 0 && _fsel < _dircount) { + if (_dirlist[_fsel].flags & 4) { + cb_open (dpy); + } + } + break; + case XK_Return: + cb_open (dpy); + break; + default: + if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) { + int i; + for (i = 0; i < _dircount; ++i) { + int j = (_fsel + i + 1) % _dircount; + char kcmp = _dirlist[j].name[0]; + if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20; + if (kcmp == (char)key) { + fib_select ( dpy, j); + break; + } + } + } + break; + } + } + break; + } + + if (_status) { + x_fib_close (dpy); + } + return _status; +} + +int x_fib_status () { + return _status; +} + +int x_fib_configure (int k, const char *v) { + if (_fib_win) { return -1; } + switch (k) { + case 0: + if (strlen (v) >= sizeof(_cur_path) -1) return -2; + if (strlen (v) < 1) return -2; + if (v[0] != '/') return -2; + if (strstr (v, "//")) return -2; + strncpy (_cur_path, v, sizeof(_cur_path)); + break; + case 1: + if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2; + strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title)); + break; + case 2: + if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2; + strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font)); + break; + case 3: + if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2; + strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places)); + break; + default: + return -2; + } + return 0; +} + +int x_fib_cfg_buttons (int k, int v) { + if (_fib_win) { return -1; } + switch (k) { + case 1: + if (v < 0) { + _btn_hidden.flags |= 8; + } else { + _btn_hidden.flags &= ~8; + } + if (v == 1) { + _btn_hidden.flags |= 2; + _fib_hidden_fn = 1; + } else if (v == 0) { + _btn_hidden.flags &= 2; + _fib_hidden_fn = 0; + } + break; + case 2: + if (v < 0) { + _btn_places.flags |= 8; + } else { + _btn_places.flags &= ~8; + } + if (v == 1) { + _btn_places.flags |= 2; + _fib_show_places = 1; + } else if (v == 0) { + _btn_places.flags &= ~2; + _fib_show_places = 0; + } + break; + case 3: + // NB. filter button is automatically hidden + // IFF the filter-function is NULL. + if (v < 0) { + _btn_filter.flags |= 8; + } else { + _btn_filter.flags &= ~8; + } + if (v == 1) { + _btn_filter.flags &= ~2; // inverse - 'show all' = !filter + _fib_filter_fn = 1; + } else if (v == 0) { + _btn_filter.flags |= 2; + _fib_filter_fn = 0; + } + default: + return -2; + } + return 0; +} + +int x_fib_cfg_filter_callback (int (*cb)(const char*)) { + if (_fib_win) { return -1; } + _fib_filter_function = cb; + return 0; +} + +char *x_fib_filename () { + if (_status > 0 && !_fib_win) + return strdup (_rv_open); + else + return NULL; +} +#endif // SOFD_HAVE_X11 + +#if defined(__clang__) +# pragma clang diagnostic pop +#elif defined(__GNUC__) && (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) +# pragma GCC diagnostic pop +#endif + +/* example usage */ +#ifdef SOFD_TEST + +static int fib_filter_movie_filename (const char *name) { + if (!_fib_filter_fn) return 1; + const int l3 = strlen (name) - 3; + const int l4 = l3 - 1; + const int l5 = l4 - 1; + const int l6 = l5 - 1; + const int l9 = l6 - 3; + if ( + (l4 > 0 && ( + !strcasecmp (&name[l4], ".avi") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".ogg") + || !strcasecmp (&name[l4], ".ogv") + || !strcasecmp (&name[l4], ".mpg") + || !strcasecmp (&name[l4], ".mov") + || !strcasecmp (&name[l4], ".mp4") + || !strcasecmp (&name[l4], ".mkv") + || !strcasecmp (&name[l4], ".vob") + || !strcasecmp (&name[l4], ".asf") + || !strcasecmp (&name[l4], ".avs") + || !strcasecmp (&name[l4], ".dts") + || !strcasecmp (&name[l4], ".flv") + || !strcasecmp (&name[l4], ".m4v") + )) || + (l5 > 0 && ( + !strcasecmp (&name[l5], ".h264") + || !strcasecmp (&name[l5], ".webm") + )) || + (l6 > 0 && ( + !strcasecmp (&name[l6], ".dirac") + )) || + (l9 > 0 && ( + !strcasecmp (&name[l9], ".matroska") + )) || + (l3 > 0 && ( + !strcasecmp (&name[l3], ".dv") + || !strcasecmp (&name[l3], ".ts") + )) + ) + { + return 1; + } + return 0; +} + +int main (int argc, char **argv) { + Display* dpy = XOpenDisplay (0); + if (!dpy) return -1; + + x_fib_cfg_filter_callback (fib_filter_movie_filename); + x_fib_configure (1, "Open Movie File"); + x_fib_load_recent ("/tmp/sofd.recent"); + x_fib_show (dpy, 0, 300, 300); + + while (1) { + XEvent event; + while (XPending (dpy) > 0) { + XNextEvent (dpy, &event); + if (x_fib_handle_events (dpy, &event)) { + if (x_fib_status () > 0) { + char *fn = x_fib_filename (); + printf ("OPEN '%s'\n", fn); + x_fib_add_recent (fn, time (NULL)); + free (fn); + } + } + } + if (x_fib_status ()) { + break; + } + usleep (80000); + } + x_fib_close (dpy); + + x_fib_save_recent ("/tmp/sofd.recent"); + + x_fib_free_recent (); + XCloseDisplay (dpy); + return 0; +} +#endif diff --git a/libs/dgl/src/sofd/libsofd.h b/libs/dgl/src/sofd/libsofd.h new file mode 100644 index 0000000..412db85 --- /dev/null +++ b/libs/dgl/src/sofd/libsofd.h @@ -0,0 +1,175 @@ +/* libSOFD - Simple Open File Dialog [for X11 without toolkit] + * + * Copyright (C) 2014 Robin Gareus <robin@gareus.org> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#ifdef SOFD_HAVE_X11 +#include <X11/Xlib.h> + +/////////////////////////////////////////////////////////////////////////////// +/* public API */ + +typedef struct FibInternalsImpl FibInternals; + +/** open a file select dialog + * @param dpy X Display connection + * @param parent (optional) if not NULL, become transient for given window + * @param x if >0 set explict initial width of the window + * @param y if >0 set explict initial height of the window + * @return 0 on success + */ +int x_fib_show (Display *dpy, Window parent, int x, int y); + +/** force close the dialog. + * This is normally not needed, the dialog closes itself + * when a file is selected or the user cancels selection. + * @param dpy X Display connection + */ +void x_fib_close (Display *dpy); + +/** non-blocking X11 event handler. + * It is safe to run this function even if the dialog is + * closed or was not initialized. + * + * @param dpy X Display connection + * @param event the XEvent to process + * @return status + * 0: the event was not for this window, or file-dialog still + * active, or the dialog window is not displayed. + * >0: file was selected, dialog closed + * <0: file selection was cancelled. + */ +int x_fib_handle_events (Display *dpy, XEvent *event); + +/** last status of the dialog + * @return >0: file was selected, <0: canceled or inactive. 0: active + */ +int x_fib_status (); + +/** query the selected filename + * @return NULL if none set, or allocated string to be free()ed by the called + */ +char *x_fib_filename (); + +/** customize/configure the dialog before calling \ref x_fib_show + * changes only have any effect if the dialog is not visible. + * @param k key to change + * 0: set current dir to display (must end with slash) + * 1: set title of dialog window + * 2: specify a custom X11 font to use + * 3: specify a custom 'places' file to include + * (following gtk-bookmark convention) + * @param v value + * @return 0 on success. + */ +int x_fib_configure (int k, const char *v); + +/** customize/configure the dialog before calling \ref x_fib_show + * changes only have any effect if the dialog is not visible. + * + * @param k button to change: + * 1: show hidden files + * 2: show places + * 3: show filter/list all (automatically hidden if there is no + * filter function) + * @param v <0 to hide the button >=0 show button, + * 0: set button-state to not-checked + * 1: set button-state to checked + * >1: retain current state + * @return 0 on success. + */ +int x_fib_cfg_buttons (int k, int v); + +/** set custom callback to filter file-names. + * NULL will disable the filter and hide the 'show all' button. + * changes only have any effect if the dialog is not visible. + * + * @param cb callback function to check file + * the callback function is called with the file name (basename only) + * and is expected to return 1 if the file passes the filter + * and 0 if the file should not be listed by default. + * @return 0 on success. + */ +int x_fib_cfg_filter_callback (int (*cb)(const char*)); + +#endif /* END X11 specific functions */ + +/* 'recently used' API. x-platform + * NOTE: all functions use a static cache and are not reentrant. + * It is expected that none of these functions are called in + * parallel from different threads. + */ + +/** release static resources of 'recently used files' + */ +void x_fib_free_recent (); + +/** add an entry to the recently used list + * + * The dialog does not add files automatically on open, + * if the application succeeds to open a selected file, + * this function should be called. + * + * @param path complete path to file + * @param atime time of last use, 0: NOW + * @return -1 on error, number of current entries otherwise + */ +int x_fib_add_recent (const char *path, time_t atime); + +/** get a platform specific path to a good location for + * saving the recently used file list. + * (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec) + * + * @param application-name to use to include in file + * @return pointer to static path or NULL + */ +const char *x_fib_recent_file(const char *appname); + +/** save the current list of recently used files to the given filename + * (the format is one file per line, filename URL encoded and space separated + * with last-used timestamp) + * + * This function tries to creates the containing directory if it does + * not exist. + * + * @param fn file to save the list to + * @return 0: on success + */ +int x_fib_save_recent (const char *fn); + +/** load a recently used file list. + * + * @param fn file to load the list from + * @return 0: on success + */ +int x_fib_load_recent (const char *fn); + +/** get number of entries in the current list + * @return number of entries in the recently used list + */ +unsigned int x_fib_recent_count (); + +/** get recently used entry at given position + * + * @param i entry to query + * @return pointer to static string + */ +const char *x_fib_recent_at (unsigned int i); diff --git a/libs/distrho/DistrhoUI.hpp b/libs/distrho/DistrhoUI.hpp index b6b2402..2832db0 100644 --- a/libs/distrho/DistrhoUI.hpp +++ b/libs/distrho/DistrhoUI.hpp @@ -20,13 +20,10 @@ #include "extra/d_leakdetector.hpp" #include "src/DistrhoPluginChecks.h" -#if DISTRHO_UI_USE_NTK -# include "../dgl/ntk/NtkWidget.hpp" -typedef DGL::NtkWidget UIWidget; -#elif DISTRHO_UI_USE_NANOVG +#if DISTRHO_UI_USE_NANOVG # include "../dgl/NanoVG.hpp" typedef DGL::NanoWidget UIWidget; -# else +#else # include "../dgl/Widget.hpp" typedef DGL::Widget UIWidget; #endif @@ -143,32 +140,28 @@ protected: */ virtual void d_uiIdle() {} -#if ! DISTRHO_UI_USE_NTK + /** + File browser selected function. + @see Window::fileBrowserSelected(const char*) + */ + virtual void d_uiFileBrowserSelected(const char* filename); + /** OpenGL window reshape function, called when parent window is resized. You can reimplement this function for a custom OpenGL state. @see Window::onReshape(uint,uint) */ virtual void d_uiReshape(uint width, uint height); -#endif /* -------------------------------------------------------------------------------------------------------- * UI Resize Handling, internal */ -#if DISTRHO_UI_USE_NTK - /** - NTK widget resize function, called when the widget is resized. - This is overriden here so the host knows when the UI is resized by you. - */ - void resize(int x, int y, int w, int h) override; -#else /** OpenGL widget resize function, called when the widget is resized. This is overriden here so the host knows when the UI is resized by you. @see Widget::onResize(const ResizeEvent&) */ void onResize(const ResizeEvent& ev) override; -#endif // ------------------------------------------------------------------------------------------------------- @@ -179,14 +172,11 @@ private: friend class UIExporterWindow; // these should not be used - void position(int, int) noexcept {} void setAbsoluteX(int) const noexcept {} void setAbsoluteY(int) const noexcept {} void setAbsolutePos(int, int) const noexcept {} - void setNeedsFullViewport(bool) const noexcept {} -#if ! DISTRHO_UI_USE_NTK void setAbsolutePos(const DGL::Point<int>&) const noexcept {} -#endif + void setNeedsFullViewport(bool) const noexcept {} DISTRHO_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(UI) }; diff --git a/libs/distrho/DistrhoUIMain.cpp b/libs/distrho/DistrhoUIMain.cpp index 3bc0971..cb1cb6e 100644 --- a/libs/distrho/DistrhoUIMain.cpp +++ b/libs/distrho/DistrhoUIMain.cpp @@ -27,28 +27,3 @@ #elif defined(DISTRHO_PLUGIN_TARGET_VST) // nothing #endif - -#ifdef DGL_NTK_APP_HPP_INCLUDED - -START_NAMESPACE_DGL - -void NtkApp::NextUI::run() -{ - if (create) - { - d_stdout("Creating NTK UI in separate thread..."); - d_UI* const ui2 = (func)(); - ui = ui2; - } - else - { - d_stdout("Destroying NTK UI in separate thread..."); - d_UI* const ui2 = ui; - ui = nullptr; - delete ui2; - } -} - -END_NAMESPACE_DGL - -#endif diff --git a/libs/distrho/DistrhoUtils.hpp b/libs/distrho/DistrhoUtils.hpp index 2dd4195..b1391ef 100644 --- a/libs/distrho/DistrhoUtils.hpp +++ b/libs/distrho/DistrhoUtils.hpp @@ -24,6 +24,9 @@ #include <cstdlib> #include <cstring> +#include <cmath> +#include <limits> + #ifdef DISTRHO_PROPER_CPP11_SUPPORT # include <cstdint> #else @@ -32,17 +35,13 @@ #if defined(DISTRHO_OS_MAC) && ! defined(CARLA_OS_MAC) namespace std { -inline float - fmin(float __x, float __y) +inline float fmin(float __x, float __y) { return __builtin_fminf(__x, __y); } -inline float - fmax(float __x, float __y) +inline float fmax(float __x, float __y) { return __builtin_fmaxf(__x, __y); } -inline float - rint(float __x) +inline float rint(float __x) { return __builtin_rintf(__x); } -inline float - round(float __x) +inline float round(float __x) { return __builtin_roundf(__x); } } #endif @@ -50,18 +49,28 @@ inline float // ----------------------------------------------------------------------- // misc functions +/* + * Return a 64-bit number from 4 8-bit numbers. + */ static inline -int64_t d_cconst(const int a, const int b, const int c, const int d) noexcept +int64_t d_cconst(const uint8_t a, const uint8_t b, const uint8_t c, const uint8_t d) noexcept { return (a << 24) | (b << 16) | (c << 8) | (d << 0); } +/* + * Dummy function. + */ static inline void d_pass() noexcept {} // ----------------------------------------------------------------------- // string print functions +/* + * Print a string to stdout with newline (gray color). + * Does nothing if DEBUG is not defined. + */ #ifndef DEBUG # define d_debug(...) #else @@ -79,6 +88,9 @@ void d_debug(const char* const fmt, ...) noexcept } #endif +/* + * Print a string to stdout with newline. + */ static inline void d_stdout(const char* const fmt, ...) noexcept { @@ -91,6 +103,9 @@ void d_stdout(const char* const fmt, ...) noexcept } catch (...) {} } +/* + * Print a string to stderr with newline. + */ static inline void d_stderr(const char* const fmt, ...) noexcept { @@ -103,6 +118,9 @@ void d_stderr(const char* const fmt, ...) noexcept } catch (...) {} } +/* + * Print a string to stderr with newline (red color). + */ static inline void d_stderr2(const char* const fmt, ...) noexcept { @@ -116,12 +134,18 @@ void d_stderr2(const char* const fmt, ...) noexcept } catch (...) {} } +/* + * Print a safe assertion error message. + */ static inline void d_safe_assert(const char* const assertion, const char* const file, const int line) noexcept { d_stderr2("assertion failure: \"%s\" in file %s, line %i", assertion, file, line); } +/* + * Print a safe exception error message. + */ static inline void d_safe_exception(const char* const exception, const char* const file, const int line) noexcept { @@ -129,5 +153,50 @@ void d_safe_exception(const char* const exception, const char* const file, const } // ----------------------------------------------------------------------- +// math functions + +/* + * Safely compare two floating point numbers. + * Returns true if they match. + */ +template<typename T> +static inline +bool d_isEqual(const T& v1, const T& v2) +{ + return std::abs(v1-v2) < std::numeric_limits<T>::epsilon(); +} + +/* + * Safely compare two floating point numbers. + * Returns true if they don't match. + */ +template<typename T> +static inline +bool d_isNotEqual(const T& v1, const T& v2) +{ + return std::abs(v1-v2) >= std::numeric_limits<T>::epsilon(); +} + +/* + * Safely check if a floating point number is zero. + */ +template<typename T> +static inline +bool d_isZero(const T& value) +{ + return std::abs(value) < std::numeric_limits<T>::epsilon(); +} + +/* + * Safely check if a floating point number is not zero. + */ +template<typename T> +static inline +bool d_isNotZero(const T& value) +{ + return std::abs(value) >= std::numeric_limits<T>::epsilon(); +} + +// ----------------------------------------------------------------------- #endif // DISTRHO_UTILS_HPP_INCLUDED diff --git a/libs/distrho/src/DistrhoDefines.h b/libs/distrho/src/DistrhoDefines.h index 501fc28..59e4c34 100644 --- a/libs/distrho/src/DistrhoDefines.h +++ b/libs/distrho/src/DistrhoDefines.h @@ -118,23 +118,17 @@ private: \ #endif /* Define namespace */ -#ifndef DISTRHO_NO_NAMESPACE -# ifndef DISTRHO_NAMESPACE -# define DISTRHO_NAMESPACE DISTRHO -# endif -# define START_NAMESPACE_DISTRHO namespace DISTRHO_NAMESPACE { -# define END_NAMESPACE_DISTRHO } -# define USE_NAMESPACE_DISTRHO using namespace DISTRHO_NAMESPACE; -#else -# define START_NAMESPACE_DISTRHO -# define END_NAMESPACE_DISTRHO -# define USE_NAMESPACE_DISTRHO +#ifndef DISTRHO_NAMESPACE +# define DISTRHO_NAMESPACE DISTRHO #endif +#define START_NAMESPACE_DISTRHO namespace DISTRHO_NAMESPACE { +#define END_NAMESPACE_DISTRHO } +#define USE_NAMESPACE_DISTRHO using namespace DISTRHO_NAMESPACE; /* Useful typedefs */ typedef unsigned char uchar; -typedef unsigned long int ulong; typedef unsigned short int ushort; typedef unsigned int uint; +typedef unsigned long int ulong; #endif // DISTRHO_DEFINES_H_INCLUDED diff --git a/libs/distrho/src/DistrhoPluginCarla.cpp b/libs/distrho/src/DistrhoPluginCarla.cpp index 3ba0b11..1f0a75e 100644 --- a/libs/distrho/src/DistrhoPluginCarla.cpp +++ b/libs/distrho/src/DistrhoPluginCarla.cpp @@ -45,22 +45,27 @@ public: fPlugin(plugin), fUI(this, 0, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, plugin->getInstancePointer()) { - fUI.setTitle(host->uiName); + fUI.setWindowTitle(host->uiName); if (host->uiParentId != 0) - fUI.setTransientWinId(host->uiParentId); + fUI.setWindowTransientWinId(host->uiParentId); + } + + ~UICarla() + { + fUI.quit(); } // --------------------------------------------- void carla_show(const bool yesNo) { - fUI.setVisible(yesNo); + fUI.setWindowVisible(yesNo); } - void carla_idle() + bool carla_idle() { - fUI.idle(); + return fUI.idle(); } void carla_setParameterValue(const uint32_t index, const float value) @@ -84,7 +89,7 @@ public: void carla_setUiTitle(const char* const uiTitle) { - fUI.setTitle(uiTitle); + fUI.setWindowTitle(uiTitle); } // --------------------------------------------- @@ -112,7 +117,7 @@ protected: void handleSetSize(const uint width, const uint height) { - fUI.setSize(width, height); + fUI.setWindowSize(width, height); } // --------------------------------------------- @@ -205,25 +210,23 @@ protected: static NativeParameter param; - // reset - param.hints = ::PARAMETER_IS_ENABLED; param.scalePointCount = 0; param.scalePoints = nullptr; { - int nativeParamHints = ::PARAMETER_IS_ENABLED; + int nativeParamHints = ::NATIVE_PARAMETER_IS_ENABLED; const uint32_t paramHints = fPlugin.getParameterHints(index); if (paramHints & kParameterIsAutomable) - nativeParamHints |= ::PARAMETER_IS_AUTOMABLE; + nativeParamHints |= ::NATIVE_PARAMETER_IS_AUTOMABLE; if (paramHints & kParameterIsBoolean) - nativeParamHints |= ::PARAMETER_IS_BOOLEAN; + nativeParamHints |= ::NATIVE_PARAMETER_IS_BOOLEAN; if (paramHints & kParameterIsInteger) - nativeParamHints |= ::PARAMETER_IS_INTEGER; + nativeParamHints |= ::NATIVE_PARAMETER_IS_INTEGER; if (paramHints & kParameterIsLogarithmic) - nativeParamHints |= ::PARAMETER_IS_LOGARITHMIC; + nativeParamHints |= ::NATIVE_PARAMETER_IS_LOGARITHMIC; if (paramHints & kParameterIsOutput) - nativeParamHints |= ::PARAMETER_IS_OUTPUT; + nativeParamHints |= ::NATIVE_PARAMETER_IS_OUTPUT; param.hints = static_cast<NativeParameterHints>(nativeParamHints); } @@ -329,7 +332,13 @@ protected: realMidiEvent.frame = midiEvent.time; realMidiEvent.size = midiEvent.size; - carla_copy<uint8_t>(realMidiEvent.buf, midiEvent.data, midiEvent.size); + uint8_t j=0; + for (; j<midiEvent.size; ++j) + realMidiEvent.data[j] = midiEvent.data[j]; + for (; j<midiEvent.size; ++j) + realMidiEvent.data[j] = midiEvent.data[j]; + + realMidiEvent.dataExt = nullptr; } fPlugin.run(const_cast<const float**>(inBuffer), outBuffer, frames, realMidiEvents, midiEventCount); @@ -348,17 +357,30 @@ protected: void uiShow(const bool show) override { if (show) + { createUiIfNeeded(); + CARLA_SAFE_ASSERT_RETURN(fUiPtr != nullptr,); - if (fUiPtr != nullptr) fUiPtr->carla_show(show); + } + else if (fUiPtr != nullptr) + { + delete fUiPtr; + fUiPtr = nullptr; + } } void uiIdle() override { CARLA_SAFE_ASSERT_RETURN(fUiPtr != nullptr,); - fUiPtr->carla_idle(); + if (! fUiPtr->carla_idle()) + { + uiClosed(); + + delete fUiPtr; + fUiPtr = nullptr; + } } void uiSetParameterValue(const uint32_t index, const float value) override diff --git a/libs/distrho/src/DistrhoPluginChecks.h b/libs/distrho/src/DistrhoPluginChecks.h index f556c06..16d6b43 100644 --- a/libs/distrho/src/DistrhoPluginChecks.h +++ b/libs/distrho/src/DistrhoPluginChecks.h @@ -93,10 +93,6 @@ # define DISTRHO_UI_USE_NANOVG 0 #endif -#ifndef DISTRHO_UI_USE_NTK -# define DISTRHO_UI_USE_NTK 0 -#endif - // ----------------------------------------------------------------------- // Define DISTRHO_UI_URI if needed diff --git a/libs/distrho/src/DistrhoPluginInternal.hpp b/libs/distrho/src/DistrhoPluginInternal.hpp index 7b4c04b..e9e2e23 100644 --- a/libs/distrho/src/DistrhoPluginInternal.hpp +++ b/libs/distrho/src/DistrhoPluginInternal.hpp @@ -83,7 +83,7 @@ struct Plugin::PrivateData { sampleRate(d_lastSampleRate) { DISTRHO_SAFE_ASSERT(bufferSize != 0); - DISTRHO_SAFE_ASSERT(sampleRate != 0.0); + DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); } ~PrivateData() noexcept @@ -433,7 +433,7 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fPlugin != nullptr,); DISTRHO_SAFE_ASSERT(sampleRate > 0.0); - if (fData->sampleRate == sampleRate) + if (d_isEqual(fData->sampleRate, sampleRate)) return; fData->sampleRate = sampleRate; diff --git a/libs/distrho/src/DistrhoPluginVST.cpp b/libs/distrho/src/DistrhoPluginVST.cpp index 703ad79..57ea038 100644 --- a/libs/distrho/src/DistrhoPluginVST.cpp +++ b/libs/distrho/src/DistrhoPluginVST.cpp @@ -270,7 +270,7 @@ public: : fAudioMaster(audioMaster), fEffect(effect) { - std::memset(fProgramName, 0, sizeof(char)*(64+1)); + std::memset(fProgramName, 0, sizeof(char)*(32+1)); std::strcpy(fProgramName, "Default"); #if DISTRHO_PLUGIN_HAS_MIDI_INPUT @@ -339,7 +339,7 @@ public: case effSetProgramName: if (char* const programName = (char*)ptr) { - DISTRHO::strncpy(fProgramName, programName, 64); + DISTRHO::strncpy(fProgramName, programName, 32); return 1; } break; @@ -371,8 +371,10 @@ public: case effSetSampleRate: fPlugin.setSampleRate(opt, true); +#if DISTRHO_PLUGIN_HAS_UI if (fVstUI != nullptr) fVstUI->setSampleRate(opt); +#endif break; case effSetBlockSize: @@ -424,7 +426,6 @@ public: # endif d_lastUiSampleRate = fPlugin.getSampleRate(); - d_stdout("effEditOpen with ptr = %p", ptr); fVstUI = new UIVst(fAudioMaster, fEffect, this, &fPlugin, (intptr_t)ptr); # if DISTRHO_PLUGIN_WANT_STATE @@ -584,7 +585,7 @@ public: case effCanDo: if (const char* const canDo = (const char*)ptr) { -# if DISTRHO_OS_MAC +# if DISTRHO_OS_MAC && DISTRHO_PLUGIN_HAS_UI if (std::strcmp(canDo, "hasCockosViewAsConfig") == 0) { fUsingNsView = true; @@ -716,7 +717,7 @@ private: PluginExporter fPlugin; // Temporary data - char fProgramName[64+1]; + char fProgramName[32+1]; #if DISTRHO_PLUGIN_HAS_MIDI_INPUT uint32_t fMidiEventCount; @@ -903,25 +904,25 @@ static intptr_t vst_dispatcherCallback(AEffect* effect, int32_t opcode, int32_t #endif case effGetEffectName: - if (ptr != nullptr) + if (char* const cptr = (char*)ptr) { - DISTRHO::strncpy((char*)ptr, plugin.getName(), 64); + DISTRHO::strncpy(cptr, plugin.getName(), 32); return 1; } return 0; case effGetVendorString: - if (ptr != nullptr) + if (char* const cptr = (char*)ptr) { - DISTRHO::strncpy((char*)ptr, plugin.getMaker(), 64); + DISTRHO::strncpy(cptr, plugin.getMaker(), 32); return 1; } return 0; case effGetProductString: - if (ptr != nullptr) + if (char* const cptr = (char*)ptr) { - DISTRHO::strncpy((char*)ptr, plugin.getLabel(), 64); + DISTRHO::strncpy(cptr, plugin.getLabel(), 32); return 1; } return 0; diff --git a/libs/distrho/src/DistrhoUI.cpp b/libs/distrho/src/DistrhoUI.cpp index eda57b0..49e8c3e 100644 --- a/libs/distrho/src/DistrhoUI.cpp +++ b/libs/distrho/src/DistrhoUI.cpp @@ -21,9 +21,9 @@ START_NAMESPACE_DISTRHO /* ------------------------------------------------------------------------------------------------------------ * Static data, see DistrhoUIInternal.hpp */ -double d_lastUiSampleRate = 0.0; -void* d_lastUiDspPtr = nullptr; -UIWindow* d_lastUiWindow = nullptr; +double d_lastUiSampleRate = 0.0; +void* d_lastUiDspPtr = nullptr; +Window* d_lastUiWindow = nullptr; /* ------------------------------------------------------------------------------------------------------------ * UI */ @@ -90,35 +90,29 @@ void UI::d_sampleRateChanged(double) {} /* ------------------------------------------------------------------------------------------------------------ * UI Callbacks (optional) */ -#if ! DISTRHO_UI_USE_NTK +void UI::d_uiFileBrowserSelected(const char*) +{ +} + void UI::d_uiReshape(uint width, uint height) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glOrtho(0, width, height, 0, 0.0f, 1.0f); - glViewport(0, 0, width, height); + glOrtho(0.0, static_cast<GLdouble>(width), static_cast<GLdouble>(height), 0.0, 0.0, 1.0); + glViewport(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height)); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } -#endif /* ------------------------------------------------------------------------------------------------------------ * UI Resize Handling, internal */ -#if DISTRHO_UI_USE_NTK -void UI::resize(int x, int y, int w, int h) -{ - UIWidget::resize(x, y, w, h); - pData->setSizeCallback(w, h); -} -#else void UI::onResize(const ResizeEvent& ev) { pData->setSizeCallback(ev.size.getWidth(), ev.size.getHeight()); } -#endif // ----------------------------------------------------------------------------------------------------------- diff --git a/libs/distrho/src/DistrhoUIInternal.hpp b/libs/distrho/src/DistrhoUIInternal.hpp index 6bd421c..a07719c 100644 --- a/libs/distrho/src/DistrhoUIInternal.hpp +++ b/libs/distrho/src/DistrhoUIInternal.hpp @@ -18,29 +18,21 @@ #define DISTRHO_UI_INTERNAL_HPP_INCLUDED #include "../DistrhoUI.hpp" +#include "../../dgl/App.hpp" +#include "../../dgl/Window.hpp" -#if DISTRHO_UI_USE_NTK -# include "../../dgl/ntk/NtkApp.hpp" -# include "../../dgl/ntk/NtkWindow.hpp" -typedef DGL::NtkApp App; -typedef DGL::NtkWindow UIWindow; -#else -# include "../../dgl/App.hpp" -# include "../../dgl/Window.hpp" -typedef DGL::App App; -typedef DGL::Window UIWindow; -#endif - +using DGL::App; using DGL::IdleCallback; +using DGL::Window; START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- // Static data, see DistrhoUI.cpp -extern double d_lastUiSampleRate; -extern void* d_lastUiDspPtr; -extern UIWindow* d_lastUiWindow; +extern double d_lastUiSampleRate; +extern void* d_lastUiDspPtr; +extern Window* d_lastUiWindow; // ----------------------------------------------------------------------- // UI callbacks @@ -83,7 +75,7 @@ struct UI::PrivateData { setSizeCallbackFunc(nullptr), ptr(nullptr) { - DISTRHO_SAFE_ASSERT(sampleRate != 0.0); + DISTRHO_SAFE_ASSERT(d_isNotZero(sampleRate)); #if defined(DISTRHO_PLUGIN_TARGET_DSSI) || defined(DISTRHO_PLUGIN_TARGET_LV2) parameterOffset += DISTRHO_PLUGIN_NUM_INPUTS + DISTRHO_PLUGIN_NUM_OUTPUTS; @@ -137,25 +129,21 @@ struct UI::PrivateData { // Plugin Window, needed to take care of resize properly static inline -UI* createUiWrapper(void* const dspPtr, UIWindow* const window) +UI* createUiWrapper(void* const dspPtr, Window* const window) { d_lastUiDspPtr = dspPtr; d_lastUiWindow = window; -#if DISTRHO_UI_USE_NTK - UI* const ret = window->getApp().createUI((void*)createUI); -#else UI* const ret = createUI(); -#endif d_lastUiDspPtr = nullptr; d_lastUiWindow = nullptr; return ret; } -class UIExporterWindow : public UIWindow +class UIExporterWindow : public Window { public: UIExporterWindow(App& app, const intptr_t winId, void* const dspPtr) - : UIWindow(app, winId), + : Window(app, winId), fUI(createUiWrapper(dspPtr, this)), fIsReady(false) { @@ -168,11 +156,7 @@ public: ~UIExporterWindow() { -#if DISTRHO_UI_USE_NTK - getApp().deleteUI(fUI); -#else delete fUI; -#endif } UI* getUI() const noexcept @@ -185,24 +169,23 @@ public: return fIsReady; } -//protected: -#if DISTRHO_UI_USE_NTK - void resize(int x, int y, int width, int height) override - { - UIWindow::resize(x, y, width, height); - fIsReady = true; - } -#else +protected: + // custom window reshape void onReshape(uint width, uint height) override { DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - // custom window reshape fUI->d_uiReshape(width, height); - fIsReady = true; } -#endif + + // custom file-browser selected + void fileBrowserSelected(const char* filename) override + { + DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); + + fUI->d_uiFileBrowserSelected(filename); + } private: UI* const fUI; @@ -254,6 +237,13 @@ public: // ------------------------------------------------------------------- + intptr_t getWindowId() const noexcept + { + return glWindow.getWindowId(); + } + + // ------------------------------------------------------------------- + uint32_t getParameterOffset() const noexcept { DISTRHO_SAFE_ASSERT_RETURN(fData != nullptr, 0); @@ -306,8 +296,6 @@ public: { if (glWindow.isReady()) fUI->d_uiIdle(); - - fChangingSize = false; } bool idle() @@ -319,8 +307,6 @@ public: if (glWindow.isReady()) fUI->d_uiIdle(); - fChangingSize = false; - return ! glApp.isQuiting(); } @@ -335,9 +321,7 @@ public: void setWindowSize(const uint width, const uint height, const bool updateUI = false) { DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); - - if (fChangingSize) - return; + DISTRHO_SAFE_ASSERT_RETURN(! fChangingSize,); fChangingSize = true; @@ -345,6 +329,8 @@ public: fUI->setSize(width, height); glWindow.setSize(width, height); + + fChangingSize = false; } void setWindowTitle(const char* const uiTitle) @@ -352,7 +338,7 @@ public: glWindow.setTitle(uiTitle); } - void setWindowTransientWinId(const intptr_t winId) + void setWindowTransientWinId(const uintptr_t winId) { glWindow.setTransientWinId(winId); } @@ -372,7 +358,7 @@ public: DISTRHO_SAFE_ASSERT_RETURN(fUI != nullptr,); DISTRHO_SAFE_ASSERT(sampleRate > 0.0); - if (fData->sampleRate == sampleRate) + if (d_isEqual(fData->sampleRate, sampleRate)) return; fData->sampleRate = sampleRate; diff --git a/libs/distrho/src/DistrhoUILV2.cpp b/libs/distrho/src/DistrhoUILV2.cpp index 4de5346..2aa6565 100644 --- a/libs/distrho/src/DistrhoUILV2.cpp +++ b/libs/distrho/src/DistrhoUILV2.cpp @@ -37,7 +37,8 @@ class UiLv2 public: UiLv2(const intptr_t winId, const LV2_Options_Option* options, const LV2_URID_Map* const uridMap, const LV2UI_Resize* const uiResz, const LV2UI_Touch* uiTouch, - const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc, void* const dspPtr) + const LV2UI_Controller controller, const LV2UI_Write_Function writeFunc, + LV2UI_Widget* const widget, void* const dspPtr) : fUI(this, winId, editParameterCallback, setParameterCallback, setStateCallback, sendNoteCallback, setSizeCallback, dspPtr), fUridMap(uridMap), fUiResize(uiResz), @@ -51,6 +52,9 @@ public: if (fUiResize != nullptr && winId != 0) fUiResize->ui_resize(fUiResize->handle, fUI.getWidth(), fUI.getHeight()); + if (widget != nullptr) + *widget = (LV2UI_Widget*)fUI.getWindowId(); + #if DISTRHO_PLUGIN_WANT_STATE // tell the DSP we're ready to receive msgs setState("__dpf_ui_data__", ""); @@ -59,7 +63,7 @@ public: if (winId != 0) return; - // if winId != 0 then options must not be null + // if winId == 0 then options must not be null DISTRHO_SAFE_ASSERT_RETURN(options != nullptr,); const LV2_URID uridWindowTitle(uridMap->map(uridMap->handle, LV2_UI__windowTitle)); @@ -106,10 +110,8 @@ public: { const uint32_t parameterOffset(fUI.getParameterOffset()); - if (rindex < parameterOffset) - return; - if (bufferSize != sizeof(float)) - return; + DISTRHO_SAFE_ASSERT_RETURN(rindex >= parameterOffset,) + DISTRHO_SAFE_ASSERT_RETURN(bufferSize == sizeof(float),) const float value(*(const float*)buffer); fUI.parameterChanged(rindex-parameterOffset, value); @@ -391,8 +393,6 @@ static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, } #endif - *widget = parentId; - const intptr_t winId((intptr_t)parentId); if (options != nullptr) @@ -419,7 +419,7 @@ static LV2UI_Handle lv2ui_instantiate(const LV2UI_Descriptor*, const char* uri, d_lastUiSampleRate = 44100.0; } - return new UiLv2(winId, options, uridMap, uiResize, uiTouch, controller, writeFunction, instance); + return new UiLv2(winId, options, uridMap, uiResize, uiTouch, controller, writeFunction, widget, instance); } #define uiPtr ((UiLv2*)ui) diff --git a/plugins/ZamSFZ/Makefile b/plugins/ZamSFZ/Makefile index b6b4d8b..aa1ba72 100644 --- a/plugins/ZamSFZ/Makefile +++ b/plugins/ZamSFZ/Makefile @@ -27,6 +27,12 @@ OBJS_UI = \ include ../Makefile.mk # -------------------------------------------------------------- +# Extra flags + +BASE_FLAGS += $(shell pkg-config --cflags sndfile rubberband) +LINK_FLAGS += $(shell pkg-config --libs sndfile rubberband) + +# -------------------------------------------------------------- # Enable all possible plugin types ifeq ($(LINUX),true) diff --git a/plugins/ZamSFZ/ZamSFZUI.cpp b/plugins/ZamSFZ/ZamSFZUI.cpp index eccdf02..3bb4d69 100644 --- a/plugins/ZamSFZ/ZamSFZUI.cpp +++ b/plugins/ZamSFZ/ZamSFZUI.cpp @@ -18,6 +18,8 @@ #include "ZamSFZUI.hpp" #include "ZamSFZPlugin.hpp" +#include "Window.hpp" + START_NAMESPACE_DISTRHO // ----------------------------------------------------------------------- @@ -82,6 +84,13 @@ void ZamSFZUI::d_stateChanged(const char* key, const char*) } } +void ZamSFZUI::d_uiFileBrowserSelected(const char* filename) +{ + // if a file was selected, tell DSP + if (filename != nullptr) + d_setState("filepath", filename); +} + // ----------------------------------------------------------------------- // Widget Callbacks @@ -105,33 +114,11 @@ void ZamSFZUI::imageKnobValueChanged(ImageKnob* knob, float value) void ZamSFZUI::imageButtonClicked(ImageButton*, int) { -/* - GtkWidget* dialog = gtk_file_chooser_dialog_new( - "Load SFZ", - NULL, - GTK_FILE_CHOOSER_ACTION_OPEN, - GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, - GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT, - NULL); - - if (filepath) { - gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (dialog), filepath); - } + DGL::Window::FileBrowserOptions opts; + opts.title = "Load SFZ"; + //opts.filters = "sfz;"; - if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) { - gtk_widget_destroy(dialog); - return; - } - - if (filepath) { - g_free(filepath); - } - filepath = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog)); - - gtk_widget_destroy(dialog); -*/ -// d_setState("filepath", "/home/damien/Music/rhodes-nord/rhodes-nord.sfz"); - d_setState("filepath", "/home/damien/Music/sfz/sfz LatelyBass/TX_LatelyBass.sfz"); + getParentWindow().openFileBrowser(opts); } void ZamSFZUI::onDisplay() diff --git a/plugins/ZamSFZ/ZamSFZUI.hpp b/plugins/ZamSFZ/ZamSFZUI.hpp index f0d1047..e82f406 100644 --- a/plugins/ZamSFZ/ZamSFZUI.hpp +++ b/plugins/ZamSFZ/ZamSFZUI.hpp @@ -46,6 +46,8 @@ protected: void d_programChanged(uint32_t index) override; void d_stateChanged(const char* key, const char* value) override; + void d_uiFileBrowserSelected(const char* filename) override; + // ------------------------------------------------------------------- // Widget Callbacks @@ -55,6 +57,7 @@ protected: void imageButtonClicked(ImageButton*, int) override; void onDisplay() override; + private: Image fImgBackground; ScopedPointer<ImageKnob> fKnobGain; |