diff --git a/vstgui/lib/algorithm.h b/vstgui/lib/algorithm.h index 5b206ae80..bda8820a3 100644 --- a/vstgui/lib/algorithm.h +++ b/vstgui/lib/algorithm.h @@ -16,7 +16,7 @@ template Optional indexOf (Iter first, Iter last, const Type& value) { auto it = std::find (first, last, value); - if (first == last) + if (it == last) return {}; return {static_cast (std::distance (first, it))}; } diff --git a/vstgui/lib/controls/csegmentbutton.cpp b/vstgui/lib/controls/csegmentbutton.cpp index 4058167ed..22073f242 100644 --- a/vstgui/lib/controls/csegmentbutton.cpp +++ b/vstgui/lib/controls/csegmentbutton.cpp @@ -349,6 +349,9 @@ CMouseEventResult CSegmentButton::onMouseDown (CPoint& where, const CButtonState break; // out of for loop } newValue += valueOffset; + + // Last segment can lead to newValue > 1.0 + newValue = std::min(newValue, 1.f); } } return kMouseDownEventHandledButDontNeedMovedOrUpEvents; @@ -517,7 +520,10 @@ uint32_t CSegmentButton::getSegmentIndex (float value) const { if (value < 0.f || value > 1.f) return kPushBack; - return static_cast (static_cast (segments.size () - 1) * value); + + const auto segmentIndex = static_cast (segments.size () - 1) * value; + const auto segmentIndexRounded = static_cast (segmentIndex + 0.5f); + return segmentIndexRounded; } //----------------------------------------------------------------------------- diff --git a/vstgui/lib/platform/common/stb_textedit.h b/vstgui/lib/platform/common/stb_textedit.h index 91a52da1b..cd38a25ab 100644 --- a/vstgui/lib/platform/common/stb_textedit.h +++ b/vstgui/lib/platform/common/stb_textedit.h @@ -1,4 +1,4 @@ -// stb_textedit.h - v1.12 - public domain - Sean Barrett +// stb_textedit.h - v1.13 - public domain - Sean Barrett // Development of this library was sponsored by RAD Game Tools // // This C header file implements the guts of a multi-line text-editing @@ -13,7 +13,7 @@ // texts, as its performance does not scale and it has limited undo). // // Non-trivial behaviors are modelled after Windows text controls. -// +// // // LICENSE // @@ -29,6 +29,7 @@ // // VERSION HISTORY // +// 1.13 (2019-02-07) fix bug in undo size management // 1.12 (2018-01-29) user can change STB_TEXTEDIT_KEYTYPE, fix redo to avoid crash // 1.11 (2017-03-03) fix HOME on last line, dragging off single-line textfield // 1.10 (2016-10-25) supress warnings about casting away const with -Wcast-qual @@ -86,8 +87,8 @@ // moderate sizes. The undo system does no memory allocations, so // it grows STB_TexteditState by the worst-case storage which is (in bytes): // -// [4 + sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT -// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT +// [4 + 3 * sizeof(STB_TEXTEDIT_POSITIONTYPE)] * STB_TEXTEDIT_UNDOSTATE_COUNT +// + sizeof(STB_TEXTEDIT_CHARTYPE) * STB_TEXTEDIT_UNDOCHAR_COUNT // // // Implementation mode: @@ -110,7 +111,7 @@ // Symbols that must be the same in header-file and implementation mode: // // STB_TEXTEDIT_CHARTYPE the character type -// STB_TEXTEDIT_POSITIONTYPE small type that a valid cursor position +// STB_TEXTEDIT_POSITIONTYPE small type that is a valid cursor position // STB_TEXTEDIT_UNDOSTATECOUNT the number of undo states to allow // STB_TEXTEDIT_UNDOCHARCOUNT the number of characters to store in the undo buffer // @@ -170,7 +171,7 @@ // Keyboard input must be encoded as a single integer value; e.g. a character code // and some bitflags that represent shift states. to simplify the interface, SHIFT must // be a bitflag, so we can test the shifted state of cursor movements to allow selection, -// i.e. (STB_TEXTED_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. +// i.e. (STB_TEXTEDIT_K_RIGHT|STB_TEXTEDIT_K_SHIFT) should be shifted right-arrow. // // You can encode other things, such as CONTROL or ALT, in additional bits, and // then test for their presence in e.g. STB_TEXTEDIT_K_WORDLEFT. For example, @@ -212,20 +213,20 @@ // call this with the mouse x,y on a mouse down; it will update the cursor // and reset the selection start/end to the cursor point. the x,y must // be relative to the text widget, with (0,0) being the top left. -// +// // drag: // call this with the mouse x,y on a mouse drag/up; it will update the // cursor and the selection end point -// +// // cut: // call this to delete the current selection; returns true if there was // one. you should FIRST copy the current selection to the system paste buffer. // (To copy, just copy the current selection out of the string yourself.) -// +// // paste: // call this to paste text at the current cursor point or over the current // selection if there is one. -// +// // key: // call this for keyboard inputs sent to the textfield. you can use it // for "key down" events or for "translated" key events. if you need to @@ -236,7 +237,7 @@ // clear. STB_TEXTEDIT_KEYTYPE defaults to int, but you can #define it to // anything other type you wante before including. // -// +// // When rendering, you can read the cursor position and selection state from // the STB_TexteditState. // @@ -295,9 +296,9 @@ typedef struct { // private data STB_TEXTEDIT_POSITIONTYPE where; - short insert_length; - short delete_length; - short char_storage; + STB_TEXTEDIT_POSITIONTYPE insert_length; + STB_TEXTEDIT_POSITIONTYPE delete_length; + int char_storage; } StbUndoRecord; typedef struct @@ -306,7 +307,7 @@ typedef struct StbUndoRecord undo_rec [STB_TEXTEDIT_UNDOSTATECOUNT]; STB_TEXTEDIT_CHARTYPE undo_char[STB_TEXTEDIT_UNDOCHARCOUNT]; short undo_point, redo_point; - short undo_char_point, redo_char_point; + int undo_char_point, redo_char_point; } StbUndoState; typedef struct @@ -558,7 +559,6 @@ static void stb_textedit_find_charpos(StbFindState *find, STB_TEXTEDIT_STRING *s // now scan to find xpos find->x = r.x0; - i = 0; for (i=0; first+i < n; ++i) find->x += STB_TEXTEDIT_GETWIDTH(str, first, i); } @@ -688,7 +688,7 @@ static void stb_textedit_prep_selection_at_cursor(STB_TexteditState *state) static int stb_textedit_cut(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) { if (STB_TEXT_HAS_SELECTION(state)) { - stb_textedit_delete_selection(str,state); // implicity clamps + stb_textedit_delete_selection(str,state); // implicitly clamps state->has_preferred_x = 0; return 1; } @@ -740,7 +740,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->has_preferred_x = 0; } } else { - stb_textedit_delete_selection(str,state); // implicity clamps + stb_textedit_delete_selection(str,state); // implicitly clamps if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, &ch, 1)) { stb_text_makeundo_insert(state, state->cursor, 1); ++state->cursor; @@ -756,7 +756,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->insert_mode = !state->insert_mode; break; #endif - + case STB_TEXTEDIT_K_UNDO: stb_text_undo(str, state); state->has_preferred_x = 0; @@ -771,7 +771,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, // if currently there's a selection, move cursor to start of selection if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_first(state); - else + else if (state->cursor > 0) --state->cursor; state->has_preferred_x = 0; @@ -820,7 +820,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, #ifdef STB_TEXTEDIT_MOVEWORDRIGHT case STB_TEXTEDIT_K_WORDRIGHT: - if (STB_TEXT_HAS_SELECTION(state)) + if (STB_TEXT_HAS_SELECTION(state)) stb_textedit_move_to_last(str, state); else { state->cursor = STB_TEXTEDIT_MOVEWORDRIGHT(str, state->cursor); @@ -898,7 +898,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } break; } - + case STB_TEXTEDIT_K_UP: case STB_TEXTEDIT_K_UP | STB_TEXTEDIT_K_SHIFT: { StbFindState find; @@ -975,7 +975,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, } state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2: #endif @@ -992,7 +992,7 @@ static void stb_textedit_key(STB_TEXTEDIT_STRING *str, STB_TexteditState *state, state->select_start = state->select_end = 0; state->has_preferred_x = 0; break; - + #ifdef STB_TEXTEDIT_K_TEXTSTART2 case STB_TEXTEDIT_K_TEXTSTART2 | STB_TEXTEDIT_K_SHIFT: #endif @@ -1096,11 +1096,11 @@ static void stb_textedit_discard_undo(StbUndoState *state) if (state->undo_rec[0].char_storage >= 0) { int n = state->undo_rec[0].insert_length, i; // delete n characters from all other records - state->undo_char_point = state->undo_char_point - (short) n; // vsnet05 + state->undo_char_point -= n; STB_TEXTEDIT_memmove(state->undo_char, state->undo_char + n, (size_t) (state->undo_char_point*sizeof(STB_TEXTEDIT_CHARTYPE))); for (i=0; i < state->undo_point; ++i) if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage = state->undo_rec[i].char_storage - (short) n; // vsnet05 // @OPTIMIZE: get rid of char_storage and infer it + state->undo_rec[i].char_storage -= n; // @OPTIMIZE: get rid of char_storage and infer it } --state->undo_point; STB_TEXTEDIT_memmove(state->undo_rec, state->undo_rec+1, (size_t) (state->undo_point*sizeof(state->undo_rec[0]))); @@ -1120,12 +1120,12 @@ static void stb_textedit_discard_redo(StbUndoState *state) if (state->undo_rec[k].char_storage >= 0) { int n = state->undo_rec[k].insert_length, i; // move the remaining redo character data to the end of the buffer - state->redo_char_point = state->redo_char_point + (short) n; // vsnet05 + state->redo_char_point += n; STB_TEXTEDIT_memmove(state->undo_char + state->redo_char_point, state->undo_char + state->redo_char_point-n, (size_t) ((STB_TEXTEDIT_UNDOCHARCOUNT - state->redo_char_point)*sizeof(STB_TEXTEDIT_CHARTYPE))); // adjust the position of all the other records to account for above memmove for (i=state->redo_point; i < k; ++i) if (state->undo_rec[i].char_storage >= 0) - state->undo_rec[i].char_storage += (short) n; // vsnet05 + state->undo_rec[i].char_storage += n; } // now move all the redo records towards the end of the buffer; the first one is at 'redo_point' STB_TEXTEDIT_memmove(state->undo_rec + state->redo_point+1, state->undo_rec + state->redo_point, (size_t) ((STB_TEXTEDIT_UNDOSTATECOUNT - state->redo_point)*sizeof(state->undo_rec[0]))); @@ -1165,15 +1165,15 @@ static STB_TEXTEDIT_CHARTYPE *stb_text_createundo(StbUndoState *state, int pos, return NULL; r->where = pos; - r->insert_length = (short) insert_len; - r->delete_length = (short) delete_len; + r->insert_length = (STB_TEXTEDIT_POSITIONTYPE) insert_len; + r->delete_length = (STB_TEXTEDIT_POSITIONTYPE) delete_len; if (insert_len == 0) { r->char_storage = -1; return NULL; } else { r->char_storage = state->undo_char_point; - state->undo_char_point = state->undo_char_point + (short) insert_len; + state->undo_char_point += insert_len; return &state->undo_char[r->char_storage]; } } @@ -1222,7 +1222,7 @@ static void stb_text_undo(STB_TEXTEDIT_STRING *str, STB_TexteditState *state) r = &s->undo_rec[s->redo_point-1]; r->char_storage = s->redo_char_point - u.delete_length; - s->redo_char_point = s->redo_char_point - (short) u.delete_length; + s->redo_char_point = s->redo_char_point - u.delete_length; // now save the characters for (i=0; i < u.delete_length; ++i) @@ -1367,38 +1367,38 @@ This software is available under 2 licenses -- choose whichever you prefer. ------------------------------------------------------------------------------ ALTERNATIVE A - MIT License Copyright (c) 2017 Sean Barrett -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 +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 +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 +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. ------------------------------------------------------------------------------ ALTERNATIVE B - Public Domain (www.unlicense.org) This is free and unencumbered software released into the public domain. -Anyone is free to copy, modify, publish, use, compile, sell, or distribute this -software, either in source code form or as a compiled binary, for any purpose, +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. -In jurisdictions that recognize copyright laws, the author or authors of this -software dedicate any and all copyright interest in the software to the public -domain. We make this dedication for the benefit of the public at large and to -the detriment of our heirs and successors. We intend this dedication to be an -overt act of relinquishment in perpetuity of all present and future rights to +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. -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 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 +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 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. ------------------------------------------------------------------------------ */ diff --git a/vstgui/lib/platform/linux/cairocontext.cpp b/vstgui/lib/platform/linux/cairocontext.cpp index 69330bdec..0bf8cd828 100644 --- a/vstgui/lib/platform/linux/cairocontext.cpp +++ b/vstgui/lib/platform/linux/cairocontext.cpp @@ -56,10 +56,7 @@ inline bool needPixelAlignment (CDrawMode mode) DrawBlock::DrawBlock (Context& context) : context (context) { auto ct = context.getCurrentTransform (); - CRect clip; - context.getClipRect (clip); - ct.transform (clip); - clip.bound (context.getSurfaceRect ()); + CRect clip = context.getCurrentStateClipRect (); if (clip.isEmpty ()) { clipIsEmpty = true; @@ -124,6 +121,12 @@ void Context::init () super::init (); } +//----------------------------------------------------------------------------- +CRect Context::getCurrentStateClipRect () const +{ + return getCurrentState ().clipRect; +} + //----------------------------------------------------------------------------- void Context::beginDraw () { diff --git a/vstgui/lib/platform/linux/cairocontext.h b/vstgui/lib/platform/linux/cairocontext.h index 24881ebb4..cf78bd259 100644 --- a/vstgui/lib/platform/linux/cairocontext.h +++ b/vstgui/lib/platform/linux/cairocontext.h @@ -58,6 +58,7 @@ class Context : public COffscreenContext void beginDraw () override; void endDraw () override; + CRect getCurrentStateClipRect () const; private: void init () override; void setSourceColor (CColor color); diff --git a/vstgui/lib/platform/linux/cairofont.cpp b/vstgui/lib/platform/linux/cairofont.cpp index 2579134ad..f906e0e22 100644 --- a/vstgui/lib/platform/linux/cairofont.cpp +++ b/vstgui/lib/platform/linux/cairofont.cpp @@ -156,6 +156,12 @@ Font::Font (UTF8StringPtr name, const CCoord& size, const int32_t& style) { impl->ascent = pango_units_to_double (pango_font_metrics_get_ascent (metrics)); impl->descent = pango_units_to_double (pango_font_metrics_get_descent (metrics)); +#if (PANGO_VERSION_MAJOR > 1) || ((PANGO_VERSION_MAJOR == 1) && PANGO_VERSION_MINOR >= 44) + auto height = pango_units_to_double (pango_font_metrics_get_height (metrics)); + impl->leading = height - (impl->ascent + impl->descent); +#else + impl->leading = 0.; +#endif pango_font_metrics_unref (metrics); } @@ -210,7 +216,6 @@ double Font::getDescent () const //------------------------------------------------------------------------ double Font::getLeading () const { -#warning TODO: Implementation return impl->leading; } diff --git a/vstgui/lib/platform/linux/cairopath.cpp b/vstgui/lib/platform/linux/cairopath.cpp index 9597e106c..7e095e892 100644 --- a/vstgui/lib/platform/linux/cairopath.cpp +++ b/vstgui/lib/platform/linux/cairopath.cpp @@ -30,11 +30,21 @@ CGradient* Path::createGradient (double color1Start, double color2Start, //------------------------------------------------------------------------ bool Path::hitTest (const CPoint& p, bool evenOddFilled, CGraphicsTransform* transform) { -#warning TODO: Implementation (use cairo_in_fill) - (void)p; - (void)evenOddFilled; - (void)transform; - return false; + auto result = false; + if (auto cPath = getPath (cr)) + { + auto tp = p; + if (transform) + transform->transform (tp); + cairo_save (cr); + cairo_new_path (cr); + cairo_append_path (cr, cPath); + cairo_set_fill_rule (cr, evenOddFilled ? CAIRO_FILL_RULE_EVEN_ODD : CAIRO_FILL_RULE_WINDING); + cairo_clip (cr); + result = cairo_in_clip (cr, tp.x, tp.y); + cairo_restore (cr); + } + return result; } //------------------------------------------------------------------------ diff --git a/vstgui/lib/platform/linux/x11fileselector.cpp b/vstgui/lib/platform/linux/x11fileselector.cpp index f0fd776f0..ab80daafa 100644 --- a/vstgui/lib/platform/linux/x11fileselector.cpp +++ b/vstgui/lib/platform/linux/x11fileselector.cpp @@ -5,6 +5,8 @@ #include "../../cfileselector.h" #include #include +#include +#include #include #include #include diff --git a/vstgui/lib/platform/mac/cocoa/cocoahelpers.h b/vstgui/lib/platform/mac/cocoa/cocoahelpers.h index a19072ffc..d2b76c743 100644 --- a/vstgui/lib/platform/mac/cocoa/cocoahelpers.h +++ b/vstgui/lib/platform/mac/cocoa/cocoahelpers.h @@ -7,6 +7,7 @@ #import "../../../crect.h" #import "../../../cpoint.h" #import "../../../ccolor.h" +#import "../../../vstguifwd.h" #if MAC_COCOA && defined (__OBJC__) @@ -68,6 +69,7 @@ extern HIDDEN VstKeyCode CreateVstKeyCodeFromNSEvent (NSEvent* theEvent); extern HIDDEN NSString* GetVirtualKeyCodeString (int32_t virtualKeyCode); extern HIDDEN int32_t eventButton (NSEvent* theEvent); extern HIDDEN void convertPointToGlobal (NSView* view, NSPoint& p); +extern HIDDEN NSImage* bitmapToNSImage (VSTGUI::CBitmap* bitmap); //------------------------------------------------------------------------------------ // Helpers @@ -114,14 +116,6 @@ HIDDEN inline NSColor* nsColorFromCColor (const VSTGUI::CColor& color) alpha:color.normAlpha ()]; } -//------------------------------------------------------------------------------------ -HIDDEN inline NSImage* imageFromCGImageRef (CGImageRef image, double scaleFactor = 1.) -{ - auto width = CGImageGetWidth (image) / scaleFactor; - auto height = CGImageGetHeight (image) / scaleFactor; - return [[NSImage alloc] initWithCGImage:image size:NSMakeSize (width, height)]; -} - //------------------------------------------------------------------------------------ struct MacEventModifier { diff --git a/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm b/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm index 48c801d5a..33752cd61 100644 --- a/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm +++ b/vstgui/lib/platform/mac/cocoa/cocoahelpers.mm @@ -8,6 +8,8 @@ #include "../../../vstkeycode.h" #include "../../../cview.h" +#include "../../../cbitmap.h" +#include "../cgbitmap.h" //------------------------------------------------------------------------------------ HIDDEN Class generateUniqueClass (NSMutableString* className, Class baseClass) @@ -222,5 +224,26 @@ HIDDEN void convertPointToGlobal (NSView* view, NSPoint& p) p = r.origin; } +//----------------------------------------------------------------------------- +HIDDEN NSImage* bitmapToNSImage (CBitmap* bitmap) +{ + if (!bitmap) + return nil; + + NSImage* image = + [[NSImage alloc] initWithSize:NSMakeSize (bitmap->getWidth (), bitmap->getHeight ())]; + for (auto& platformBitmap : *bitmap) + { + if (auto cgBitmap = dynamic_cast (platformBitmap.get ())) + { + if (auto rep = [[NSBitmapImageRep alloc] initWithCGImage:cgBitmap->getCGImage ()]) + { + [image addRepresentation:rep]; + [rep release]; + } + } + } + return image; +} #endif // MAC_COCOA diff --git a/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm b/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm index 0cfd10a1c..b349598a1 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm +++ b/vstgui/lib/platform/mac/cocoa/nsviewdraggingsession.mm @@ -259,19 +259,7 @@ void initClass () //----------------------------------------------------------------------------- NSImage* NSViewDraggingSession::nsImageForDragOperation (CBitmap* bitmap) { - if (!bitmap) - return nil; - auto platformBitmap = bitmap->getPlatformBitmap (); - if (!platformBitmap) - return nil; - auto cgBitmap = platformBitmap.cast (); - if (!cgBitmap) - return nil; - auto cgImage = cgBitmap->getCGImage (); - if (!cgImage) - return nil; - auto scaleFactor = platformBitmap->getScaleFactor (); - return [imageFromCGImageRef (cgImage, scaleFactor) autorelease]; + return bitmapToNSImage (bitmap); } //------------------------------------------------------------------------ diff --git a/vstgui/lib/platform/mac/cocoa/nsviewframe.h b/vstgui/lib/platform/mac/cocoa/nsviewframe.h index e8193fe29..888eef408 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewframe.h +++ b/vstgui/lib/platform/mac/cocoa/nsviewframe.h @@ -112,9 +112,7 @@ class NSViewFrame : public IPlatformFrame, public ICocoaPlatformFrame, public IP bool trackingAreaInitialized; bool inDraw; bool useInvalidRects {false}; -#if DEBUG - bool visualizeDirtyRects {false}; -#endif + CCursorType cursor; CButtonState mouseDownButtonState {}; CInvalidRectList invalidRectList; diff --git a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm index bd6404aa2..ac621f8cb 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewframe.mm +++ b/vstgui/lib/platform/mac/cocoa/nsviewframe.mm @@ -35,6 +35,7 @@ using namespace VSTGUI; #if DEBUG + //------------------------------------------------------------------------ @interface DebugRedrawAnimDelegate : NSObject @property (retain, readwrite) CALayer* layer; @@ -43,13 +44,20 @@ @interface DebugRedrawAnimDelegate : NSObject @implementation DebugRedrawAnimDelegate +//------------------------------------------------------------------------ +- (void)dealloc +{ + self.layer = nil; + [super dealloc]; +} + //------------------------------------------------------------------------ - (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag { if (flag) { [self.layer removeFromSuperlayer]; - [self.layer release]; + self.layer = nil; } } @@ -905,7 +913,7 @@ static id VSTGUI_NSView_makeTouchbar (id self) void NSViewFrame::addDebugRedrawRect (CRect r, bool isClipBoundingBox) { #if DEBUG - if (visualizeDirtyRects && nsView.layer) + if (getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas () && nsView.layer) { auto delegate = [[DebugRedrawAnimDelegate new] autorelease]; auto anim = [CABasicAnimation animation]; @@ -1307,8 +1315,6 @@ static id VSTGUI_NSView_makeTouchbar (id self) lastDragOperationResult = kDragError; if (nsView) { - CGBitmap* cgBitmap = dragBitmap ? dynamic_cast (dragBitmap->getPlatformBitmap ().get ()) : nullptr; - CGImageRef cgImage = cgBitmap ? cgBitmap->getCGImage () : nullptr; NSPoint bitmapOffset = { static_cast(offset.x), static_cast(offset.y) }; NSEvent* event = [NSApp currentEvent]; @@ -1316,10 +1322,9 @@ static id VSTGUI_NSView_makeTouchbar (id self) [event type] == MacEventType::LeftMouseDragged)) return kDragRefused; NSPoint nsLocation = [event locationInWindow]; - NSImage* nsImage = nil; - if (cgImage) + NSImage* nsImage = bitmapToNSImage (dragBitmap); + if (nsImage) { - nsImage = [imageFromCGImageRef (cgImage) autorelease]; nsLocation = [nsView convertPoint:nsLocation fromView:nil]; bitmapOffset.x += nsLocation.x; bitmapOffset.y += nsLocation.y + [nsImage size].height; diff --git a/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm b/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm index 9bd9f1944..cae35d8b9 100644 --- a/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm +++ b/vstgui/lib/platform/mac/cocoa/nsviewoptionmenu.mm @@ -133,19 +133,12 @@ static id VSTGUI_NSMenu_Init (id self, SEL _cmd, void* _menu) [nsItem setKeyEquivalentModifierMask:keyModifiers]; } } - if (nsItem && item->getIcon ()) + if (nsItem) { - IPlatformBitmap* platformBitmap = item->getIcon ()->getPlatformBitmap (); - CGBitmap* cgBitmap = platformBitmap ? dynamic_cast (platformBitmap) : nullptr; - CGImageRef image = cgBitmap ? cgBitmap->getCGImage () : nullptr; - if (image) + if (auto nsImage = bitmapToNSImage (item->getIcon ())) { - NSImage* nsImage = imageFromCGImageRef (image); - if (nsImage) - { - [nsItem setImage:nsImage]; - [nsImage release]; - } + [nsItem setImage:nsImage]; + [nsImage release]; } } } diff --git a/vstgui/lib/platform/mac/macfactory.h b/vstgui/lib/platform/mac/macfactory.h index ada77140a..4c71b3ccb 100644 --- a/vstgui/lib/platform/mac/macfactory.h +++ b/vstgui/lib/platform/mac/macfactory.h @@ -22,6 +22,9 @@ class MacFactory final : public IPlatformFactory void setUseAsynchronousLayerDrawing (bool state) const noexcept; bool getUseAsynchronousLayerDrawing () const noexcept; + void enableVisualizeRedrawAreas (bool state) const noexcept; + bool enableVisualizeRedrawAreas () const noexcept; + /** Return platform ticks (millisecond resolution) * @return ticks */ diff --git a/vstgui/lib/platform/mac/macfactory.mm b/vstgui/lib/platform/mac/macfactory.mm index 5fcac633d..a929276b4 100644 --- a/vstgui/lib/platform/mac/macfactory.mm +++ b/vstgui/lib/platform/mac/macfactory.mm @@ -33,6 +33,7 @@ struct mach_timebase_info timebaseInfo; CFBundleRef bundle {nullptr}; bool useAsynchronousLayerDrawing {true}; + bool visualizeRedrawAreas {false}; }; //----------------------------------------------------------------------------- @@ -61,6 +62,18 @@ return impl->useAsynchronousLayerDrawing; } +//----------------------------------------------------------------------------- +void MacFactory::enableVisualizeRedrawAreas (bool state) const noexcept +{ + impl->visualizeRedrawAreas = state; +} + +//----------------------------------------------------------------------------- +bool MacFactory::enableVisualizeRedrawAreas () const noexcept +{ + return impl->visualizeRedrawAreas; +} + //----------------------------------------------------------------------------- uint64_t MacFactory::getTicks () const noexcept { diff --git a/vstgui/lib/vstguibase.h b/vstgui/lib/vstguibase.h index 03e3e8642..516b5ee52 100644 --- a/vstgui/lib/vstguibase.h +++ b/vstgui/lib/vstguibase.h @@ -13,6 +13,7 @@ //----------------------------------------------------------------------------- #define VSTGUI_VERSION_MAJOR 4 #define VSTGUI_VERSION_MINOR 10 +#define VSTGUI_VERSION_PATCHLEVEL 1 //----------------------------------------------------------------------------- // Platform definitions @@ -543,6 +544,12 @@ struct BitScopeToggleT B bit; }; +//----------------------------------------------------------------------------- +#define VSTGUI_NEWER_THAN(major, minor) \ + (VSTGUI_VERSION > major || VSTGUI_VERSION_MAJOR == major && VSTGUI_VERSION_MINOR > minor) + +#define VSTGUI_NEWER_THAN_4_10 VSTGUI_NEWER_THAN (4, 10) + } // VSTGUI //----------------------------------------------------------------------------- diff --git a/vstgui/lib/vstguifwd.h b/vstgui/lib/vstguifwd.h index 3aff5f203..9d5250879 100644 --- a/vstgui/lib/vstguifwd.h +++ b/vstgui/lib/vstguifwd.h @@ -6,6 +6,7 @@ #include "vstguibase.h" #include +#include #include namespace VSTGUI { diff --git a/vstgui/plugin-bindings/vst3editor.cpp b/vstgui/plugin-bindings/vst3editor.cpp index ca3740d86..e3fc3daf9 100644 --- a/vstgui/plugin-bindings/vst3editor.cpp +++ b/vstgui/plugin-bindings/vst3editor.cpp @@ -317,7 +317,11 @@ class ParameterChangeListener : public Steinberg::FObject c->setValue ((float)value - minValue); } else + { c->setValue ((float)value); + if (c->isDirty ()) + c->valueChanged (); + } } else c->setValueNormalized ((float)value); diff --git a/vstgui/standalone/source/platform/mac/macapplication.mm b/vstgui/standalone/source/platform/mac/macapplication.mm index bcfa77574..acbc2ec3a 100644 --- a/vstgui/standalone/source/platform/mac/macapplication.mm +++ b/vstgui/standalone/source/platform/mac/macapplication.mm @@ -122,6 +122,10 @@ - (BOOL)validateMenuItem:(nonnull NSMenuItem*)menuItem { return Detail::getApplicationPlatformAccess ()->canHandleCommand ([command command]); } + else if (menuItem.action == @selector (visualizeRedrawAreas:) || menuItem.action == @selector (useAsynchronousCALayerDrawing:)) + { + return YES; + } return NO; } @@ -402,15 +406,20 @@ - (void)setupMainMenu } NSMenuItem* debugMenu = [mainMenu itemWithTitle:@"Debug"]; - if (debugMenu && debugMenu.submenu) + if (debugMenu && debugMenu.submenu && [debugMenu.submenu itemWithTitle:@"Color Panel"] == nil) { - if ([debugMenu.submenu itemWithTitle:@"Color Panel"] == nil) - { - [debugMenu.submenu addItem:[NSMenuItem separatorItem]]; - [debugMenu.submenu addItemWithTitle:@"Color Panel" - action:@selector (orderFrontColorPanel:) - keyEquivalent:@""]; - } + [debugMenu.submenu addItem:[NSMenuItem separatorItem]]; + [debugMenu.submenu addItemWithTitle:@"Color Panel" + action:@selector (orderFrontColorPanel:) + keyEquivalent:@""]; + [debugMenu.submenu addItem:[NSMenuItem separatorItem]]; + [debugMenu.submenu addItemWithTitle:@"Use Asynchronous CALayer Drawing" + action:@selector (useAsynchronousCALayerDrawing:) + keyEquivalent:@""]; + [debugMenu.submenu addItemWithTitle:@"Visualize Redraw Areas" + action:@selector (visualizeRedrawAreas:) + keyEquivalent:@""]; + [self updateDebugMenuItems]; } // move Windows menu to the end @@ -436,6 +445,43 @@ - (void)triggerSetupMainMenu }); } +//------------------------------------------------------------------------ +- (void)visualizeRedrawAreas:(id)sender +{ + auto state = VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas (); + VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas (!state); + [self updateDebugMenuItems]; +} + +//------------------------------------------------------------------------ +- (void)useAsynchronousCALayerDrawing:(id)sender +{ + auto state = VSTGUI::getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing (); + VSTGUI::getPlatformFactory ().asMacFactory ()->setUseAsynchronousLayerDrawing (!state); + [self updateDebugMenuItems]; +} + +//------------------------------------------------------------------------ +- (void)updateDebugMenuItems +{ + NSMenuItem* debugMenu = [NSApp.mainMenu itemWithTitle:@"Debug"]; + if (debugMenu && debugMenu.submenu) + { + if (auto item = [debugMenu.submenu itemWithTitle:@"Visualize Redraw Areas"]) + { + auto state = + VSTGUI::getPlatformFactory ().asMacFactory ()->enableVisualizeRedrawAreas (); + item.state = state ? NSControlStateValueOn : NSControlStateValueOff; + } + if (auto item = [debugMenu.submenu itemWithTitle:@"Use Asynchronous CALayer Drawing"]) + { + auto state = + VSTGUI::getPlatformFactory ().asMacFactory ()->getUseAsynchronousLayerDrawing (); + item.state = state ? NSControlStateValueOn : NSControlStateValueOff; + } + } +} + #if !VSTGUI_STANDALONE_USE_GENERIC_ALERTBOX_ON_MACOS //------------------------------------------------------------------------ - (nonnull NSAlert*)createAlert:(const AlertBoxConfig&)config diff --git a/vstgui/tests/unittest/CMakeLists.txt b/vstgui/tests/unittest/CMakeLists.txt index 159f6b7c7..0e7ea5f39 100644 --- a/vstgui/tests/unittest/CMakeLists.txt +++ b/vstgui/tests/unittest/CMakeLists.txt @@ -125,45 +125,77 @@ if(UNIX AND NOT CMAKE_HOST_APPLE) endif() ########################################################################################## -#include_directories(../) -add_executable(${target} ${${target}_sources}) -target_link_libraries(${target} - ${${target}_PLATFORM_LIBS} -) -vstgui_set_cxx_version(${target} 14) -target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS} ENABLE_UNIT_TESTS=1 VSTGUI_LIVE_EDITING=1) -vstgui_source_group_by_folder(${target}) +if(XCODE) + set(${target}_sources + "${${target}_sources}" + "${VSTGUI_TEST_BASE}xcodetestintegration.mm" + ) -add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittests") + add_library(${target} MODULE ${${target}_sources}) + vstgui_set_cxx_version(${target} 14) + target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS} ENABLE_UNIT_TESTS=1 VSTGUI_LIVE_EDITING=1) + target_link_libraries(${target} + ${${target}_PLATFORM_LIBS} + "-framework XCTest" + ) -########################################################################################## -if(UNIX AND NOT CMAKE_HOST_APPLE) - target_include_directories(${target} PRIVATE ${X11_INCLUDE_DIR}) - target_include_directories(${target} PRIVATE ${GTK3_INCLUDE_DIRS}) - target_include_directories(${target} PRIVATE ${GTKMM3_INCLUDE_DIRS}) - target_include_directories(${target} PRIVATE ${FREETYPE_INCLUDE_DIRS}) -endif() + set_target_properties(${target} + PROPERTIES + BUNDLE TRUE + XCODE_ATTRIBUTE_GENERATE_PKGINFO_FILE YES + XCODE_ATTRIBUTE_WRAPPER_EXTENSION xctest + XCODE_ATTRIBUTE_ENABLE_TESTABILITY YES + ) -if(CMAKE_HOST_APPLE) - option(VSTGUI_ENABLE_CODECOVERAGE "Generate Code Coverage Data" OFF) - if(VSTGUI_ENABLE_CODECOVERAGE) - add_executable(codecoverage ${${target}_sources}) - - set(target codecoverage) - - target_link_libraries(${target} - "-framework Cocoa" - "-framework OpenGL" - "-framework QuartzCore" - "-framework Accelerate" - "--coverage" - ) - - vstgui_set_cxx_version(${target} 14) - target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS} ENABLE_UNIT_TESTS=1 VSTGUI_LIVE_EDITING=1 VSTGUI_ENABLE_DEPRECATED_METHODS=0) - vstgui_source_group_by_folder(${target}) - target_compile_options(${target} PUBLIC "--coverage") - add_custom_command(TARGET ${target} POST_BUILD COMMAND "/usr/bin/ruby" "${CMAKE_CURRENT_SOURCE_DIR}/lcov/generate.rb" "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/vstgui.build/Debug/codecoverage.build/Objects-normal/x86_64" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/coverage_output" VERBATIM) - endif() -endif() + vstgui_source_group_by_folder(${target}) + +else() + set(${target}_sources + ${${target}_sources} + "${VSTGUI_TEST_BASE}unittestmain.cpp" + ) + + add_executable(${target} ${${target}_sources}) + target_link_libraries(${target} + ${${target}_PLATFORM_LIBS} + ) + + vstgui_set_cxx_version(${target} 14) + target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS} ENABLE_UNIT_TESTS=1 VSTGUI_LIVE_EDITING=1) + vstgui_source_group_by_folder(${target}) + + add_custom_command(TARGET ${target} POST_BUILD COMMAND "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/unittests") + + ########################################################################################## + if(UNIX AND NOT CMAKE_HOST_APPLE) + target_include_directories(${target} PRIVATE ${X11_INCLUDE_DIR}) + target_include_directories(${target} PRIVATE ${GTK3_INCLUDE_DIRS}) + target_include_directories(${target} PRIVATE ${GTKMM3_INCLUDE_DIRS}) + target_include_directories(${target} PRIVATE ${FREETYPE_INCLUDE_DIRS}) + endif() + +endif(XCODE) + +#if(CMAKE_HOST_APPLE) +# option(VSTGUI_ENABLE_CODECOVERAGE "Generate Code Coverage Data" OFF) +# if(VSTGUI_ENABLE_CODECOVERAGE) +# add_executable(codecoverage ${${target}_sources}) +# +# set(target codecoverage) +# +# target_link_libraries(${target} +# "-framework Cocoa" +# "-framework OpenGL" +# "-framework QuartzCore" +# "-framework Accelerate" +# "--coverage" +# ) +# +# vstgui_set_cxx_version(${target} 14) +# target_compile_definitions(${target} ${VSTGUI_COMPILE_DEFINITIONS} ENABLE_UNIT_TESTS=1 VSTGUI_LIVE_EDITING=1 VSTGUI_ENABLE_DEPRECATED_METHODS=0) +# vstgui_source_group_by_folder(${target}) +# target_compile_options(${target} PUBLIC "--coverage") +# add_custom_command(TARGET ${target} POST_BUILD COMMAND "/usr/bin/ruby" "${CMAKE_CURRENT_SOURCE_DIR}/lcov/generate.rb" "${PROJECT_SOURCE_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/vstgui.build/Debug/codecoverage.build/Objects-normal/x86_64" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${target}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/coverage_output" VERBATIM) +# endif() +#endif() diff --git a/vstgui/tests/unittest/lib/controls/csegmentbutton_test.cpp b/vstgui/tests/unittest/lib/controls/csegmentbutton_test.cpp index 74a948d57..36577589f 100644 --- a/vstgui/tests/unittest/lib/controls/csegmentbutton_test.cpp +++ b/vstgui/tests/unittest/lib/controls/csegmentbutton_test.cpp @@ -302,6 +302,59 @@ TESTCASE(CSegmentButtonTest, parent->removed (root); ); + + TEST(mouseDownEventWithManySegments, + // Create segment button with 32 segments and attach it + const auto numSegments = 32; + CRect r (0, 0, 20 * numSegments, 100); + auto b = new CSegmentButton (r); + b->setStyle (CSegmentButton::Style::kHorizontal); + for (auto i = 0; i < numSegments; ++i) + b->addSegment ({}); + for (const auto& s : b->getSegments()) + EXPECT (s.rect == CRect (0, 0, 0, 0)); + auto root = owned (new CViewContainer (r)); + auto parent = new CViewContainer (r); + root->addView (parent); + parent->addView (b); + parent->attached (root); + + // Select the e.g. 20th segment + constexpr auto kSelectedSegment = 20; + CPoint p (0, 0); + p (20 * kSelectedSegment + 5, 0); + EXPECT (b->onMouseDown (p, kLButton) == kMouseDownEventHandledButDontNeedMovedOrUpEvents); + EXPECT (b->getSelectedSegment () == kSelectedSegment); + + parent->removed (root); + ); + + TEST(mouseDownEventOnLastSegment, + // Create segment button with 31 segments and attach it. + // 31 segments causing rounding errors inside segment button. + const auto numSegments = 31; + CRect r(0, 0, 20 * numSegments, 100); + auto b = new CSegmentButton(r); + b->setStyle(CSegmentButton::Style::kHorizontal); + for (auto i = 0; i < numSegments; ++i) + b->addSegment({}); + for (const auto& s : b->getSegments()) + EXPECT(s.rect == CRect(0, 0, 0, 0)); + auto root = owned(new CViewContainer(r)); + auto parent = new CViewContainer(r); + root->addView(parent); + parent->addView(b); + parent->attached(root); + + // Select the last segment + constexpr auto kSelectedSegment = numSegments - 1; + CPoint p(0, 0); + p(20 * kSelectedSegment + 5, 0); + EXPECT(b->onMouseDown(p, kLButton) == kMouseDownEventHandledButDontNeedMovedOrUpEvents); + EXPECT(b->getSelectedSegment() == kSelectedSegment); + + parent->removed(root); + ); TEST(focusPathSetting, CSegmentButton b (CRect (0, 0, 10, 10)); diff --git a/vstgui/tests/unittest/lib/controls/ctextbutton_test.cpp b/vstgui/tests/unittest/lib/controls/ctextbutton_test.cpp index 97c0eb248..528e6052c 100644 --- a/vstgui/tests/unittest/lib/controls/ctextbutton_test.cpp +++ b/vstgui/tests/unittest/lib/controls/ctextbutton_test.cpp @@ -7,7 +7,7 @@ namespace VSTGUI { -TESTCASE(CCheckboxTest, +TESTCASE(CTextButtonTest, TEST(mouseEventsKickStyle, auto b = owned (new CTextButton (CRect (10, 10, 50, 20))); diff --git a/vstgui/tests/unittest/lib/crect_test.cpp b/vstgui/tests/unittest/lib/crect_test.cpp index 9a81a7891..fabf2c036 100644 --- a/vstgui/tests/unittest/lib/crect_test.cpp +++ b/vstgui/tests/unittest/lib/crect_test.cpp @@ -91,7 +91,7 @@ TESTCASE(CRectTest, EXPECT(r.getWidth () == 100.); ); - TEST(setWidth, + TEST(setHeight, CRect r (0., 0., 0., 0.); EXPECT(r.getHeight () == 0.); r.setHeight (100.); diff --git a/vstgui/tests/unittest/lib/cviewcontainer_test.cpp b/vstgui/tests/unittest/lib/cviewcontainer_test.cpp index 6162543e5..4a3e3ee98 100644 --- a/vstgui/tests/unittest/lib/cviewcontainer_test.cpp +++ b/vstgui/tests/unittest/lib/cviewcontainer_test.cpp @@ -143,6 +143,7 @@ TESTCASE(CViewContainerTest, ); TEST(addView, + assert (container); CView* view = new CView (CRect (0, 0, 10, 10)); CView* view2 = new CView (CRect (0, 0, 10, 10)); diff --git a/vstgui/tests/unittest/lib/utf8stringview_test.cpp b/vstgui/tests/unittest/lib/utf8stringview_test.cpp index dbc37284c..fcb8c0673 100644 --- a/vstgui/tests/unittest/lib/utf8stringview_test.cpp +++ b/vstgui/tests/unittest/lib/utf8stringview_test.cpp @@ -41,7 +41,8 @@ TESTCASE ( EXPECT(str.calculateByteCount () == 46); ); - TEST(contains, UTF8StringView str (asciiStr); + TEST(contains, + UTF8StringView str (asciiStr); EXPECT(str.contains ("simple") == true); EXPECT(str.contains ("not") == false); ); @@ -74,7 +75,7 @@ TESTCASE ( EXPECT(str1 != str3); ); - TEST(contains, + TEST(containsCheckCase, UTF8StringView str (asciiStr); EXPECT(str.contains ("simple")); EXPECT(str.contains ("Simple") == false); @@ -86,11 +87,6 @@ TESTCASE ( EXPECT(str.startsWith ("This")); ); - TEST(endsWith, - UTF8StringView str (asciiStr); - EXPECT(str.endsWith ("String")); - ); - TEST(toDouble, UTF8StringView str ("5.1"); EXPECT(str.toDouble () == 5.1); diff --git a/vstgui/tests/unittest/uidescription/uiattributes_test.cpp b/vstgui/tests/unittest/uidescription/uiattributes_test.cpp index f885f9d11..a4085d102 100644 --- a/vstgui/tests/unittest/uidescription/uiattributes_test.cpp +++ b/vstgui/tests/unittest/uidescription/uiattributes_test.cpp @@ -191,6 +191,11 @@ TESTCASE(UIAttributesTest, EXPECT(UIAttributes::stringToRect ("0, 12.5, 5, 8", r) && r == CRect (0, 12.5, 5, 8)) ) + TEST(stringArrayToString_emptyStringArray, + const UIAttributes::StringArray strings; + const auto s = UIAttributes::stringArrayToString(strings); + EXPECT(s.empty()) + ) ); } // VSTGUI diff --git a/vstgui/tests/unittest/uidescription/uiviewcreator/ccheckboxcreator_test.cpp b/vstgui/tests/unittest/uidescription/uiviewcreator/ccheckboxcreator_test.cpp index d98a4d4dd..9751825b2 100644 --- a/vstgui/tests/unittest/uidescription/uiviewcreator/ccheckboxcreator_test.cpp +++ b/vstgui/tests/unittest/uidescription/uiviewcreator/ccheckboxcreator_test.cpp @@ -13,7 +13,7 @@ namespace VSTGUI { using namespace UIViewCreator; -TESTCASE(COnOffButtonCreatorTest, +TESTCASE(CCheckBoxCreatorTest, TEST(title, DummyUIDescription uidesc; diff --git a/vstgui/tests/unittest/uidescription/uiviewcreator/ctexteditcreator_test.cpp b/vstgui/tests/unittest/uidescription/uiviewcreator/ctexteditcreator_test.cpp index d17c4e1b7..0dd4f1e4b 100644 --- a/vstgui/tests/unittest/uidescription/uiviewcreator/ctexteditcreator_test.cpp +++ b/vstgui/tests/unittest/uidescription/uiviewcreator/ctexteditcreator_test.cpp @@ -29,14 +29,14 @@ TESTCASE(CTextEditCreatorTest, }); ); - TEST(autoSizeToFit, + TEST(secureStyle, DummyUIDescription uidesc; testAttribute (kCTextEdit, kAttrSecureStyle, true, &uidesc, [&] (CTextEdit* v) { return v->getSecureStyle () == true; }); ); - TEST(autoSizeToFit, + TEST(placeholderTitle, DummyUIDescription uidesc; auto testValue = "This is a placeholder"; testAttribute (kCTextEdit, kAttrPlaceholderTitle, testValue, &uidesc, [&] (CTextEdit* b) { diff --git a/vstgui/tests/unittest/uidescription/uiviewcreator/stringlistcreator_test.cpp b/vstgui/tests/unittest/uidescription/uiviewcreator/stringlistcreator_test.cpp index 2321d4c64..ed50817b9 100644 --- a/vstgui/tests/unittest/uidescription/uiviewcreator/stringlistcreator_test.cpp +++ b/vstgui/tests/unittest/uidescription/uiviewcreator/stringlistcreator_test.cpp @@ -114,7 +114,7 @@ TESTCASE(StringListControlCreatorTest, }); ); - TEST(lineWidth, + TEST(textInset, DummyUIDescription uidesc; testAttribute(kCStringListControl, kAttrTextInset, 14., &uidesc, [&] (CListControl* v) { return getDrawer (v)->getTextInset () == 14.; diff --git a/vstgui/tests/unittest/unittestmain.cpp b/vstgui/tests/unittest/unittestmain.cpp new file mode 100644 index 000000000..659f1328a --- /dev/null +++ b/vstgui/tests/unittest/unittestmain.cpp @@ -0,0 +1,176 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#include "unittests.h" + +#if ENABLE_UNIT_TESTS +#include "../../lib/vstguidebug.h" +#include "../../lib/vstguiinit.h" + +#include +#include +#include + +#if MAC +#include +#endif + +#if WINDOWS +#ifndef NOMINMAX +#define NOMINMAX +#endif +#include +#include +#endif + +//------------------------------------------------------------------------ +namespace VSTGUI { +namespace UnitTest { + +using namespace std::chrono; + +//---------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------- +//---------------------------------------------------------------------------------------------------- +class StdOutContext : public Context +{ +private: + struct Result { + int succeded; + int failed; + + Result () : succeded (0), failed (0) {} + + Result& operator +=(const Result& r) { succeded += r.succeded; failed += r.failed; return *this; } + }; +public: + StdOutContext () : intend (0) {} + + void printRaw (const char* str) override + { + testOutput += str; + testOutput += "\n"; + } + + void printOutput () + { + if (testOutput.empty () == false) + { + printf ("%s", testOutput.c_str ()); + testOutput = ""; + } + } + void printIntend () + { + for (int i = 0; i < intend; i++) printf ("\t"); + } + + Result runTestCase (const TestCase& testCase) + { + Result result; + printf ("%s\n", testCase.getName ().c_str()); + intend++; + for (auto& it : testCase) + { + try { + if (testCase.setup ()) + { + testCase.setup () (this); + } + if (runTest (it.first, it.second)) + { + result.succeded++; + } + else + { + result.failed++; + } + if (testCase.teardown ()) + { + testCase.teardown () (this); + } + } catch (const std::exception&) + { + result.failed++; + } + } + intend--; + return result; + } + + bool runTest (const std::string& testName, const TestFunction& f) + { + time_point start, end; + printIntend (); + printf ("%s", testName.c_str()); + intend++; + start = system_clock::now (); + bool result; + try { + result = f (this); + } catch (const error& exc) + { + result = false; + print ("%s", exc.what () ? exc.what () : "unknown"); + } catch (const std::exception& exc) + { + result = false; + printf ("Exception: %s", exc.what () ? exc.what () : "unknown"); + } + end = system_clock::now (); + intend--; + printf (" [%s] -> %lld µs\n", result ? "OK" : "Failed", duration_cast (end-start).count ()); + printOutput (); + return result; + } + + int run () + { + Result result; + time_point start, end; + start = system_clock::now (); + for (auto& it : UnitTestRegistry::instance ()) + { + result += runTestCase (std::move (it)); + } + end = system_clock::now (); + print ("\nDone running %d tests in %lldms. [%d Failed]\n", result.succeded+result.failed, duration_cast (end-start).count (), result.failed); + printOutput (); + return result.failed; + } +private: + int intend; + std::string testOutput; +}; + +static int RunTests () +{ + StdOutContext context; + return context.run (); +} + +//------------------------------------------------------------------------ +} // UnitTest +} // VSTGUI + +int main () +{ + VSTGUI::setAssertionHandler ([] (const char* file, const char* line, const char* desc) { + throw std::logic_error (desc ? desc : "unknown"); + }); +#if MAC + VSTGUI::init (CFBundleGetMainBundle ()); +#elif WINDOWS + CoInitialize (nullptr); + VSTGUI::init (GetModuleHandle (nullptr)); +#elif LINUX + VSTGUI::init (nullptr); +#endif + auto result = VSTGUI::UnitTest::RunTests (); + VSTGUI::exit (); + return result; +} + +#endif + diff --git a/vstgui/tests/unittest/unittests.cpp b/vstgui/tests/unittest/unittests.cpp index 774010b0e..ba0dcef0b 100644 --- a/vstgui/tests/unittest/unittests.cpp +++ b/vstgui/tests/unittest/unittests.cpp @@ -8,10 +8,6 @@ #include "../../lib/vstguidebug.h" #include "../../lib/vstguiinit.h" -#if MAC -#include -#endif - #include #include #include @@ -133,148 +129,8 @@ void Context::print (const char* fmt, ...) #endif } -using namespace std::chrono; - -//---------------------------------------------------------------------------------------------------- -//---------------------------------------------------------------------------------------------------- -//---------------------------------------------------------------------------------------------------- -class StdOutContext : public Context -{ -private: - struct Result { - int succeded; - int failed; - - Result () : succeded (0), failed (0) {} - - Result& operator +=(const Result& r) { succeded += r.succeded; failed += r.failed; return *this; } - }; -public: - StdOutContext () : intend (0) {} - - void printRaw (const char* str) override - { - testOutput += str; - testOutput += "\n"; - } - - void printOutput () - { - if (testOutput.empty () == false) - { - printf ("%s", testOutput.c_str ()); - testOutput = ""; - } - } - void printIntend () - { - for (int i = 0; i < intend; i++) printf ("\t"); - } - - Result runTestCase (const TestCase& testCase) - { - Result result; - printf ("%s\n", testCase.getName ().c_str()); - intend++; - for (auto& it : testCase) - { - try { - if (testCase.setup ()) - { - testCase.setup () (this); - } - if (runTest (it.first, it.second)) - { - result.succeded++; - } - else - { - result.failed++; - } - if (testCase.teardown ()) - { - testCase.teardown () (this); - } - } catch (const std::exception&) - { - result.failed++; - } - } - intend--; - return result; - } - - bool runTest (const std::string& testName, const TestFunction& f) - { - time_point start, end; - printIntend (); - printf ("%s", testName.c_str()); - intend++; - start = system_clock::now (); - bool result; - try { - result = f (this); - } catch (const error& exc) - { - result = false; - print ("%s", exc.what () ? exc.what () : "unknown"); - } catch (const std::exception& exc) - { - result = false; - printf ("Exception: %s", exc.what () ? exc.what () : "unknown"); - } - end = system_clock::now (); - intend--; - printf (" [%s] -> %lld µs\n", result ? "OK" : "Failed", duration_cast (end-start).count ()); - printOutput (); - return result; - } - - int run () - { - Result result; - time_point start, end; - start = system_clock::now (); - for (auto& it : UnitTestRegistry::instance ()) - { - result += runTestCase (std::move (it)); - } - end = system_clock::now (); - print ("\nDone running %d tests in %lldms. [%d Failed]\n", result.succeded+result.failed, duration_cast (end-start).count (), result.failed); - printOutput (); - return result.failed; - } -private: - int intend; - std::string testOutput; -}; - -static int RunTests () -{ - StdOutContext context; - return context.run (); -} - }} // namespaces -int main () -{ - VSTGUI::setAssertionHandler ([] (const char* file, const char* line, const char* desc) { - throw std::logic_error (desc ? desc : "unknown"); - }); -#if MAC - VSTGUI::init (CFBundleGetMainBundle ()); -#elif WINDOWS - CoInitialize (nullptr); - VSTGUI::init (GetModuleHandle (nullptr)); -#elif LINUX - VSTGUI::init (nullptr); -#endif - auto result = VSTGUI::UnitTest::RunTests (); - VSTGUI::exit (); - return result; -} - TESTCASE(Example, static int result; diff --git a/vstgui/tests/unittest/unittests.h b/vstgui/tests/unittest/unittests.h index b547e973f..5b399b4b0 100644 --- a/vstgui/tests/unittest/unittests.h +++ b/vstgui/tests/unittest/unittests.h @@ -61,7 +61,13 @@ namespace UnitTest { class error : public std::logic_error { public: - error (const char* str) : std::logic_error (str) {} + error (const char* file, size_t line, const char* str) + : std::logic_error (str), filePath (file), lineNo (line) + { + } + + std::string filePath; + size_t lineNo; }; #define VSTGUI_UNITTEST_MAKE_STRING_PRIVATE_DONT_USE(x) # x @@ -70,7 +76,7 @@ class error : public std::logic_error //---------------------------------------------------------------------------------------------------- #define TESTCASE(name,function) static VSTGUI::UnitTest::TestCaseRegistrar name##TestCaseRegistrar (VSTGUI_UNITTEST_MAKE_STRING(name), [](VSTGUI::UnitTest::TestCase* testCase) { function }) #define TEST(name,function) testCase->registerTest (VSTGUI_UNITTEST_MAKE_STRING(name), [](VSTGUI::UnitTest::Context* context) { { function } return true; }); -#define EXPECT(condition) if (!(condition)) { throw VSTGUI::UnitTest::error (__FILE__ ":" VSTGUI_UNITTEST_MAKE_STRING(__LINE__) ": Expected: " VSTGUI_UNITTEST_MAKE_STRING(condition)); } +#define EXPECT(condition) if (!(condition)) { throw VSTGUI::UnitTest::error (__FILE__, __LINE__, "Expected: " VSTGUI_UNITTEST_MAKE_STRING(condition)); } #define FAIL(reason) { context->print (__FILE__ ":" VSTGUI_UNITTEST_MAKE_STRING(__LINE__) ": Failure: " reason); return false; } #define EXPECT_EXCEPTION(call, name) \ diff --git a/vstgui/tests/unittest/xcodetestintegration.mm b/vstgui/tests/unittest/xcodetestintegration.mm new file mode 100644 index 000000000..99388f8b8 --- /dev/null +++ b/vstgui/tests/unittest/xcodetestintegration.mm @@ -0,0 +1,157 @@ +// This file is part of VSTGUI. It is subject to the license terms +// in the LICENSE file found in the top-level directory of this +// distribution and at http://github.com/steinbergmedia/vstgui/LICENSE + +#import +#import +#import + +#import "../../lib/cstring.h" +#import "../../lib/vstguiinit.h" +#import "unittests.h" + +#import +#import +#import +#import + +using namespace VSTGUI::UnitTest; + +static std::string currentFile; +static std::string currentLineNo; + +//------------------------------------------------------------------------ +void setupFunc (id self, SEL _cmd) +{ + auto cls = [self class]; + std::string clsName (class_getName (cls) + 7); + auto it = + std::find_if (UnitTestRegistry::instance ().begin (), UnitTestRegistry::instance ().end (), + [&] (auto& testCase) { return testCase.getName () == clsName; }); + assert (it != UnitTestRegistry::instance ().end ()); + auto ivar = class_getInstanceVariable ([self class], "_testCase"); + object_setIvar (self, ivar, reinterpret_cast (&(*it))); +} + +//------------------------------------------------------------------------ +void testFunc (id self, SEL _cmd) +{ + auto ivar = class_getInstanceVariable ([self class], "_testCase"); + auto testCase = reinterpret_cast (object_getIvar (self, ivar)); + auto name = std::string (sel_getName (_cmd) + 4); + auto it = std::find_if (testCase->begin (), testCase->end (), + [&] (auto& pair) { return pair.first == name; }); + if (it == testCase->end ()) + { + + return; + } + struct TestContext : Context + { + void printRaw (const char* str) override { text += str; } + std::string text; + } context; + + if (auto setup = testCase->setup ()) + setup (&context); + + XCTSourceCodeLocation* sourceCodeLocation = nullptr; + bool result; + try + { + result = it->second (&context); + } + catch (const error& exc) + { + result = false; + context.print ("%s", exc.what () ? exc.what () : "unknown"); + sourceCodeLocation = [[[XCTSourceCodeLocation alloc] + initWithFilePath:[NSString stringWithUTF8String:exc.filePath.data ()] + lineNumber:exc.lineNo] autorelease]; + } + catch (const std::logic_error& exc) + { + result = false; + context.print ("%s", exc.what () ? exc.what () : "unknown"); + } + catch (const std::exception& exc) + { + result = false; + context.print ("Exception: %s", exc.what () ? exc.what () : "unknown"); + } + if (!result) + { + if (!sourceCodeLocation) + { + auto lineNo = VSTGUI::UTF8StringView (currentLineNo.data ()).toInteger (); + sourceCodeLocation = [[[XCTSourceCodeLocation alloc] + initWithFilePath:[NSString stringWithUTF8String:currentFile.data ()] + lineNumber:lineNo] autorelease]; + } + auto sourceCodeContext = + [[[XCTSourceCodeContext alloc] initWithLocation:sourceCodeLocation] autorelease]; + auto issue = + [[[XCTIssue alloc] initWithType:XCTIssueTypeAssertionFailure + compactDescription:[NSString stringWithUTF8String:context.text.data ()] + detailedDescription:nullptr + sourceCodeContext:sourceCodeContext + associatedError:nullptr + attachments:@[]] autorelease]; + + [self recordIssue:issue]; + } + + if (auto teardown = testCase->teardown ()) + teardown (&context); +} + +//------------------------------------------------------------------------ +@interface VSTGUITestCaseFactory : XCTestCase +@end + +@implementation VSTGUITestCaseFactory + +//------------------------------------------------------------------------ ++ (void)initialize +{ + VSTGUI::init (CFBundleGetMainBundle ()); + VSTGUI::setAssertionHandler ([] (const char* file, const char* line, const char* desc) { + if (desc) + throw std::logic_error (desc); + currentFile = file; + currentLineNo = line; + std::string text = "assert: "; + text += file; + text += ":"; + text += line; + throw std::logic_error (text.data ()); + }); + + auto baseClass = objc_getClass ("XCTestCase"); + for (auto& testCase : UnitTestRegistry::instance ()) + { + std::string className = "VSTGUI_" + testCase.getName (); + auto cls = objc_allocateClassPair (baseClass, className.data (), 0); + if (!cls) + { + NSLog (@"Duplicate TestCase name: %s", testCase.getName ().data ()); + continue; + } + class_addIvar (cls, "_testCase", sizeof (void*), (uint8_t)log2 (sizeof (void*)), + @encode (void*)); + class_addMethod (cls, @selector (setUp), reinterpret_cast (setupFunc), "v@:"); + for (auto& test : testCase) + { + std::string name = "test" + test.first; + name[4] = toupper(name[4]); + auto sel = sel_registerName (name.data ()); + if (!class_addMethod (cls, sel, reinterpret_cast (testFunc), "v@:")) + { + NSLog (@"Duplicate Method name: %s [%s]", name.data (), className.data ()); + } + } + objc_registerClassPair (cls); + } +} + +@end diff --git a/vstgui/uidescription/uiattributes.cpp b/vstgui/uidescription/uiattributes.cpp index 28267f960..35201d2ea 100644 --- a/vstgui/uidescription/uiattributes.cpp +++ b/vstgui/uidescription/uiattributes.cpp @@ -196,6 +196,9 @@ bool UIAttributes::stringToRect (const std::string& str, CRect& r) //----------------------------------------------------------------------------- std::string UIAttributes::stringArrayToString (const StringArray& values) { + if (values.empty()) + return {}; + std::string value; size_t numValues = values.size (); for (size_t i = 0; i < numValues - 1; i++)