@@ -0,0 +1,26 @@ | |||||
/* | |||||
* Carla Juce setup | |||||
* Copyright (C) 2013-2014 Filipe Coelho <falktx@falktx.com> | |||||
* | |||||
* This program is free software; you can redistribute it and/or | |||||
* modify it under the terms of the GNU General Public License as | |||||
* published by the Free Software Foundation; either version 2 of | |||||
* the License, or any later version. | |||||
* | |||||
* This program is distributed in the hope that it will be useful, | |||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |||||
* GNU General Public License for more details. | |||||
* | |||||
* For a full copy of the GNU General Public License see the doc/GPL.txt file. | |||||
*/ | |||||
#ifndef CARLA_JUCE_GUI_EXTRA_H_INCLUDED | |||||
#define CARLA_JUCE_GUI_EXTRA_H_INCLUDED | |||||
#include "juce_gui_basics.h" | |||||
#include "juce_gui_extra/AppConfig.h" | |||||
#include "juce_gui_extra/juce_gui_extra.h" | |||||
#endif // CARLA_JUCE_GUI_EXTRA_H_INCLUDED |
@@ -0,0 +1,27 @@ | |||||
/* | |||||
============================================================================== | |||||
Build options for juce_gui_extra static library | |||||
============================================================================== | |||||
*/ | |||||
#ifndef CARLA_JUCE_GUI_EXTRA_APPCONFIG_H_INCLUDED | |||||
#define CARLA_JUCE_GUI_EXTRA_APPCONFIG_H_INCLUDED | |||||
#include "../juce_gui_basics/AppConfig.h" | |||||
//============================================================================= | |||||
/** Config: JUCE_WEB_BROWSER | |||||
This lets you disable the WebBrowserComponent class (Mac and Windows). | |||||
If you're not using any embedded web-pages, turning this off may reduce your code size. | |||||
*/ | |||||
#define JUCE_WEB_BROWSER 0 | |||||
/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR | |||||
This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support. See the documentation | |||||
for that macro for more details. | |||||
*/ | |||||
#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 0 | |||||
#endif // CARLA_JUCE_GUI_EXTRA_APPCONFIG_H_INCLUDED |
@@ -0,0 +1,96 @@ | |||||
#!/usr/bin/make -f | |||||
# Makefile for juce_gui_extra # | |||||
# --------------------------- # | |||||
# Created by falkTX | |||||
# | |||||
include ../../Makefile.mk | |||||
# -------------------------------------------------------------- | |||||
BUILD_CXX_FLAGS += $(JUCE_GUI_EXTRA_FLAGS) -I. -w | |||||
LINK_FLAGS += $(JUCE_GUI_EXTRA_LIBS) -L.. -ljuce_gui_basics | |||||
ifeq ($(MACOS),true) | |||||
BUILD_CXX_FLAGS += -ObjC++ | |||||
OBJS = juce_gui_extra.mm.o | |||||
OBJS_posix32 = juce_gui_extra.mm.posix32.o | |||||
OBJS_posix64 = juce_gui_extra.mm.posix64.o | |||||
else | |||||
OBJS = juce_gui_extra.cpp.o | |||||
OBJS_posix32 = juce_gui_extra.cpp.posix32.o | |||||
OBJS_posix64 = juce_gui_extra.cpp.posix64.o | |||||
endif | |||||
OBJS_win32 = juce_gui_extra.cpp.win32.o | |||||
OBJS_win64 = juce_gui_extra.cpp.win64.o | |||||
# -------------------------------------------------------------- | |||||
all: ../juce_gui_extra.a | |||||
posix32: ../juce_gui_extra.posix32.a | |||||
posix64: ../juce_gui_extra.posix64.a | |||||
win32: ../juce_gui_extra.win32.a | |||||
win64: ../juce_gui_extra.win64.a | |||||
# -------------------------------------------------------------- | |||||
../juce_gui_extra.a: $(OBJS) | |||||
$(RM) $@ | |||||
$(AR) crs $@ $^ | |||||
../juce_gui_extra.posix32.a: $(OBJS_posix32) | |||||
$(RM) $@ | |||||
$(AR) crs $@ $^ | |||||
../juce_gui_extra.posix64.a: $(OBJS_posix64) | |||||
$(RM) $@ | |||||
$(AR) crs $@ $^ | |||||
../juce_gui_extra.win32.a: $(OBJS_win32) | |||||
$(RM) $@ | |||||
$(AR) crs $@ $^ | |||||
../juce_gui_extra.win64.a: $(OBJS_win64) | |||||
$(RM) $@ | |||||
$(AR) crs $@ $^ | |||||
../libjuce_gui_extra.dll: $(OBJS) | |||||
$(CXX) $^ -shared $(LINK_FLAGS) -o $@ | |||||
../libjuce_gui_extra.dylib: $(OBJS) | |||||
$(CXX) $^ -dynamiclib $(LINK_FLAGS) -o $@ | |||||
../libjuce_gui_extra.so: $(OBJS) | |||||
$(CXX) $^ -shared $(LINK_FLAGS) -o $@ | |||||
# -------------------------------------------------------------- | |||||
%.cpp.o: %.cpp | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
%.mm.o: %.mm | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) -c -o $@ | |||||
%.posix32.o: % | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
%.posix64.o: % | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
%.win32.o: % | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) $(32BIT_FLAGS) -c -o $@ | |||||
%.win64.o: % | |||||
$(CXX) $< $(BUILD_CXX_FLAGS) $(64BIT_FLAGS) -c -o $@ | |||||
# -------------------------------------------------------------- | |||||
clean: | |||||
$(RM) *.o ../juce_gui_extra*.a ../libjuce_gui_extra.* | |||||
debug: | |||||
$(MAKE) DEBUG=true | |||||
# -------------------------------------------------------------- |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#include "juce_CPlusPlusCodeTokeniserFunctions.h" | |||||
//============================================================================== | |||||
CPlusPlusCodeTokeniser::CPlusPlusCodeTokeniser() {} | |||||
CPlusPlusCodeTokeniser::~CPlusPlusCodeTokeniser() {} | |||||
int CPlusPlusCodeTokeniser::readNextToken (CodeDocument::Iterator& source) | |||||
{ | |||||
return CppTokeniserFunctions::readNextToken (source); | |||||
} | |||||
CodeEditorComponent::ColourScheme CPlusPlusCodeTokeniser::getDefaultColourScheme() | |||||
{ | |||||
struct Type | |||||
{ | |||||
const char* name; | |||||
uint32 colour; | |||||
}; | |||||
const Type types[] = | |||||
{ | |||||
{ "Error", 0xffcc0000 }, | |||||
{ "Comment", 0xff00aa00 }, | |||||
{ "Keyword", 0xff0000cc }, | |||||
{ "Operator", 0xff225500 }, | |||||
{ "Identifier", 0xff000000 }, | |||||
{ "Integer", 0xff880000 }, | |||||
{ "Float", 0xff885500 }, | |||||
{ "String", 0xff990099 }, | |||||
{ "Bracket", 0xff000055 }, | |||||
{ "Punctuation", 0xff004400 }, | |||||
{ "Preprocessor Text", 0xff660000 } | |||||
}; | |||||
CodeEditorComponent::ColourScheme cs; | |||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2) | |||||
cs.set (types[i].name, Colour (types[i].colour)); | |||||
return cs; | |||||
} | |||||
bool CPlusPlusCodeTokeniser::isReservedKeyword (const String& token) noexcept | |||||
{ | |||||
return CppTokeniserFunctions::isReservedKeyword (token.getCharPointer(), token.length()); | |||||
} |
@@ -0,0 +1,71 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED | |||||
#define JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A simple lexical analyser for syntax colouring of C++ code. | |||||
@see CodeEditorComponent, CodeDocument | |||||
*/ | |||||
class JUCE_API CPlusPlusCodeTokeniser : public CodeTokeniser | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
CPlusPlusCodeTokeniser(); | |||||
~CPlusPlusCodeTokeniser(); | |||||
//============================================================================== | |||||
int readNextToken (CodeDocument::Iterator&) override; | |||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override; | |||||
/** This is a handy method for checking whether a string is a c++ reserved keyword. */ | |||||
static bool isReservedKeyword (const String& token) noexcept; | |||||
/** The token values returned by this tokeniser. */ | |||||
enum TokenType | |||||
{ | |||||
tokenType_error = 0, | |||||
tokenType_comment, | |||||
tokenType_keyword, | |||||
tokenType_operator, | |||||
tokenType_identifier, | |||||
tokenType_integer, | |||||
tokenType_float, | |||||
tokenType_string, | |||||
tokenType_bracket, | |||||
tokenType_punctuation, | |||||
tokenType_preprocessor | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_LEAK_DETECTOR (CPlusPlusCodeTokeniser) | |||||
}; | |||||
#endif // JUCE_CPLUSPLUSCODETOKENISER_H_INCLUDED |
@@ -0,0 +1,541 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED | |||||
#define JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED | |||||
//============================================================================== | |||||
/** Class containing some basic functions for simple tokenising of C++ code. | |||||
*/ | |||||
struct CppTokeniserFunctions | |||||
{ | |||||
static bool isIdentifierStart (const juce_wchar c) noexcept | |||||
{ | |||||
return CharacterFunctions::isLetter (c) | |||||
|| c == '_' || c == '@'; | |||||
} | |||||
static bool isIdentifierBody (const juce_wchar c) noexcept | |||||
{ | |||||
return CharacterFunctions::isLetterOrDigit (c) | |||||
|| c == '_' || c == '@'; | |||||
} | |||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept | |||||
{ | |||||
static const char* const keywords2Char[] = | |||||
{ "if", "do", "or", nullptr }; | |||||
static const char* const keywords3Char[] = | |||||
{ "for", "int", "new", "try", "xor", "and", "asm", "not", nullptr }; | |||||
static const char* const keywords4Char[] = | |||||
{ "bool", "void", "this", "true", "long", "else", "char", | |||||
"enum", "case", "goto", "auto", nullptr }; | |||||
static const char* const keywords5Char[] = | |||||
{ "float", "const", "while", "break", "false", "catch", "class", "bitor", | |||||
"compl", "or_eq", "short", "throw", "union", "using", "final", nullptr }; | |||||
static const char* const keywords6Char[] = | |||||
{ "return", "and_eq", "bitand", "delete", "double", "export", "extern", | |||||
"friend", "inline", "not_eq", "public", "signed", "sizeof", "static", | |||||
"struct", "switch", "typeid", "xor_eq", nullptr }; | |||||
static const char* const keywords7Char[] = | |||||
{ "nullptr", "alignas", "alignof", "default", "mutable", "private", | |||||
"typedef", "virtual", "wchar_t", nullptr }; | |||||
static const char* const keywordsOther[] = | |||||
{ "char16_t", "char32_t", "const_cast", "constexpr", "continue", "decltype", "dynamic_cast", | |||||
"explicit", "namespace", "noexcept", "operator", "protected", "register", "reinterpret_cast", | |||||
"static_assert", "static_cast", "template", "thread_local", "typename", "unsigned", "volatile", | |||||
"@class", "@dynamic", "@end", "@implementation", "@interface", "@public", "@private", | |||||
"@protected", "@property", "@synthesize", nullptr }; | |||||
const char* const* k; | |||||
switch (tokenLength) | |||||
{ | |||||
case 2: k = keywords2Char; break; | |||||
case 3: k = keywords3Char; break; | |||||
case 4: k = keywords4Char; break; | |||||
case 5: k = keywords5Char; break; | |||||
case 6: k = keywords6Char; break; | |||||
case 7: k = keywords7Char; break; | |||||
default: | |||||
if (tokenLength < 2 || tokenLength > 16) | |||||
return false; | |||||
k = keywordsOther; | |||||
break; | |||||
} | |||||
for (int i = 0; k[i] != 0; ++i) | |||||
if (token.compare (CharPointer_ASCII (k[i])) == 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
template <typename Iterator> | |||||
static int parseIdentifier (Iterator& source) noexcept | |||||
{ | |||||
int tokenLength = 0; | |||||
String::CharPointerType::CharType possibleIdentifier [100]; | |||||
String::CharPointerType possible (possibleIdentifier); | |||||
while (isIdentifierBody (source.peekNextChar())) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (tokenLength < 20) | |||||
possible.write (c); | |||||
++tokenLength; | |||||
} | |||||
if (tokenLength > 1 && tokenLength <= 16) | |||||
{ | |||||
possible.writeNull(); | |||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength)) | |||||
return CPlusPlusCodeTokeniser::tokenType_keyword; | |||||
} | |||||
return CPlusPlusCodeTokeniser::tokenType_identifier; | |||||
} | |||||
template <typename Iterator> | |||||
static bool skipNumberSuffix (Iterator& source) | |||||
{ | |||||
const juce_wchar c = source.peekNextChar(); | |||||
if (c == 'l' || c == 'L' || c == 'u' || c == 'U') | |||||
source.skip(); | |||||
if (CharacterFunctions::isLetterOrDigit (source.peekNextChar())) | |||||
return false; | |||||
return true; | |||||
} | |||||
static bool isHexDigit (const juce_wchar c) noexcept | |||||
{ | |||||
return (c >= '0' && c <= '9') | |||||
|| (c >= 'a' && c <= 'f') | |||||
|| (c >= 'A' && c <= 'F'); | |||||
} | |||||
template <typename Iterator> | |||||
static bool parseHexLiteral (Iterator& source) noexcept | |||||
{ | |||||
if (source.peekNextChar() == '-') | |||||
source.skip(); | |||||
if (source.nextChar() != '0') | |||||
return false; | |||||
juce_wchar c = source.nextChar(); | |||||
if (c != 'x' && c != 'X') | |||||
return false; | |||||
int numDigits = 0; | |||||
while (isHexDigit (source.peekNextChar())) | |||||
{ | |||||
++numDigits; | |||||
source.skip(); | |||||
} | |||||
if (numDigits == 0) | |||||
return false; | |||||
return skipNumberSuffix (source); | |||||
} | |||||
static bool isOctalDigit (const juce_wchar c) noexcept | |||||
{ | |||||
return c >= '0' && c <= '7'; | |||||
} | |||||
template <typename Iterator> | |||||
static bool parseOctalLiteral (Iterator& source) noexcept | |||||
{ | |||||
if (source.peekNextChar() == '-') | |||||
source.skip(); | |||||
if (source.nextChar() != '0') | |||||
return false; | |||||
if (! isOctalDigit (source.nextChar())) | |||||
return false; | |||||
while (isOctalDigit (source.peekNextChar())) | |||||
source.skip(); | |||||
return skipNumberSuffix (source); | |||||
} | |||||
static bool isDecimalDigit (const juce_wchar c) noexcept | |||||
{ | |||||
return c >= '0' && c <= '9'; | |||||
} | |||||
template <typename Iterator> | |||||
static bool parseDecimalLiteral (Iterator& source) noexcept | |||||
{ | |||||
if (source.peekNextChar() == '-') | |||||
source.skip(); | |||||
int numChars = 0; | |||||
while (isDecimalDigit (source.peekNextChar())) | |||||
{ | |||||
++numChars; | |||||
source.skip(); | |||||
} | |||||
if (numChars == 0) | |||||
return false; | |||||
return skipNumberSuffix (source); | |||||
} | |||||
template <typename Iterator> | |||||
static bool parseFloatLiteral (Iterator& source) noexcept | |||||
{ | |||||
if (source.peekNextChar() == '-') | |||||
source.skip(); | |||||
int numDigits = 0; | |||||
while (isDecimalDigit (source.peekNextChar())) | |||||
{ | |||||
source.skip(); | |||||
++numDigits; | |||||
} | |||||
const bool hasPoint = (source.peekNextChar() == '.'); | |||||
if (hasPoint) | |||||
{ | |||||
source.skip(); | |||||
while (isDecimalDigit (source.peekNextChar())) | |||||
{ | |||||
source.skip(); | |||||
++numDigits; | |||||
} | |||||
} | |||||
if (numDigits == 0) | |||||
return false; | |||||
juce_wchar c = source.peekNextChar(); | |||||
const bool hasExponent = (c == 'e' || c == 'E'); | |||||
if (hasExponent) | |||||
{ | |||||
source.skip(); | |||||
c = source.peekNextChar(); | |||||
if (c == '+' || c == '-') | |||||
source.skip(); | |||||
int numExpDigits = 0; | |||||
while (isDecimalDigit (source.peekNextChar())) | |||||
{ | |||||
source.skip(); | |||||
++numExpDigits; | |||||
} | |||||
if (numExpDigits == 0) | |||||
return false; | |||||
} | |||||
c = source.peekNextChar(); | |||||
if (c == 'f' || c == 'F') | |||||
source.skip(); | |||||
else if (! (hasExponent || hasPoint)) | |||||
return false; | |||||
return true; | |||||
} | |||||
template <typename Iterator> | |||||
static int parseNumber (Iterator& source) | |||||
{ | |||||
const Iterator original (source); | |||||
if (parseFloatLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_float; | |||||
source = original; | |||||
if (parseHexLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer; | |||||
source = original; | |||||
if (parseOctalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer; | |||||
source = original; | |||||
if (parseDecimalLiteral (source)) return CPlusPlusCodeTokeniser::tokenType_integer; | |||||
source = original; | |||||
return CPlusPlusCodeTokeniser::tokenType_error; | |||||
} | |||||
template <typename Iterator> | |||||
static void skipQuotedString (Iterator& source) noexcept | |||||
{ | |||||
const juce_wchar quote = source.nextChar(); | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (c == quote || c == 0) | |||||
break; | |||||
if (c == '\\') | |||||
source.skip(); | |||||
} | |||||
} | |||||
template <typename Iterator> | |||||
static void skipComment (Iterator& source) noexcept | |||||
{ | |||||
bool lastWasStar = false; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (c == 0 || (c == '/' && lastWasStar)) | |||||
break; | |||||
lastWasStar = (c == '*'); | |||||
} | |||||
} | |||||
template <typename Iterator> | |||||
static void skipPreprocessorLine (Iterator& source) noexcept | |||||
{ | |||||
bool lastWasBackslash = false; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = source.peekNextChar(); | |||||
if (c == '"') | |||||
{ | |||||
skipQuotedString (source); | |||||
continue; | |||||
} | |||||
if (c == '/') | |||||
{ | |||||
Iterator next (source); | |||||
next.skip(); | |||||
const juce_wchar c2 = next.peekNextChar(); | |||||
if (c2 == '/' || c2 == '*') | |||||
return; | |||||
} | |||||
if (c == 0) | |||||
break; | |||||
if (c == '\n' || c == '\r') | |||||
{ | |||||
source.skipToEndOfLine(); | |||||
if (lastWasBackslash) | |||||
skipPreprocessorLine (source); | |||||
break; | |||||
} | |||||
lastWasBackslash = (c == '\\'); | |||||
source.skip(); | |||||
} | |||||
} | |||||
template <typename Iterator> | |||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c) noexcept | |||||
{ | |||||
if (source.peekNextChar() == c) | |||||
source.skip(); | |||||
} | |||||
template <typename Iterator> | |||||
static void skipIfNextCharMatches (Iterator& source, const juce_wchar c1, const juce_wchar c2) noexcept | |||||
{ | |||||
const juce_wchar c = source.peekNextChar(); | |||||
if (c == c1 || c == c2) | |||||
source.skip(); | |||||
} | |||||
template <typename Iterator> | |||||
static int readNextToken (Iterator& source) | |||||
{ | |||||
source.skipWhitespace(); | |||||
const juce_wchar firstChar = source.peekNextChar(); | |||||
switch (firstChar) | |||||
{ | |||||
case 0: | |||||
break; | |||||
case '0': case '1': case '2': case '3': case '4': | |||||
case '5': case '6': case '7': case '8': case '9': | |||||
case '.': | |||||
{ | |||||
int result = parseNumber (source); | |||||
if (result == CPlusPlusCodeTokeniser::tokenType_error) | |||||
{ | |||||
source.skip(); | |||||
if (firstChar == '.') | |||||
return CPlusPlusCodeTokeniser::tokenType_punctuation; | |||||
} | |||||
return result; | |||||
} | |||||
case ',': | |||||
case ';': | |||||
case ':': | |||||
source.skip(); | |||||
return CPlusPlusCodeTokeniser::tokenType_punctuation; | |||||
case '(': case ')': | |||||
case '{': case '}': | |||||
case '[': case ']': | |||||
source.skip(); | |||||
return CPlusPlusCodeTokeniser::tokenType_bracket; | |||||
case '"': | |||||
case '\'': | |||||
skipQuotedString (source); | |||||
return CPlusPlusCodeTokeniser::tokenType_string; | |||||
case '+': | |||||
source.skip(); | |||||
skipIfNextCharMatches (source, '+', '='); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
case '-': | |||||
{ | |||||
source.skip(); | |||||
int result = parseNumber (source); | |||||
if (result == CPlusPlusCodeTokeniser::tokenType_error) | |||||
{ | |||||
skipIfNextCharMatches (source, '-', '='); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
} | |||||
return result; | |||||
} | |||||
case '*': case '%': | |||||
case '=': case '!': | |||||
source.skip(); | |||||
skipIfNextCharMatches (source, '='); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
case '/': | |||||
{ | |||||
source.skip(); | |||||
juce_wchar nextChar = source.peekNextChar(); | |||||
if (nextChar == '/') | |||||
{ | |||||
source.skipToEndOfLine(); | |||||
return CPlusPlusCodeTokeniser::tokenType_comment; | |||||
} | |||||
if (nextChar == '*') | |||||
{ | |||||
source.skip(); | |||||
skipComment (source); | |||||
return CPlusPlusCodeTokeniser::tokenType_comment; | |||||
} | |||||
if (nextChar == '=') | |||||
source.skip(); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
} | |||||
case '?': | |||||
case '~': | |||||
source.skip(); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
case '<': case '>': | |||||
case '|': case '&': case '^': | |||||
source.skip(); | |||||
skipIfNextCharMatches (source, firstChar); | |||||
skipIfNextCharMatches (source, '='); | |||||
return CPlusPlusCodeTokeniser::tokenType_operator; | |||||
case '#': | |||||
skipPreprocessorLine (source); | |||||
return CPlusPlusCodeTokeniser::tokenType_preprocessor; | |||||
default: | |||||
if (isIdentifierStart (firstChar)) | |||||
return parseIdentifier (source); | |||||
source.skip(); | |||||
break; | |||||
} | |||||
return CPlusPlusCodeTokeniser::tokenType_error; | |||||
} | |||||
/** A class that can be passed to the CppTokeniserFunctions functions in order to | |||||
parse a String. | |||||
*/ | |||||
struct StringIterator | |||||
{ | |||||
StringIterator (const String& s) noexcept : t (s.getCharPointer()), numChars (0) {} | |||||
StringIterator (String::CharPointerType s) noexcept : t (s), numChars (0) {} | |||||
juce_wchar nextChar() noexcept { if (isEOF()) return 0; ++numChars; return t.getAndAdvance(); } | |||||
juce_wchar peekNextChar()noexcept { return *t; } | |||||
void skip() noexcept { if (! isEOF()) { ++t; ++numChars; } } | |||||
void skipWhitespace() noexcept { while (t.isWhitespace()) skip(); } | |||||
void skipToEndOfLine() noexcept { while (*t != '\r' && *t != '\n' && *t != 0) skip(); } | |||||
bool isEOF() const noexcept { return t.isEmpty(); } | |||||
String::CharPointerType t; | |||||
int numChars; | |||||
}; | |||||
}; | |||||
#endif // JUCE_CPLUSPLUSCODETOKENISERFUNCTIONS_H_INCLUDED |
@@ -0,0 +1,992 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class CodeDocumentLine | |||||
{ | |||||
public: | |||||
CodeDocumentLine (const String::CharPointerType startOfLine, | |||||
const String::CharPointerType endOfLine, | |||||
const int lineLen, | |||||
const int numNewLineChars, | |||||
const int startInFile) | |||||
: line (startOfLine, endOfLine), | |||||
lineStartInFile (startInFile), | |||||
lineLength (lineLen), | |||||
lineLengthWithoutNewLines (lineLen - numNewLineChars) | |||||
{ | |||||
} | |||||
static void createLines (Array<CodeDocumentLine*>& newLines, StringRef text) | |||||
{ | |||||
String::CharPointerType t (text.text); | |||||
int charNumInFile = 0; | |||||
bool finished = false; | |||||
while (! (finished || t.isEmpty())) | |||||
{ | |||||
String::CharPointerType startOfLine (t); | |||||
int startOfLineInFile = charNumInFile; | |||||
int lineLength = 0; | |||||
int numNewLineChars = 0; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = t.getAndAdvance(); | |||||
if (c == 0) | |||||
{ | |||||
finished = true; | |||||
break; | |||||
} | |||||
++charNumInFile; | |||||
++lineLength; | |||||
if (c == '\r') | |||||
{ | |||||
++numNewLineChars; | |||||
if (*t == '\n') | |||||
{ | |||||
++t; | |||||
++charNumInFile; | |||||
++lineLength; | |||||
++numNewLineChars; | |||||
} | |||||
break; | |||||
} | |||||
if (c == '\n') | |||||
{ | |||||
++numNewLineChars; | |||||
break; | |||||
} | |||||
} | |||||
newLines.add (new CodeDocumentLine (startOfLine, t, lineLength, | |||||
numNewLineChars, startOfLineInFile)); | |||||
} | |||||
jassert (charNumInFile == text.length()); | |||||
} | |||||
bool endsWithLineBreak() const noexcept | |||||
{ | |||||
return lineLengthWithoutNewLines != lineLength; | |||||
} | |||||
void updateLength() noexcept | |||||
{ | |||||
lineLength = 0; | |||||
lineLengthWithoutNewLines = 0; | |||||
String::CharPointerType t (line.getCharPointer()); | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = t.getAndAdvance(); | |||||
if (c == 0) | |||||
break; | |||||
++lineLength; | |||||
if (c != '\n' && c != '\r') | |||||
lineLengthWithoutNewLines = lineLength; | |||||
} | |||||
} | |||||
String line; | |||||
int lineStartInFile, lineLength, lineLengthWithoutNewLines; | |||||
}; | |||||
//============================================================================== | |||||
CodeDocument::Iterator::Iterator (const CodeDocument& doc) noexcept | |||||
: document (&doc), | |||||
charPointer (nullptr), | |||||
line (0), | |||||
position (0) | |||||
{ | |||||
} | |||||
CodeDocument::Iterator::Iterator (const CodeDocument::Iterator& other) noexcept | |||||
: document (other.document), | |||||
charPointer (other.charPointer), | |||||
line (other.line), | |||||
position (other.position) | |||||
{ | |||||
} | |||||
CodeDocument::Iterator& CodeDocument::Iterator::operator= (const CodeDocument::Iterator& other) noexcept | |||||
{ | |||||
document = other.document; | |||||
charPointer = other.charPointer; | |||||
line = other.line; | |||||
position = other.position; | |||||
return *this; | |||||
} | |||||
CodeDocument::Iterator::~Iterator() noexcept | |||||
{ | |||||
} | |||||
juce_wchar CodeDocument::Iterator::nextChar() noexcept | |||||
{ | |||||
for (;;) | |||||
{ | |||||
if (charPointer.getAddress() == nullptr) | |||||
{ | |||||
if (const CodeDocumentLine* const l = document->lines[line]) | |||||
charPointer = l->line.getCharPointer(); | |||||
else | |||||
return 0; | |||||
} | |||||
const juce_wchar result = charPointer.getAndAdvance(); | |||||
if (result == 0) | |||||
{ | |||||
++line; | |||||
charPointer = nullptr; | |||||
} | |||||
else | |||||
{ | |||||
++position; | |||||
return result; | |||||
} | |||||
} | |||||
} | |||||
void CodeDocument::Iterator::skip() noexcept | |||||
{ | |||||
nextChar(); | |||||
} | |||||
void CodeDocument::Iterator::skipToEndOfLine() noexcept | |||||
{ | |||||
if (charPointer.getAddress() == nullptr) | |||||
{ | |||||
const CodeDocumentLine* const l = document->lines[line]; | |||||
if (l == nullptr) | |||||
return; | |||||
charPointer = l->line.getCharPointer(); | |||||
} | |||||
position += (int) charPointer.length(); | |||||
++line; | |||||
charPointer = nullptr; | |||||
} | |||||
juce_wchar CodeDocument::Iterator::peekNextChar() const noexcept | |||||
{ | |||||
if (charPointer.getAddress() == nullptr) | |||||
{ | |||||
if (const CodeDocumentLine* const l = document->lines[line]) | |||||
charPointer = l->line.getCharPointer(); | |||||
else | |||||
return 0; | |||||
} | |||||
const juce_wchar c = *charPointer; | |||||
if (c != 0) | |||||
return c; | |||||
if (const CodeDocumentLine* const l = document->lines [line + 1]) | |||||
return l->line[0]; | |||||
return 0; | |||||
} | |||||
void CodeDocument::Iterator::skipWhitespace() noexcept | |||||
{ | |||||
while (CharacterFunctions::isWhitespace (peekNextChar())) | |||||
skip(); | |||||
} | |||||
bool CodeDocument::Iterator::isEOF() const noexcept | |||||
{ | |||||
return charPointer.getAddress() == nullptr && line >= document->lines.size(); | |||||
} | |||||
//============================================================================== | |||||
CodeDocument::Position::Position() noexcept | |||||
: owner (nullptr), characterPos (0), line (0), | |||||
indexInLine (0), positionMaintained (false) | |||||
{ | |||||
} | |||||
CodeDocument::Position::Position (const CodeDocument& ownerDocument, | |||||
const int line_, const int indexInLine_) noexcept | |||||
: owner (const_cast <CodeDocument*> (&ownerDocument)), | |||||
characterPos (0), line (line_), | |||||
indexInLine (indexInLine_), positionMaintained (false) | |||||
{ | |||||
setLineAndIndex (line_, indexInLine_); | |||||
} | |||||
CodeDocument::Position::Position (const CodeDocument& ownerDocument, const int characterPos_) noexcept | |||||
: owner (const_cast <CodeDocument*> (&ownerDocument)), | |||||
positionMaintained (false) | |||||
{ | |||||
setPosition (characterPos_); | |||||
} | |||||
CodeDocument::Position::Position (const Position& other) noexcept | |||||
: owner (other.owner), characterPos (other.characterPos), line (other.line), | |||||
indexInLine (other.indexInLine), positionMaintained (false) | |||||
{ | |||||
jassert (*this == other); | |||||
} | |||||
CodeDocument::Position::~Position() | |||||
{ | |||||
setPositionMaintained (false); | |||||
} | |||||
CodeDocument::Position& CodeDocument::Position::operator= (const Position& other) | |||||
{ | |||||
if (this != &other) | |||||
{ | |||||
const bool wasPositionMaintained = positionMaintained; | |||||
if (owner != other.owner) | |||||
setPositionMaintained (false); | |||||
owner = other.owner; | |||||
line = other.line; | |||||
indexInLine = other.indexInLine; | |||||
characterPos = other.characterPos; | |||||
setPositionMaintained (wasPositionMaintained); | |||||
jassert (*this == other); | |||||
} | |||||
return *this; | |||||
} | |||||
bool CodeDocument::Position::operator== (const Position& other) const noexcept | |||||
{ | |||||
jassert ((characterPos == other.characterPos) | |||||
== (line == other.line && indexInLine == other.indexInLine)); | |||||
return characterPos == other.characterPos | |||||
&& line == other.line | |||||
&& indexInLine == other.indexInLine | |||||
&& owner == other.owner; | |||||
} | |||||
bool CodeDocument::Position::operator!= (const Position& other) const noexcept | |||||
{ | |||||
return ! operator== (other); | |||||
} | |||||
void CodeDocument::Position::setLineAndIndex (const int newLineNum, const int newIndexInLine) | |||||
{ | |||||
jassert (owner != nullptr); | |||||
if (owner->lines.size() == 0) | |||||
{ | |||||
line = 0; | |||||
indexInLine = 0; | |||||
characterPos = 0; | |||||
} | |||||
else | |||||
{ | |||||
if (newLineNum >= owner->lines.size()) | |||||
{ | |||||
line = owner->lines.size() - 1; | |||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line); | |||||
indexInLine = l.lineLengthWithoutNewLines; | |||||
characterPos = l.lineStartInFile + indexInLine; | |||||
} | |||||
else | |||||
{ | |||||
line = jmax (0, newLineNum); | |||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line); | |||||
if (l.lineLengthWithoutNewLines > 0) | |||||
indexInLine = jlimit (0, l.lineLengthWithoutNewLines, newIndexInLine); | |||||
else | |||||
indexInLine = 0; | |||||
characterPos = l.lineStartInFile + indexInLine; | |||||
} | |||||
} | |||||
} | |||||
void CodeDocument::Position::setPosition (const int newPosition) | |||||
{ | |||||
jassert (owner != nullptr); | |||||
line = 0; | |||||
indexInLine = 0; | |||||
characterPos = 0; | |||||
if (newPosition > 0) | |||||
{ | |||||
int lineStart = 0; | |||||
int lineEnd = owner->lines.size(); | |||||
for (;;) | |||||
{ | |||||
if (lineEnd - lineStart < 4) | |||||
{ | |||||
for (int i = lineStart; i < lineEnd; ++i) | |||||
{ | |||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (i); | |||||
const int index = newPosition - l.lineStartInFile; | |||||
if (index >= 0 && (index < l.lineLength || i == lineEnd - 1)) | |||||
{ | |||||
line = i; | |||||
indexInLine = jmin (l.lineLengthWithoutNewLines, index); | |||||
characterPos = l.lineStartInFile + indexInLine; | |||||
} | |||||
} | |||||
break; | |||||
} | |||||
else | |||||
{ | |||||
const int midIndex = (lineStart + lineEnd + 1) / 2; | |||||
if (newPosition >= owner->lines.getUnchecked (midIndex)->lineStartInFile) | |||||
lineStart = midIndex; | |||||
else | |||||
lineEnd = midIndex; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void CodeDocument::Position::moveBy (int characterDelta) | |||||
{ | |||||
jassert (owner != nullptr); | |||||
if (characterDelta == 1) | |||||
{ | |||||
setPosition (getPosition()); | |||||
// If moving right, make sure we don't get stuck between the \r and \n characters.. | |||||
if (line < owner->lines.size()) | |||||
{ | |||||
const CodeDocumentLine& l = *owner->lines.getUnchecked (line); | |||||
if (indexInLine + characterDelta < l.lineLength | |||||
&& indexInLine + characterDelta >= l.lineLengthWithoutNewLines + 1) | |||||
++characterDelta; | |||||
} | |||||
} | |||||
setPosition (characterPos + characterDelta); | |||||
} | |||||
CodeDocument::Position CodeDocument::Position::movedBy (const int characterDelta) const | |||||
{ | |||||
CodeDocument::Position p (*this); | |||||
p.moveBy (characterDelta); | |||||
return p; | |||||
} | |||||
CodeDocument::Position CodeDocument::Position::movedByLines (const int deltaLines) const | |||||
{ | |||||
CodeDocument::Position p (*this); | |||||
p.setLineAndIndex (getLineNumber() + deltaLines, getIndexInLine()); | |||||
return p; | |||||
} | |||||
juce_wchar CodeDocument::Position::getCharacter() const | |||||
{ | |||||
if (const CodeDocumentLine* const l = owner->lines [line]) | |||||
return l->line [getIndexInLine()]; | |||||
return 0; | |||||
} | |||||
String CodeDocument::Position::getLineText() const | |||||
{ | |||||
if (const CodeDocumentLine* const l = owner->lines [line]) | |||||
return l->line; | |||||
return String::empty; | |||||
} | |||||
void CodeDocument::Position::setPositionMaintained (const bool isMaintained) | |||||
{ | |||||
if (isMaintained != positionMaintained) | |||||
{ | |||||
positionMaintained = isMaintained; | |||||
if (owner != nullptr) | |||||
{ | |||||
if (isMaintained) | |||||
{ | |||||
jassert (! owner->positionsToMaintain.contains (this)); | |||||
owner->positionsToMaintain.add (this); | |||||
} | |||||
else | |||||
{ | |||||
// If this happens, you may have deleted the document while there are Position objects that are still using it... | |||||
jassert (owner->positionsToMaintain.contains (this)); | |||||
owner->positionsToMaintain.removeFirstMatchingValue (this); | |||||
} | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
CodeDocument::CodeDocument() | |||||
: undoManager (std::numeric_limits<int>::max(), 10000), | |||||
currentActionIndex (0), | |||||
indexOfSavedState (-1), | |||||
maximumLineLength (-1), | |||||
newLineChars ("\r\n") | |||||
{ | |||||
} | |||||
CodeDocument::~CodeDocument() | |||||
{ | |||||
} | |||||
String CodeDocument::getAllContent() const | |||||
{ | |||||
return getTextBetween (Position (*this, 0), | |||||
Position (*this, lines.size(), 0)); | |||||
} | |||||
String CodeDocument::getTextBetween (const Position& start, const Position& end) const | |||||
{ | |||||
if (end.getPosition() <= start.getPosition()) | |||||
return String::empty; | |||||
const int startLine = start.getLineNumber(); | |||||
const int endLine = end.getLineNumber(); | |||||
if (startLine == endLine) | |||||
{ | |||||
if (CodeDocumentLine* const line = lines [startLine]) | |||||
return line->line.substring (start.getIndexInLine(), end.getIndexInLine()); | |||||
return String::empty; | |||||
} | |||||
MemoryOutputStream mo; | |||||
mo.preallocate ((size_t) (end.getPosition() - start.getPosition() + 4)); | |||||
const int maxLine = jmin (lines.size() - 1, endLine); | |||||
for (int i = jmax (0, startLine); i <= maxLine; ++i) | |||||
{ | |||||
const CodeDocumentLine& line = *lines.getUnchecked(i); | |||||
int len = line.lineLength; | |||||
if (i == startLine) | |||||
{ | |||||
const int index = start.getIndexInLine(); | |||||
mo << line.line.substring (index, len); | |||||
} | |||||
else if (i == endLine) | |||||
{ | |||||
len = end.getIndexInLine(); | |||||
mo << line.line.substring (0, len); | |||||
} | |||||
else | |||||
{ | |||||
mo << line.line; | |||||
} | |||||
} | |||||
return mo.toUTF8(); | |||||
} | |||||
int CodeDocument::getNumCharacters() const noexcept | |||||
{ | |||||
if (const CodeDocumentLine* const lastLine = lines.getLast()) | |||||
return lastLine->lineStartInFile + lastLine->lineLength; | |||||
return 0; | |||||
} | |||||
String CodeDocument::getLine (const int lineIndex) const noexcept | |||||
{ | |||||
if (const CodeDocumentLine* const line = lines [lineIndex]) | |||||
return line->line; | |||||
return String::empty; | |||||
} | |||||
int CodeDocument::getMaximumLineLength() noexcept | |||||
{ | |||||
if (maximumLineLength < 0) | |||||
{ | |||||
maximumLineLength = 0; | |||||
for (int i = lines.size(); --i >= 0;) | |||||
maximumLineLength = jmax (maximumLineLength, lines.getUnchecked(i)->lineLength); | |||||
} | |||||
return maximumLineLength; | |||||
} | |||||
void CodeDocument::deleteSection (const Position& startPosition, const Position& endPosition) | |||||
{ | |||||
deleteSection (startPosition.getPosition(), endPosition.getPosition()); | |||||
} | |||||
void CodeDocument::deleteSection (const int start, const int end) | |||||
{ | |||||
remove (start, end, true); | |||||
} | |||||
void CodeDocument::insertText (const Position& position, const String& text) | |||||
{ | |||||
insertText (position.getPosition(), text); | |||||
} | |||||
void CodeDocument::insertText (const int insertIndex, const String& text) | |||||
{ | |||||
insert (text, insertIndex, true); | |||||
} | |||||
void CodeDocument::replaceSection (const int start, const int end, const String& newText) | |||||
{ | |||||
insertText (end, newText); | |||||
deleteSection (start, end); | |||||
} | |||||
void CodeDocument::applyChanges (const String& newContent) | |||||
{ | |||||
const String corrected (StringArray::fromLines (newContent) | |||||
.joinIntoString (newLineChars)); | |||||
TextDiff diff (getAllContent(), corrected); | |||||
for (int i = 0; i < diff.changes.size(); ++i) | |||||
{ | |||||
const TextDiff::Change& c = diff.changes.getReference(i); | |||||
if (c.isDeletion()) | |||||
remove (c.start, c.start + c.length, true); | |||||
else | |||||
insert (c.insertedText, c.start, true); | |||||
} | |||||
} | |||||
void CodeDocument::replaceAllContent (const String& newContent) | |||||
{ | |||||
remove (0, getNumCharacters(), true); | |||||
insert (newContent, 0, true); | |||||
} | |||||
bool CodeDocument::loadFromStream (InputStream& stream) | |||||
{ | |||||
remove (0, getNumCharacters(), false); | |||||
insert (stream.readEntireStreamAsString(), 0, false); | |||||
setSavePoint(); | |||||
clearUndoHistory(); | |||||
return true; | |||||
} | |||||
bool CodeDocument::writeToStream (OutputStream& stream) | |||||
{ | |||||
for (int i = 0; i < lines.size(); ++i) | |||||
{ | |||||
String temp (lines.getUnchecked(i)->line); // use a copy to avoid bloating the memory footprint of the stored string. | |||||
const char* utf8 = temp.toUTF8(); | |||||
if (! stream.write (utf8, strlen (utf8))) | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
void CodeDocument::setNewLineCharacters (const String& newChars) noexcept | |||||
{ | |||||
jassert (newChars == "\r\n" || newChars == "\n" || newChars == "\r"); | |||||
newLineChars = newChars; | |||||
} | |||||
void CodeDocument::newTransaction() | |||||
{ | |||||
undoManager.beginNewTransaction (String::empty); | |||||
} | |||||
void CodeDocument::undo() | |||||
{ | |||||
newTransaction(); | |||||
undoManager.undo(); | |||||
} | |||||
void CodeDocument::redo() | |||||
{ | |||||
undoManager.redo(); | |||||
} | |||||
void CodeDocument::clearUndoHistory() | |||||
{ | |||||
undoManager.clearUndoHistory(); | |||||
} | |||||
void CodeDocument::setSavePoint() noexcept | |||||
{ | |||||
indexOfSavedState = currentActionIndex; | |||||
} | |||||
bool CodeDocument::hasChangedSinceSavePoint() const noexcept | |||||
{ | |||||
return currentActionIndex != indexOfSavedState; | |||||
} | |||||
//============================================================================== | |||||
namespace CodeDocumentHelpers | |||||
{ | |||||
static int getCharacterType (const juce_wchar character) noexcept | |||||
{ | |||||
return (CharacterFunctions::isLetterOrDigit (character) || character == '_') | |||||
? 2 : (CharacterFunctions::isWhitespace (character) ? 0 : 1); | |||||
} | |||||
static bool isTokenCharacter (const juce_wchar c) noexcept | |||||
{ | |||||
return CharacterFunctions::isLetterOrDigit (c) || c == '.' || c == '_'; | |||||
} | |||||
} | |||||
CodeDocument::Position CodeDocument::findWordBreakAfter (const Position& position) const noexcept | |||||
{ | |||||
Position p (position); | |||||
const int maxDistance = 256; | |||||
int i = 0; | |||||
while (i < maxDistance | |||||
&& CharacterFunctions::isWhitespace (p.getCharacter()) | |||||
&& (i == 0 || (p.getCharacter() != '\n' | |||||
&& p.getCharacter() != '\r'))) | |||||
{ | |||||
++i; | |||||
p.moveBy (1); | |||||
} | |||||
if (i == 0) | |||||
{ | |||||
const int type = CodeDocumentHelpers::getCharacterType (p.getCharacter()); | |||||
while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.getCharacter())) | |||||
{ | |||||
++i; | |||||
p.moveBy (1); | |||||
} | |||||
while (i < maxDistance | |||||
&& CharacterFunctions::isWhitespace (p.getCharacter()) | |||||
&& (i == 0 || (p.getCharacter() != '\n' | |||||
&& p.getCharacter() != '\r'))) | |||||
{ | |||||
++i; | |||||
p.moveBy (1); | |||||
} | |||||
} | |||||
return p; | |||||
} | |||||
CodeDocument::Position CodeDocument::findWordBreakBefore (const Position& position) const noexcept | |||||
{ | |||||
Position p (position); | |||||
const int maxDistance = 256; | |||||
int i = 0; | |||||
bool stoppedAtLineStart = false; | |||||
while (i < maxDistance) | |||||
{ | |||||
const juce_wchar c = p.movedBy (-1).getCharacter(); | |||||
if (c == '\r' || c == '\n') | |||||
{ | |||||
stoppedAtLineStart = true; | |||||
if (i > 0) | |||||
break; | |||||
} | |||||
if (! CharacterFunctions::isWhitespace (c)) | |||||
break; | |||||
p.moveBy (-1); | |||||
++i; | |||||
} | |||||
if (i < maxDistance && ! stoppedAtLineStart) | |||||
{ | |||||
const int type = CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter()); | |||||
while (i < maxDistance && type == CodeDocumentHelpers::getCharacterType (p.movedBy (-1).getCharacter())) | |||||
{ | |||||
p.moveBy (-1); | |||||
++i; | |||||
} | |||||
} | |||||
return p; | |||||
} | |||||
void CodeDocument::findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept | |||||
{ | |||||
end = pos; | |||||
while (CodeDocumentHelpers::isTokenCharacter (end.getCharacter())) | |||||
end.moveBy (1); | |||||
start = end; | |||||
while (start.getIndexInLine() > 0 | |||||
&& CodeDocumentHelpers::isTokenCharacter (start.movedBy (-1).getCharacter())) | |||||
start.moveBy (-1); | |||||
} | |||||
void CodeDocument::findLineContaining (const Position& pos, Position& s, Position& e) const noexcept | |||||
{ | |||||
s.setLineAndIndex (pos.getLineNumber(), 0); | |||||
e.setLineAndIndex (pos.getLineNumber() + 1, 0); | |||||
} | |||||
void CodeDocument::checkLastLineStatus() | |||||
{ | |||||
while (lines.size() > 0 | |||||
&& lines.getLast()->lineLength == 0 | |||||
&& (lines.size() == 1 || ! lines.getUnchecked (lines.size() - 2)->endsWithLineBreak())) | |||||
{ | |||||
// remove any empty lines at the end if the preceding line doesn't end in a newline. | |||||
lines.removeLast(); | |||||
} | |||||
const CodeDocumentLine* const lastLine = lines.getLast(); | |||||
if (lastLine != nullptr && lastLine->endsWithLineBreak()) | |||||
{ | |||||
// check that there's an empty line at the end if the preceding one ends in a newline.. | |||||
lines.add (new CodeDocumentLine (StringRef(), StringRef(), 0, 0, | |||||
lastLine->lineStartInFile + lastLine->lineLength)); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void CodeDocument::addListener (CodeDocument::Listener* const l) noexcept { listeners.add (l); } | |||||
void CodeDocument::removeListener (CodeDocument::Listener* const l) noexcept { listeners.remove (l); } | |||||
//============================================================================== | |||||
class CodeDocumentInsertAction : public UndoableAction | |||||
{ | |||||
public: | |||||
CodeDocumentInsertAction (CodeDocument& doc, const String& t, const int pos) noexcept | |||||
: owner (doc), text (t), insertPos (pos) | |||||
{ | |||||
} | |||||
bool perform() | |||||
{ | |||||
owner.currentActionIndex++; | |||||
owner.insert (text, insertPos, false); | |||||
return true; | |||||
} | |||||
bool undo() | |||||
{ | |||||
owner.currentActionIndex--; | |||||
owner.remove (insertPos, insertPos + text.length(), false); | |||||
return true; | |||||
} | |||||
int getSizeInUnits() { return text.length() + 32; } | |||||
private: | |||||
CodeDocument& owner; | |||||
const String text; | |||||
const int insertPos; | |||||
JUCE_DECLARE_NON_COPYABLE (CodeDocumentInsertAction) | |||||
}; | |||||
void CodeDocument::insert (const String& text, const int insertPos, const bool undoable) | |||||
{ | |||||
if (text.isNotEmpty()) | |||||
{ | |||||
if (undoable) | |||||
{ | |||||
undoManager.perform (new CodeDocumentInsertAction (*this, text, insertPos)); | |||||
} | |||||
else | |||||
{ | |||||
Position pos (*this, insertPos); | |||||
const int firstAffectedLine = pos.getLineNumber(); | |||||
CodeDocumentLine* const firstLine = lines [firstAffectedLine]; | |||||
String textInsideOriginalLine (text); | |||||
if (firstLine != nullptr) | |||||
{ | |||||
const int index = pos.getIndexInLine(); | |||||
textInsideOriginalLine = firstLine->line.substring (0, index) | |||||
+ textInsideOriginalLine | |||||
+ firstLine->line.substring (index); | |||||
} | |||||
maximumLineLength = -1; | |||||
Array <CodeDocumentLine*> newLines; | |||||
CodeDocumentLine::createLines (newLines, textInsideOriginalLine); | |||||
jassert (newLines.size() > 0); | |||||
CodeDocumentLine* const newFirstLine = newLines.getUnchecked (0); | |||||
newFirstLine->lineStartInFile = firstLine != nullptr ? firstLine->lineStartInFile : 0; | |||||
lines.set (firstAffectedLine, newFirstLine); | |||||
if (newLines.size() > 1) | |||||
lines.insertArray (firstAffectedLine + 1, newLines.getRawDataPointer() + 1, newLines.size() - 1); | |||||
int lineStart = newFirstLine->lineStartInFile; | |||||
for (int i = firstAffectedLine; i < lines.size(); ++i) | |||||
{ | |||||
CodeDocumentLine& l = *lines.getUnchecked (i); | |||||
l.lineStartInFile = lineStart; | |||||
lineStart += l.lineLength; | |||||
} | |||||
checkLastLineStatus(); | |||||
const int newTextLength = text.length(); | |||||
for (int i = 0; i < positionsToMaintain.size(); ++i) | |||||
{ | |||||
CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i); | |||||
if (p.getPosition() >= insertPos) | |||||
p.setPosition (p.getPosition() + newTextLength); | |||||
} | |||||
listeners.call (&CodeDocument::Listener::codeDocumentTextInserted, text, insertPos); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class CodeDocumentDeleteAction : public UndoableAction | |||||
{ | |||||
public: | |||||
CodeDocumentDeleteAction (CodeDocument& doc, const int start, const int end) noexcept | |||||
: owner (doc), startPos (start), endPos (end), | |||||
removedText (doc.getTextBetween (CodeDocument::Position (doc, start), | |||||
CodeDocument::Position (doc, end))) | |||||
{ | |||||
} | |||||
bool perform() | |||||
{ | |||||
owner.currentActionIndex++; | |||||
owner.remove (startPos, endPos, false); | |||||
return true; | |||||
} | |||||
bool undo() | |||||
{ | |||||
owner.currentActionIndex--; | |||||
owner.insert (removedText, startPos, false); | |||||
return true; | |||||
} | |||||
int getSizeInUnits() { return (endPos - startPos) + 32; } | |||||
private: | |||||
CodeDocument& owner; | |||||
const int startPos, endPos; | |||||
const String removedText; | |||||
JUCE_DECLARE_NON_COPYABLE (CodeDocumentDeleteAction) | |||||
}; | |||||
void CodeDocument::remove (const int startPos, const int endPos, const bool undoable) | |||||
{ | |||||
if (endPos <= startPos) | |||||
return; | |||||
if (undoable) | |||||
{ | |||||
undoManager.perform (new CodeDocumentDeleteAction (*this, startPos, endPos)); | |||||
} | |||||
else | |||||
{ | |||||
Position startPosition (*this, startPos); | |||||
Position endPosition (*this, endPos); | |||||
maximumLineLength = -1; | |||||
const int firstAffectedLine = startPosition.getLineNumber(); | |||||
const int endLine = endPosition.getLineNumber(); | |||||
CodeDocumentLine& firstLine = *lines.getUnchecked (firstAffectedLine); | |||||
if (firstAffectedLine == endLine) | |||||
{ | |||||
firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine()) | |||||
+ firstLine.line.substring (endPosition.getIndexInLine()); | |||||
firstLine.updateLength(); | |||||
} | |||||
else | |||||
{ | |||||
CodeDocumentLine& lastLine = *lines.getUnchecked (endLine); | |||||
firstLine.line = firstLine.line.substring (0, startPosition.getIndexInLine()) | |||||
+ lastLine.line.substring (endPosition.getIndexInLine()); | |||||
firstLine.updateLength(); | |||||
int numLinesToRemove = endLine - firstAffectedLine; | |||||
lines.removeRange (firstAffectedLine + 1, numLinesToRemove); | |||||
} | |||||
for (int i = firstAffectedLine + 1; i < lines.size(); ++i) | |||||
{ | |||||
CodeDocumentLine& l = *lines.getUnchecked (i); | |||||
const CodeDocumentLine& previousLine = *lines.getUnchecked (i - 1); | |||||
l.lineStartInFile = previousLine.lineStartInFile + previousLine.lineLength; | |||||
} | |||||
checkLastLineStatus(); | |||||
const int totalChars = getNumCharacters(); | |||||
for (int i = 0; i < positionsToMaintain.size(); ++i) | |||||
{ | |||||
CodeDocument::Position& p = *positionsToMaintain.getUnchecked(i); | |||||
if (p.getPosition() > startPosition.getPosition()) | |||||
p.setPosition (jmax (startPos, p.getPosition() + startPos - endPos)); | |||||
if (p.getPosition() > totalChars) | |||||
p.setPosition (totalChars); | |||||
} | |||||
listeners.call (&CodeDocument::Listener::codeDocumentTextDeleted, startPos, endPos); | |||||
} | |||||
} |
@@ -0,0 +1,417 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_CODEDOCUMENT_H_INCLUDED | |||||
#define JUCE_CODEDOCUMENT_H_INCLUDED | |||||
class CodeDocumentLine; | |||||
//============================================================================== | |||||
/** | |||||
A class for storing and manipulating a source code file. | |||||
When using a CodeEditorComponent, it takes one of these as its source object. | |||||
The CodeDocument stores its content as an array of lines, which makes it | |||||
quick to insert and delete. | |||||
@see CodeEditorComponent | |||||
*/ | |||||
class JUCE_API CodeDocument | |||||
{ | |||||
public: | |||||
/** Creates a new, empty document. */ | |||||
CodeDocument(); | |||||
/** Destructor. */ | |||||
~CodeDocument(); | |||||
//============================================================================== | |||||
/** A position in a code document. | |||||
Using this class you can find a position in a code document and quickly get its | |||||
character position, line, and index. By calling setPositionMaintained (true), the | |||||
position is automatically updated when text is inserted or deleted in the document, | |||||
so that it maintains its original place in the text. | |||||
*/ | |||||
class JUCE_API Position | |||||
{ | |||||
public: | |||||
/** Creates an uninitialised postion. | |||||
Don't attempt to call any methods on this until you've given it an owner document | |||||
to refer to! | |||||
*/ | |||||
Position() noexcept; | |||||
/** Creates a position based on a line and index in a document. | |||||
Note that this index is NOT the column number, it's the number of characters from the | |||||
start of the line. The "column" number isn't quite the same, because if the line | |||||
contains any tab characters, the relationship of the index to its visual column depends on | |||||
the number of spaces per tab being used! | |||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document, | |||||
they will be adjusted to keep them within its limits. | |||||
*/ | |||||
Position (const CodeDocument& ownerDocument, | |||||
int line, int indexInLine) noexcept; | |||||
/** Creates a position based on a character index in a document. | |||||
This position is placed at the specified number of characters from the start of the | |||||
document. The line and column are auto-calculated. | |||||
If the position is beyond the range of the document, it'll be adjusted to keep it | |||||
inside. | |||||
*/ | |||||
Position (const CodeDocument& ownerDocument, | |||||
int charactersFromStartOfDocument) noexcept; | |||||
/** Creates a copy of another position. | |||||
This will copy the position, but the new object will not be set to maintain its position, | |||||
even if the source object was set to do so. | |||||
*/ | |||||
Position (const Position&) noexcept; | |||||
/** Destructor. */ | |||||
~Position(); | |||||
Position& operator= (const Position&); | |||||
bool operator== (const Position&) const noexcept; | |||||
bool operator!= (const Position&) const noexcept; | |||||
/** Points this object at a new position within the document. | |||||
If the position is beyond the range of the document, it'll be adjusted to keep it | |||||
inside. | |||||
@see getPosition, setLineAndIndex | |||||
*/ | |||||
void setPosition (int charactersFromStartOfDocument); | |||||
/** Returns the position as the number of characters from the start of the document. | |||||
@see setPosition, getLineNumber, getIndexInLine | |||||
*/ | |||||
int getPosition() const noexcept { return characterPos; } | |||||
/** Moves the position to a new line and index within the line. | |||||
Note that the index is NOT the column at which the position appears in an editor. | |||||
If the line contains any tab characters, the relationship of the index to its | |||||
visual position depends on the number of spaces per tab being used! | |||||
Lines are numbered from zero, and if the line or index are beyond the bounds of the document, | |||||
they will be adjusted to keep them within its limits. | |||||
*/ | |||||
void setLineAndIndex (int newLine, int newIndexInLine); | |||||
/** Returns the line number of this position. | |||||
The first line in the document is numbered zero, not one! | |||||
*/ | |||||
int getLineNumber() const noexcept { return line; } | |||||
/** Returns the number of characters from the start of the line. | |||||
Note that this value is NOT the column at which the position appears in an editor. | |||||
If the line contains any tab characters, the relationship of the index to its | |||||
visual position depends on the number of spaces per tab being used! | |||||
*/ | |||||
int getIndexInLine() const noexcept { return indexInLine; } | |||||
/** Allows the position to be automatically updated when the document changes. | |||||
If this is set to true, the positon will register with its document so that | |||||
when the document has text inserted or deleted, this position will be automatically | |||||
moved to keep it at the same position in the text. | |||||
*/ | |||||
void setPositionMaintained (bool isMaintained); | |||||
//============================================================================== | |||||
/** Moves the position forwards or backwards by the specified number of characters. | |||||
@see movedBy | |||||
*/ | |||||
void moveBy (int characterDelta); | |||||
/** Returns a position which is the same as this one, moved by the specified number of | |||||
characters. | |||||
@see moveBy | |||||
*/ | |||||
Position movedBy (int characterDelta) const; | |||||
/** Returns a position which is the same as this one, moved up or down by the specified | |||||
number of lines. | |||||
@see movedBy | |||||
*/ | |||||
Position movedByLines (int deltaLines) const; | |||||
/** Returns the character in the document at this position. | |||||
@see getLineText | |||||
*/ | |||||
juce_wchar getCharacter() const; | |||||
/** Returns the line from the document that this position is within. | |||||
@see getCharacter, getLineNumber | |||||
*/ | |||||
String getLineText() const; | |||||
private: | |||||
CodeDocument* owner; | |||||
int characterPos, line, indexInLine; | |||||
bool positionMaintained; | |||||
}; | |||||
//============================================================================== | |||||
/** Returns the full text of the document. */ | |||||
String getAllContent() const; | |||||
/** Returns a section of the document's text. */ | |||||
String getTextBetween (const Position& start, const Position& end) const; | |||||
/** Returns a line from the document. */ | |||||
String getLine (int lineIndex) const noexcept; | |||||
/** Returns the number of characters in the document. */ | |||||
int getNumCharacters() const noexcept; | |||||
/** Returns the number of lines in the document. */ | |||||
int getNumLines() const noexcept { return lines.size(); } | |||||
/** Returns the number of characters in the longest line of the document. */ | |||||
int getMaximumLineLength() noexcept; | |||||
/** Deletes a section of the text. | |||||
This operation is undoable. | |||||
*/ | |||||
void deleteSection (const Position& startPosition, const Position& endPosition); | |||||
/** Deletes a section of the text. | |||||
This operation is undoable. | |||||
*/ | |||||
void deleteSection (int startIndex, int endIndex); | |||||
/** Inserts some text into the document at a given position. | |||||
This operation is undoable. | |||||
*/ | |||||
void insertText (const Position& position, const String& text); | |||||
/** Inserts some text into the document at a given position. | |||||
This operation is undoable. | |||||
*/ | |||||
void insertText (int insertIndex, const String& text); | |||||
/** Replaces a section of the text with a new string. | |||||
This operation is undoable. | |||||
*/ | |||||
void replaceSection (int startIndex, int endIndex, const String& newText); | |||||
/** Clears the document and replaces it with some new text. | |||||
This operation is undoable - if you're trying to completely reset the document, you | |||||
might want to also call clearUndoHistory() and setSavePoint() after using this method. | |||||
*/ | |||||
void replaceAllContent (const String& newContent); | |||||
/** Analyses the changes between the current content and some new text, and applies | |||||
those changes. | |||||
*/ | |||||
void applyChanges (const String& newContent); | |||||
/** Replaces the editor's contents with the contents of a stream. | |||||
This will also reset the undo history and save point marker. | |||||
*/ | |||||
bool loadFromStream (InputStream& stream); | |||||
/** Writes the editor's current contents to a stream. */ | |||||
bool writeToStream (OutputStream& stream); | |||||
//============================================================================== | |||||
/** Returns the preferred new-line characters for the document. | |||||
This will be either "\n", "\r\n", or (rarely) "\r". | |||||
@see setNewLineCharacters | |||||
*/ | |||||
String getNewLineCharacters() const noexcept { return newLineChars; } | |||||
/** Sets the new-line characters that the document should use. | |||||
The string must be either "\n", "\r\n", or (rarely) "\r". | |||||
@see getNewLineCharacters | |||||
*/ | |||||
void setNewLineCharacters (const String& newLine) noexcept; | |||||
//============================================================================== | |||||
/** Begins a new undo transaction. | |||||
The document itself will not call this internally, so relies on whatever is using the | |||||
document to periodically call this to break up the undo sequence into sensible chunks. | |||||
@see UndoManager::beginNewTransaction | |||||
*/ | |||||
void newTransaction(); | |||||
/** Undo the last operation. | |||||
@see UndoManager::undo | |||||
*/ | |||||
void undo(); | |||||
/** Redo the last operation. | |||||
@see UndoManager::redo | |||||
*/ | |||||
void redo(); | |||||
/** Clears the undo history. | |||||
@see UndoManager::clearUndoHistory | |||||
*/ | |||||
void clearUndoHistory(); | |||||
/** Returns the document's UndoManager */ | |||||
UndoManager& getUndoManager() noexcept { return undoManager; } | |||||
//============================================================================== | |||||
/** Makes a note that the document's current state matches the one that is saved. | |||||
After this has been called, hasChangedSinceSavePoint() will return false until | |||||
the document has been altered, and then it'll start returning true. If the document is | |||||
altered, but then undone until it gets back to this state, hasChangedSinceSavePoint() | |||||
will again return false. | |||||
@see hasChangedSinceSavePoint | |||||
*/ | |||||
void setSavePoint() noexcept; | |||||
/** Returns true if the state of the document differs from the state it was in when | |||||
setSavePoint() was last called. | |||||
@see setSavePoint | |||||
*/ | |||||
bool hasChangedSinceSavePoint() const noexcept; | |||||
//============================================================================== | |||||
/** Searches for a word-break. */ | |||||
Position findWordBreakAfter (const Position& position) const noexcept; | |||||
/** Searches for a word-break. */ | |||||
Position findWordBreakBefore (const Position& position) const noexcept; | |||||
/** Finds the token that contains the given position. */ | |||||
void findTokenContaining (const Position& pos, Position& start, Position& end) const noexcept; | |||||
/** Finds the line that contains the given position. */ | |||||
void findLineContaining (const Position& pos, Position& start, Position& end) const noexcept; | |||||
//============================================================================== | |||||
/** An object that receives callbacks from the CodeDocument when its text changes. | |||||
@see CodeDocument::addListener, CodeDocument::removeListener | |||||
*/ | |||||
class JUCE_API Listener | |||||
{ | |||||
public: | |||||
Listener() {} | |||||
virtual ~Listener() {} | |||||
/** Called by a CodeDocument when text is added. */ | |||||
virtual void codeDocumentTextInserted (const String& newText, int insertIndex) = 0; | |||||
/** Called by a CodeDocument when text is deleted. */ | |||||
virtual void codeDocumentTextDeleted (int startIndex, int endIndex) = 0; | |||||
}; | |||||
/** Registers a listener object to receive callbacks when the document changes. | |||||
If the listener is already registered, this method has no effect. | |||||
@see removeListener | |||||
*/ | |||||
void addListener (Listener* listener) noexcept; | |||||
/** Deregisters a listener. | |||||
@see addListener | |||||
*/ | |||||
void removeListener (Listener* listener) noexcept; | |||||
//============================================================================== | |||||
/** Iterates the text in a CodeDocument. | |||||
This class lets you read characters from a CodeDocument. It's designed to be used | |||||
by a CodeTokeniser object. | |||||
@see CodeDocument | |||||
*/ | |||||
class JUCE_API Iterator | |||||
{ | |||||
public: | |||||
Iterator (const CodeDocument& document) noexcept; | |||||
Iterator (const Iterator&) noexcept; | |||||
Iterator& operator= (const Iterator&) noexcept; | |||||
~Iterator() noexcept; | |||||
/** Reads the next character and returns it. | |||||
@see peekNextChar | |||||
*/ | |||||
juce_wchar nextChar() noexcept; | |||||
/** Reads the next character without advancing the current position. */ | |||||
juce_wchar peekNextChar() const noexcept; | |||||
/** Advances the position by one character. */ | |||||
void skip() noexcept; | |||||
/** Returns the position as the number of characters from the start of the document. */ | |||||
int getPosition() const noexcept { return position; } | |||||
/** Skips over any whitespace characters until the next character is non-whitespace. */ | |||||
void skipWhitespace() noexcept; | |||||
/** Skips forward until the next character will be the first character on the next line */ | |||||
void skipToEndOfLine() noexcept; | |||||
/** Returns the line number of the next character. */ | |||||
int getLine() const noexcept { return line; } | |||||
/** Returns true if the iterator has reached the end of the document. */ | |||||
bool isEOF() const noexcept; | |||||
private: | |||||
const CodeDocument* document; | |||||
mutable String::CharPointerType charPointer; | |||||
int line, position; | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
friend class CodeDocumentInsertAction; | |||||
friend class CodeDocumentDeleteAction; | |||||
friend class Iterator; | |||||
friend class Position; | |||||
OwnedArray <CodeDocumentLine> lines; | |||||
Array <Position*> positionsToMaintain; | |||||
UndoManager undoManager; | |||||
int currentActionIndex, indexOfSavedState; | |||||
int maximumLineLength; | |||||
ListenerList <Listener> listeners; | |||||
String newLineChars; | |||||
void insert (const String& text, int insertPos, bool undoable); | |||||
void remove (int startPos, int endPos, bool undoable); | |||||
void checkLastLineStatus(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeDocument) | |||||
}; | |||||
#endif // JUCE_CODEDOCUMENT_H_INCLUDED |
@@ -0,0 +1,432 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_CODEEDITORCOMPONENT_H_INCLUDED | |||||
#define JUCE_CODEEDITORCOMPONENT_H_INCLUDED | |||||
class CodeTokeniser; | |||||
//============================================================================== | |||||
/** | |||||
A text editor component designed specifically for source code. | |||||
This is designed to handle syntax highlighting and fast editing of very large | |||||
files. | |||||
*/ | |||||
class JUCE_API CodeEditorComponent : public Component, | |||||
public ApplicationCommandTarget, | |||||
public TextInputTarget | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an editor for a document. | |||||
The tokeniser object is optional - pass nullptr to disable syntax highlighting. | |||||
The object that you pass in is not owned or deleted by the editor - you must | |||||
make sure that it doesn't get deleted while this component is still using it. | |||||
@see CodeDocument | |||||
*/ | |||||
CodeEditorComponent (CodeDocument& document, | |||||
CodeTokeniser* codeTokeniser); | |||||
/** Destructor. */ | |||||
~CodeEditorComponent(); | |||||
//============================================================================== | |||||
/** Returns the code document that this component is editing. */ | |||||
CodeDocument& getDocument() const noexcept { return document; } | |||||
/** Loads the given content into the document. | |||||
This will completely reset the CodeDocument object, clear its undo history, | |||||
and fill it with this text. | |||||
*/ | |||||
void loadContent (const String& newContent); | |||||
//============================================================================== | |||||
/** Returns the standard character width. */ | |||||
float getCharWidth() const noexcept { return charWidth; } | |||||
/** Returns the height of a line of text, in pixels. */ | |||||
int getLineHeight() const noexcept { return lineHeight; } | |||||
/** Returns the number of whole lines visible on the screen, | |||||
This doesn't include a cut-off line that might be visible at the bottom if the | |||||
component's height isn't an exact multiple of the line-height. | |||||
*/ | |||||
int getNumLinesOnScreen() const noexcept { return linesOnScreen; } | |||||
/** Returns the index of the first line that's visible at the top of the editor. */ | |||||
int getFirstLineOnScreen() const noexcept { return firstLineOnScreen; } | |||||
/** Returns the number of whole columns visible on the screen. | |||||
This doesn't include any cut-off columns at the right-hand edge. | |||||
*/ | |||||
int getNumColumnsOnScreen() const noexcept { return columnsOnScreen; } | |||||
/** Returns the current caret position. */ | |||||
CodeDocument::Position getCaretPos() const { return caretPos; } | |||||
/** Returns the position of the caret, relative to the editor's origin. */ | |||||
Rectangle<int> getCaretRectangle() override; | |||||
/** Moves the caret. | |||||
If selecting is true, the section of the document between the current | |||||
caret position and the new one will become selected. If false, any currently | |||||
selected region will be deselected. | |||||
*/ | |||||
void moveCaretTo (const CodeDocument::Position& newPos, bool selecting); | |||||
/** Returns the on-screen position of a character in the document. | |||||
The rectangle returned is relative to this component's top-left origin. | |||||
*/ | |||||
Rectangle<int> getCharacterBounds (const CodeDocument::Position& pos) const; | |||||
/** Finds the character at a given on-screen position. | |||||
The coordinates are relative to this component's top-left origin. | |||||
*/ | |||||
CodeDocument::Position getPositionAt (int x, int y); | |||||
/** Returns the start of the selection as a position. */ | |||||
CodeDocument::Position getSelectionStart() const { return selectionStart; } | |||||
/** Returns the end of the selection as a position. */ | |||||
CodeDocument::Position getSelectionEnd() const { return selectionEnd; } | |||||
/** Enables or disables the line-number display in the gutter. */ | |||||
void setLineNumbersShown (bool shouldBeShown); | |||||
//============================================================================== | |||||
bool moveCaretLeft (bool moveInWholeWordSteps, bool selecting); | |||||
bool moveCaretRight (bool moveInWholeWordSteps, bool selecting); | |||||
bool moveCaretUp (bool selecting); | |||||
bool moveCaretDown (bool selecting); | |||||
bool scrollDown(); | |||||
bool scrollUp(); | |||||
bool pageUp (bool selecting); | |||||
bool pageDown (bool selecting); | |||||
bool moveCaretToTop (bool selecting); | |||||
bool moveCaretToStartOfLine (bool selecting); | |||||
bool moveCaretToEnd (bool selecting); | |||||
bool moveCaretToEndOfLine (bool selecting); | |||||
bool deleteBackwards (bool moveInWholeWordSteps); | |||||
bool deleteForwards (bool moveInWholeWordSteps); | |||||
bool deleteWhitespaceBackwardsToTabStop(); | |||||
bool copyToClipboard(); | |||||
bool cutToClipboard(); | |||||
bool pasteFromClipboard(); | |||||
bool undo(); | |||||
bool redo(); | |||||
void selectRegion (const CodeDocument::Position& start, const CodeDocument::Position& end); | |||||
bool selectAll(); | |||||
void deselectAll(); | |||||
void scrollToLine (int newFirstLineOnScreen); | |||||
void scrollBy (int deltaLines); | |||||
void scrollToColumn (int newFirstColumnOnScreen); | |||||
void scrollToKeepCaretOnScreen(); | |||||
void scrollToKeepLinesOnScreen (Range<int> linesToShow); | |||||
void insertTextAtCaret (const String& textToInsert) override; | |||||
void insertTabAtCaret(); | |||||
void indentSelection(); | |||||
void unindentSelection(); | |||||
//============================================================================== | |||||
Range<int> getHighlightedRegion() const override; | |||||
bool isHighlightActive() const noexcept; | |||||
void setHighlightedRegion (const Range<int>& newRange) override; | |||||
String getTextInRange (const Range<int>& range) const override; | |||||
//============================================================================== | |||||
/** Can be used to save and restore the editor's caret position, selection state, etc. */ | |||||
struct State | |||||
{ | |||||
/** Creates an object containing the state of the given editor. */ | |||||
State (const CodeEditorComponent&); | |||||
/** Creates a state object from a string that was previously created with toString(). */ | |||||
State (const String& stringifiedVersion); | |||||
State (const State&) noexcept; | |||||
/** Updates the given editor with this saved state. */ | |||||
void restoreState (CodeEditorComponent&) const; | |||||
/** Returns a stringified version of this state that can be used to recreate it later. */ | |||||
String toString() const; | |||||
private: | |||||
int lastTopLine, lastCaretPos, lastSelectionEnd; | |||||
}; | |||||
//============================================================================== | |||||
/** Changes the current tab settings. | |||||
This lets you change the tab size and whether pressing the tab key inserts a | |||||
tab character, or its equivalent number of spaces. | |||||
*/ | |||||
void setTabSize (int numSpacesPerTab, bool insertSpacesInsteadOfTabCharacters); | |||||
/** Returns the current number of spaces per tab. | |||||
@see setTabSize | |||||
*/ | |||||
int getTabSize() const noexcept { return spacesPerTab; } | |||||
/** Returns true if the tab key will insert spaces instead of actual tab characters. | |||||
@see setTabSize | |||||
*/ | |||||
bool areSpacesInsertedForTabs() const { return useSpacesForTabs; } | |||||
/** Returns a string containing spaces or tab characters to generate the given number of spaces. */ | |||||
String getTabString (int numSpaces) const; | |||||
/** Changes the font. | |||||
Make sure you only use a fixed-width font, or this component will look pretty nasty! | |||||
*/ | |||||
void setFont (const Font& newFont); | |||||
/** Returns the font that the editor is using. */ | |||||
const Font& getFont() const noexcept { return font; } | |||||
/** Makes the editor read-only. */ | |||||
void setReadOnly (bool shouldBeReadOnly) noexcept; | |||||
/** Returns true if the editor is set to be read-only. */ | |||||
bool isReadOnly() const noexcept { return readOnly; } | |||||
//============================================================================== | |||||
struct JUCE_API ColourScheme | |||||
{ | |||||
struct TokenType | |||||
{ | |||||
String name; | |||||
Colour colour; | |||||
}; | |||||
Array<TokenType> types; | |||||
void set (const String& name, const Colour colour); | |||||
}; | |||||
/** Changes the syntax highlighting scheme. | |||||
The token type values are dependent on the tokeniser being used - use | |||||
CodeTokeniser::getTokenTypes() to get a list of the token types. | |||||
@see getColourForTokenType | |||||
*/ | |||||
void setColourScheme (const ColourScheme& scheme); | |||||
/** Returns the current syntax highlighting colour scheme. */ | |||||
const ColourScheme& getColourScheme() const noexcept { return colourScheme; } | |||||
/** Returns one the syntax highlighting colour for the given token. | |||||
The token type values are dependent on the tokeniser being used. | |||||
@see setColourScheme | |||||
*/ | |||||
Colour getColourForTokenType (int tokenType) const; | |||||
//============================================================================== | |||||
/** A set of colour IDs to use to change the colour of various aspects of the editor. | |||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||||
methods. | |||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||||
*/ | |||||
enum ColourIds | |||||
{ | |||||
backgroundColourId = 0x1004500, /**< A colour to use to fill the editor's background. */ | |||||
highlightColourId = 0x1004502, /**< The colour to use for the highlighted background under selected text. */ | |||||
defaultTextColourId = 0x1004503, /**< The colour to use for text when no syntax colouring is enabled. */ | |||||
lineNumberBackgroundId = 0x1004504, /**< The colour to use for filling the background of the line-number gutter. */ | |||||
lineNumberTextId = 0x1004505, /**< The colour to use for drawing the line numbers. */ | |||||
}; | |||||
//============================================================================== | |||||
/** Changes the size of the scrollbars. */ | |||||
void setScrollbarThickness (int thickness); | |||||
/** Returns the thickness of the scrollbars. */ | |||||
int getScrollbarThickness() const noexcept { return scrollbarThickness; } | |||||
//============================================================================== | |||||
/** Called when the return key is pressed - this can be overridden for custom behaviour. */ | |||||
virtual void handleReturnKey(); | |||||
/** Called when the tab key is pressed - this can be overridden for custom behaviour. */ | |||||
virtual void handleTabKey(); | |||||
/** Called when the escape key is pressed - this can be overridden for custom behaviour. */ | |||||
virtual void handleEscapeKey(); | |||||
//============================================================================== | |||||
/** This adds the items to the popup menu. | |||||
By default it adds the cut/copy/paste items, but you can override this if | |||||
you need to replace these with your own items. | |||||
If you want to add your own items to the existing ones, you can override this, | |||||
call the base class's addPopupMenuItems() method, then append your own items. | |||||
When the menu has been shown, performPopupMenuAction() will be called to | |||||
perform the item that the user has chosen. | |||||
If this was triggered by a mouse-click, the mouseClickEvent parameter will be | |||||
a pointer to the info about it, or may be null if the menu is being triggered | |||||
by some other means. | |||||
@see performPopupMenuAction, setPopupMenuEnabled, isPopupMenuEnabled | |||||
*/ | |||||
virtual void addPopupMenuItems (PopupMenu& menuToAddTo, | |||||
const MouseEvent* mouseClickEvent); | |||||
/** This is called to perform one of the items that was shown on the popup menu. | |||||
If you've overridden addPopupMenuItems(), you should also override this | |||||
to perform the actions that you've added. | |||||
If you've overridden addPopupMenuItems() but have still left the default items | |||||
on the menu, remember to call the superclass's performPopupMenuAction() | |||||
so that it can perform the default actions if that's what the user clicked on. | |||||
@see addPopupMenuItems, setPopupMenuEnabled, isPopupMenuEnabled | |||||
*/ | |||||
virtual void performPopupMenuAction (int menuItemID); | |||||
/** Specifies a commmand-manager which the editor will notify whenever the state | |||||
of any of its commands changes. | |||||
If you're making use of the editor's ApplicationCommandTarget interface, then | |||||
you should also use this to tell it which command manager it should use. Make | |||||
sure that the manager does not go out of scope while the editor is using it. You | |||||
can pass a nullptr here to disable this. | |||||
*/ | |||||
void setCommandManager (ApplicationCommandManager* newManager) noexcept; | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
/** @internal */ | |||||
void resized() override; | |||||
/** @internal */ | |||||
bool keyPressed (const KeyPress&) override; | |||||
/** @internal */ | |||||
void mouseDown (const MouseEvent&) override; | |||||
/** @internal */ | |||||
void mouseDrag (const MouseEvent&) override; | |||||
/** @internal */ | |||||
void mouseUp (const MouseEvent&) override; | |||||
/** @internal */ | |||||
void mouseDoubleClick (const MouseEvent&) override; | |||||
/** @internal */ | |||||
void mouseWheelMove (const MouseEvent&, const MouseWheelDetails&) override; | |||||
/** @internal */ | |||||
void focusGained (FocusChangeType) override; | |||||
/** @internal */ | |||||
void focusLost (FocusChangeType) override; | |||||
/** @internal */ | |||||
bool isTextInputActive() const override; | |||||
/** @internal */ | |||||
void setTemporaryUnderlining (const Array<Range<int> >&) override; | |||||
/** @internal */ | |||||
ApplicationCommandTarget* getNextCommandTarget() override; | |||||
/** @internal */ | |||||
void getAllCommands (Array<CommandID>&) override; | |||||
/** @internal */ | |||||
void getCommandInfo (CommandID, ApplicationCommandInfo&) override; | |||||
/** @internal */ | |||||
bool perform (const InvocationInfo&) override; | |||||
private: | |||||
//============================================================================== | |||||
CodeDocument& document; | |||||
Font font; | |||||
int firstLineOnScreen, spacesPerTab; | |||||
float charWidth; | |||||
int lineHeight, linesOnScreen, columnsOnScreen; | |||||
int scrollbarThickness, columnToTryToMaintain; | |||||
bool readOnly, useSpacesForTabs, showLineNumbers, shouldFollowDocumentChanges; | |||||
double xOffset; | |||||
CodeDocument::Position caretPos, selectionStart, selectionEnd; | |||||
ScopedPointer<CaretComponent> caret; | |||||
ScrollBar verticalScrollBar, horizontalScrollBar; | |||||
ApplicationCommandManager* appCommandManager; | |||||
class Pimpl; | |||||
friend class Pimpl; | |||||
friend struct ContainerDeletePolicy<Pimpl>; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
class GutterComponent; | |||||
friend class GutterComponent; | |||||
friend struct ContainerDeletePolicy<GutterComponent>; | |||||
ScopedPointer<GutterComponent> gutter; | |||||
enum DragType | |||||
{ | |||||
notDragging, | |||||
draggingSelectionStart, | |||||
draggingSelectionEnd | |||||
}; | |||||
DragType dragType; | |||||
//============================================================================== | |||||
CodeTokeniser* codeTokeniser; | |||||
ColourScheme colourScheme; | |||||
class CodeEditorLine; | |||||
OwnedArray<CodeEditorLine> lines; | |||||
void rebuildLineTokens(); | |||||
void rebuildLineTokensAsync(); | |||||
void codeDocumentChanged (int start, int end); | |||||
OwnedArray<CodeDocument::Iterator> cachedIterators; | |||||
void clearCachedIterators (int firstLineToBeInvalid); | |||||
void updateCachedIterators (int maxLineNum); | |||||
void getIteratorForPosition (int position, CodeDocument::Iterator&); | |||||
void moveLineDelta (int delta, bool selecting); | |||||
int getGutterSize() const noexcept; | |||||
//============================================================================== | |||||
void insertText (const String&); | |||||
virtual void updateCaretPosition(); | |||||
void updateScrollBars(); | |||||
void scrollToLineInternal (int line); | |||||
void scrollToColumnInternal (double column); | |||||
void newTransaction(); | |||||
void cut(); | |||||
void indentSelectedLines (int spacesToAdd); | |||||
bool skipBackwardsToPreviousTab(); | |||||
bool performCommand (CommandID); | |||||
int indexToColumn (int line, int index) const noexcept; | |||||
int columnToIndex (int line, int column) const noexcept; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CodeEditorComponent) | |||||
}; | |||||
#endif // JUCE_CODEEDITORCOMPONENT_H_INCLUDED |
@@ -0,0 +1,58 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_CODETOKENISER_H_INCLUDED | |||||
#define JUCE_CODETOKENISER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A base class for tokenising code so that the syntax can be displayed in a | |||||
code editor. | |||||
@see CodeDocument, CodeEditorComponent | |||||
*/ | |||||
class JUCE_API CodeTokeniser | |||||
{ | |||||
public: | |||||
CodeTokeniser() {} | |||||
virtual ~CodeTokeniser() {} | |||||
//============================================================================== | |||||
/** Reads the next token from the source and returns its token type. | |||||
This must leave the source pointing to the first character in the | |||||
next token. | |||||
*/ | |||||
virtual int readNextToken (CodeDocument::Iterator& source) = 0; | |||||
/** Returns a suggested syntax highlighting colour scheme. */ | |||||
virtual CodeEditorComponent::ColourScheme getDefaultColourScheme() = 0; | |||||
private: | |||||
JUCE_LEAK_DETECTOR (CodeTokeniser) | |||||
}; | |||||
#endif // JUCE_CODETOKENISER_H_INCLUDED |
@@ -0,0 +1,233 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
struct LuaTokeniserFunctions | |||||
{ | |||||
static bool isReservedKeyword (String::CharPointerType token, const int tokenLength) noexcept | |||||
{ | |||||
static const char* const keywords2Char[] = | |||||
{ "if", "or", "in", "do", nullptr }; | |||||
static const char* const keywords3Char[] = | |||||
{ "and", "end", "for", "nil", "not", nullptr }; | |||||
static const char* const keywords4Char[] = | |||||
{ "then", "true", "else", nullptr }; | |||||
static const char* const keywords5Char[] = | |||||
{ "false", "local", "until", "while", "break", nullptr }; | |||||
static const char* const keywords6Char[] = | |||||
{ "repeat", "return", "elseif", nullptr}; | |||||
static const char* const keywordsOther[] = | |||||
{ "function", "@interface", "@end", "@synthesize", "@dynamic", "@public", | |||||
"@private", "@property", "@protected", "@class", nullptr }; | |||||
const char* const* k; | |||||
switch (tokenLength) | |||||
{ | |||||
case 2: k = keywords2Char; break; | |||||
case 3: k = keywords3Char; break; | |||||
case 4: k = keywords4Char; break; | |||||
case 5: k = keywords5Char; break; | |||||
case 6: k = keywords6Char; break; | |||||
default: | |||||
if (tokenLength < 2 || tokenLength > 16) | |||||
return false; | |||||
k = keywordsOther; | |||||
break; | |||||
} | |||||
for (int i = 0; k[i] != 0; ++i) | |||||
if (token.compare (CharPointer_ASCII (k[i])) == 0) | |||||
return true; | |||||
return false; | |||||
} | |||||
template <typename Iterator> | |||||
static int parseIdentifier (Iterator& source) noexcept | |||||
{ | |||||
int tokenLength = 0; | |||||
String::CharPointerType::CharType possibleIdentifier [100]; | |||||
String::CharPointerType possible (possibleIdentifier); | |||||
while (CppTokeniserFunctions::isIdentifierBody (source.peekNextChar())) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (tokenLength < 20) | |||||
possible.write (c); | |||||
++tokenLength; | |||||
} | |||||
if (tokenLength > 1 && tokenLength <= 16) | |||||
{ | |||||
possible.writeNull(); | |||||
if (isReservedKeyword (String::CharPointerType (possibleIdentifier), tokenLength)) | |||||
return LuaTokeniser::tokenType_keyword; | |||||
} | |||||
return LuaTokeniser::tokenType_identifier; | |||||
} | |||||
template <typename Iterator> | |||||
static int readNextToken (Iterator& source) | |||||
{ | |||||
source.skipWhitespace(); | |||||
const juce_wchar firstChar = source.peekNextChar(); | |||||
switch (firstChar) | |||||
{ | |||||
case 0: | |||||
break; | |||||
case '0': case '1': case '2': case '3': case '4': | |||||
case '5': case '6': case '7': case '8': case '9': | |||||
case '.': | |||||
{ | |||||
int result = CppTokeniserFunctions::parseNumber (source); | |||||
if (result == LuaTokeniser::tokenType_error) | |||||
{ | |||||
source.skip(); | |||||
if (firstChar == '.') | |||||
return LuaTokeniser::tokenType_punctuation; | |||||
} | |||||
return result; | |||||
} | |||||
case ',': | |||||
case ';': | |||||
case ':': | |||||
source.skip(); | |||||
return LuaTokeniser::tokenType_punctuation; | |||||
case '(': case ')': | |||||
case '{': case '}': | |||||
case '[': case ']': | |||||
source.skip(); | |||||
return LuaTokeniser::tokenType_bracket; | |||||
case '"': | |||||
case '\'': | |||||
CppTokeniserFunctions::skipQuotedString (source); | |||||
return LuaTokeniser::tokenType_string; | |||||
case '+': | |||||
source.skip(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '+', '='); | |||||
return LuaTokeniser::tokenType_operator; | |||||
case '-': | |||||
{ | |||||
source.skip(); | |||||
int result = CppTokeniserFunctions::parseNumber (source); | |||||
if (source.peekNextChar() == '-') | |||||
{ | |||||
source.skipToEndOfLine(); | |||||
return LuaTokeniser::tokenType_comment; | |||||
} | |||||
if (result == LuaTokeniser::tokenType_error) | |||||
{ | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '-', '='); | |||||
return LuaTokeniser::tokenType_operator; | |||||
} | |||||
return result; | |||||
} | |||||
case '*': case '%': | |||||
case '=': case '!': | |||||
source.skip(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '='); | |||||
return LuaTokeniser::tokenType_operator; | |||||
case '?': | |||||
case '~': | |||||
source.skip(); | |||||
return LuaTokeniser::tokenType_operator; | |||||
case '<': case '>': | |||||
case '|': case '&': case '^': | |||||
source.skip(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, firstChar); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '='); | |||||
return LuaTokeniser::tokenType_operator; | |||||
default: | |||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar)) | |||||
return parseIdentifier (source); | |||||
source.skip(); | |||||
break; | |||||
} | |||||
return LuaTokeniser::tokenType_error; | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
LuaTokeniser::LuaTokeniser() {} | |||||
LuaTokeniser::~LuaTokeniser() {} | |||||
int LuaTokeniser::readNextToken (CodeDocument::Iterator& source) | |||||
{ | |||||
return LuaTokeniserFunctions::readNextToken (source); | |||||
} | |||||
CodeEditorComponent::ColourScheme LuaTokeniser::getDefaultColourScheme() | |||||
{ | |||||
static const CodeEditorComponent::ColourScheme::TokenType types[] = | |||||
{ | |||||
{ "Error", Colour (0xffcc0000) }, | |||||
{ "Comment", Colour (0xff3c3c3c) }, | |||||
{ "Keyword", Colour (0xff0000cc) }, | |||||
{ "Operator", Colour (0xff225500) }, | |||||
{ "Identifier", Colour (0xff000000) }, | |||||
{ "Integer", Colour (0xff880000) }, | |||||
{ "Float", Colour (0xff885500) }, | |||||
{ "String", Colour (0xff990099) }, | |||||
{ "Bracket", Colour (0xff000055) }, | |||||
{ "Punctuation", Colour (0xff004400) } | |||||
}; | |||||
CodeEditorComponent::ColourScheme cs; | |||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2) | |||||
cs.set (types[i].name, types[i].colour); | |||||
return cs; | |||||
} |
@@ -0,0 +1,63 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_LUACODETOKENISER_H_INCLUDED | |||||
#define JUCE_LUACODETOKENISER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
*/ | |||||
class JUCE_API LuaTokeniser : public CodeTokeniser | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
LuaTokeniser(); | |||||
~LuaTokeniser(); | |||||
//============================================================================== | |||||
int readNextToken (CodeDocument::Iterator&) override; | |||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override; | |||||
/** The token values returned by this tokeniser. */ | |||||
enum TokenType | |||||
{ | |||||
tokenType_error = 0, | |||||
tokenType_comment, | |||||
tokenType_keyword, | |||||
tokenType_operator, | |||||
tokenType_identifier, | |||||
tokenType_integer, | |||||
tokenType_float, | |||||
tokenType_string, | |||||
tokenType_bracket, | |||||
tokenType_punctuation | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (LuaTokeniser) | |||||
}; | |||||
#endif // JUCE_LUACODETOKENISER_H_INCLUDED |
@@ -0,0 +1,166 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
XmlTokeniser::XmlTokeniser() {} | |||||
XmlTokeniser::~XmlTokeniser() {} | |||||
CodeEditorComponent::ColourScheme XmlTokeniser::getDefaultColourScheme() | |||||
{ | |||||
struct Type | |||||
{ | |||||
const char* name; | |||||
uint32 colour; | |||||
}; | |||||
const Type types[] = | |||||
{ | |||||
{ "Error", 0xffcc0000 }, | |||||
{ "Comment", 0xff00aa00 }, | |||||
{ "Keyword", 0xff0000cc }, | |||||
{ "Operator", 0xff225500 }, | |||||
{ "Identifier", 0xff000000 }, | |||||
{ "String", 0xff990099 }, | |||||
{ "Bracket", 0xff000055 }, | |||||
{ "Punctuation", 0xff004400 }, | |||||
{ "Preprocessor Text", 0xff660000 } | |||||
}; | |||||
CodeEditorComponent::ColourScheme cs; | |||||
for (unsigned int i = 0; i < sizeof (types) / sizeof (types[0]); ++i) // (NB: numElementsInArray doesn't work here in GCC4.2) | |||||
cs.set (types[i].name, Colour (types[i].colour)); | |||||
return cs; | |||||
}; | |||||
template <typename Iterator> | |||||
static void skipToEndOfXmlDTD (Iterator& source) noexcept | |||||
{ | |||||
bool lastWasQuestionMark = false; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (c == 0 || (c == '>' && lastWasQuestionMark)) | |||||
break; | |||||
lastWasQuestionMark = (c == '?'); | |||||
} | |||||
} | |||||
template <typename Iterator> | |||||
static void skipToEndOfXmlComment (Iterator& source) noexcept | |||||
{ | |||||
juce_wchar last[2] = { 0 }; | |||||
for (;;) | |||||
{ | |||||
const juce_wchar c = source.nextChar(); | |||||
if (c == 0 || (c == '>' && last[0] == '-' && last[1] == '-')) | |||||
break; | |||||
last[1] = last[0]; | |||||
last[0] = c; | |||||
} | |||||
} | |||||
int XmlTokeniser::readNextToken (CodeDocument::Iterator& source) | |||||
{ | |||||
source.skipWhitespace(); | |||||
const juce_wchar firstChar = source.peekNextChar(); | |||||
switch (firstChar) | |||||
{ | |||||
case 0: break; | |||||
case '"': | |||||
case '\'': | |||||
CppTokeniserFunctions::skipQuotedString (source); | |||||
return tokenType_string; | |||||
case '<': | |||||
{ | |||||
source.skip(); | |||||
source.skipWhitespace(); | |||||
const juce_wchar nextChar = source.peekNextChar(); | |||||
if (nextChar == '?') | |||||
{ | |||||
source.skip(); | |||||
skipToEndOfXmlDTD (source); | |||||
return tokenType_preprocessor; | |||||
} | |||||
if (nextChar == '!') | |||||
{ | |||||
source.skip(); | |||||
if (source.peekNextChar() == '-') | |||||
{ | |||||
source.skip(); | |||||
if (source.peekNextChar() == '-') | |||||
{ | |||||
skipToEndOfXmlComment (source); | |||||
return tokenType_comment; | |||||
} | |||||
} | |||||
} | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/'); | |||||
CppTokeniserFunctions::parseIdentifier (source); | |||||
source.skipWhitespace(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '/'); | |||||
source.skipWhitespace(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>'); | |||||
return tokenType_keyword; | |||||
} | |||||
case '>': | |||||
source.skip(); | |||||
return tokenType_keyword; | |||||
case '/': | |||||
source.skip(); | |||||
source.skipWhitespace(); | |||||
CppTokeniserFunctions::skipIfNextCharMatches (source, '>'); | |||||
return tokenType_keyword; | |||||
case '=': | |||||
case ':': | |||||
source.skip(); | |||||
return tokenType_operator; | |||||
default: | |||||
if (CppTokeniserFunctions::isIdentifierStart (firstChar)) | |||||
CppTokeniserFunctions::parseIdentifier (source); | |||||
source.skip(); | |||||
break; | |||||
}; | |||||
return tokenType_identifier; | |||||
} |
@@ -0,0 +1,62 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_XMLCODETOKENISER_H_INCLUDED | |||||
#define JUCE_XMLCODETOKENISER_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
*/ | |||||
class JUCE_API XmlTokeniser : public CodeTokeniser | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
XmlTokeniser(); | |||||
~XmlTokeniser(); | |||||
//============================================================================== | |||||
int readNextToken (CodeDocument::Iterator&) override; | |||||
CodeEditorComponent::ColourScheme getDefaultColourScheme() override; | |||||
/** The token values returned by this tokeniser. */ | |||||
enum TokenType | |||||
{ | |||||
tokenType_error = 0, | |||||
tokenType_comment, | |||||
tokenType_keyword, | |||||
tokenType_operator, | |||||
tokenType_identifier, | |||||
tokenType_string, | |||||
tokenType_bracket, | |||||
tokenType_punctuation, | |||||
tokenType_preprocessor | |||||
}; | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (XmlTokeniser) | |||||
}; | |||||
#endif // JUCE_XMLCODETOKENISER_H_INCLUDED |
@@ -0,0 +1,257 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
FileBasedDocument::FileBasedDocument (const String& fileExtension_, | |||||
const String& fileWildcard_, | |||||
const String& openFileDialogTitle_, | |||||
const String& saveFileDialogTitle_) | |||||
: changedSinceSave (false), | |||||
fileExtension (fileExtension_), | |||||
fileWildcard (fileWildcard_), | |||||
openFileDialogTitle (openFileDialogTitle_), | |||||
saveFileDialogTitle (saveFileDialogTitle_) | |||||
{ | |||||
} | |||||
FileBasedDocument::~FileBasedDocument() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void FileBasedDocument::setChangedFlag (const bool hasChanged) | |||||
{ | |||||
if (changedSinceSave != hasChanged) | |||||
{ | |||||
changedSinceSave = hasChanged; | |||||
sendChangeMessage(); | |||||
} | |||||
} | |||||
void FileBasedDocument::changed() | |||||
{ | |||||
changedSinceSave = true; | |||||
sendChangeMessage(); | |||||
} | |||||
//============================================================================== | |||||
void FileBasedDocument::setFile (const File& newFile) | |||||
{ | |||||
if (documentFile != newFile) | |||||
{ | |||||
documentFile = newFile; | |||||
changed(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
Result FileBasedDocument::loadFrom (const File& newFile, const bool showMessageOnFailure) | |||||
{ | |||||
MouseCursor::showWaitCursor(); | |||||
const File oldFile (documentFile); | |||||
documentFile = newFile; | |||||
Result result (Result::fail (TRANS("The file doesn't exist"))); | |||||
if (newFile.existsAsFile()) | |||||
{ | |||||
result = loadDocument (newFile); | |||||
if (result.wasOk()) | |||||
{ | |||||
setChangedFlag (false); | |||||
MouseCursor::hideWaitCursor(); | |||||
setLastDocumentOpened (newFile); | |||||
return result; | |||||
} | |||||
} | |||||
documentFile = oldFile; | |||||
MouseCursor::hideWaitCursor(); | |||||
if (showMessageOnFailure) | |||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||||
TRANS("Failed to open file..."), | |||||
TRANS("There was an error while trying to load the file: FLNM") | |||||
.replace ("FLNM", "\n" + newFile.getFullPathName()) | |||||
+ "\n\n" | |||||
+ result.getErrorMessage()); | |||||
return result; | |||||
} | |||||
#if JUCE_MODAL_LOOPS_PERMITTED | |||||
Result FileBasedDocument::loadFromUserSpecifiedFile (const bool showMessageOnFailure) | |||||
{ | |||||
FileChooser fc (openFileDialogTitle, | |||||
getLastDocumentOpened(), | |||||
fileWildcard); | |||||
if (fc.browseForFileToOpen()) | |||||
return loadFrom (fc.getResult(), showMessageOnFailure); | |||||
return Result::fail (TRANS("User cancelled")); | |||||
} | |||||
static bool askToOverwriteFile (const File& newFile) | |||||
{ | |||||
return AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, | |||||
TRANS("File already exists"), | |||||
TRANS("There's already a file called: FLMN") | |||||
.replace ("FLNM", newFile.getFullPathName()) | |||||
+ "\n\n" | |||||
+ TRANS("Are you sure you want to overwrite it?"), | |||||
TRANS("Overwrite"), | |||||
TRANS("Cancel")); | |||||
} | |||||
//============================================================================== | |||||
FileBasedDocument::SaveResult FileBasedDocument::save (const bool askUserForFileIfNotSpecified, | |||||
const bool showMessageOnFailure) | |||||
{ | |||||
return saveAs (documentFile, | |||||
false, | |||||
askUserForFileIfNotSpecified, | |||||
showMessageOnFailure); | |||||
} | |||||
FileBasedDocument::SaveResult FileBasedDocument::saveAs (const File& newFile, | |||||
const bool warnAboutOverwritingExistingFiles, | |||||
const bool askUserForFileIfNotSpecified, | |||||
const bool showMessageOnFailure) | |||||
{ | |||||
if (newFile == File::nonexistent) | |||||
{ | |||||
if (askUserForFileIfNotSpecified) | |||||
return saveAsInteractive (true); | |||||
// can't save to an unspecified file | |||||
jassertfalse; | |||||
return failedToWriteToFile; | |||||
} | |||||
if (warnAboutOverwritingExistingFiles | |||||
&& newFile.exists() | |||||
&& ! askToOverwriteFile (newFile)) | |||||
return userCancelledSave; | |||||
MouseCursor::showWaitCursor(); | |||||
const File oldFile (documentFile); | |||||
documentFile = newFile; | |||||
const Result result (saveDocument (newFile)); | |||||
if (result.wasOk()) | |||||
{ | |||||
setChangedFlag (false); | |||||
MouseCursor::hideWaitCursor(); | |||||
return savedOk; | |||||
} | |||||
documentFile = oldFile; | |||||
MouseCursor::hideWaitCursor(); | |||||
if (showMessageOnFailure) | |||||
AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, | |||||
TRANS("Error writing to file..."), | |||||
TRANS("An error occurred while trying to save \"DCNM\" to the file: FLNM") | |||||
.replace ("DCNM", getDocumentTitle()) | |||||
.replace ("FLNM", "\n" + newFile.getFullPathName()) | |||||
+ "\n\n" | |||||
+ result.getErrorMessage()); | |||||
return failedToWriteToFile; | |||||
} | |||||
FileBasedDocument::SaveResult FileBasedDocument::saveIfNeededAndUserAgrees() | |||||
{ | |||||
if (! hasChangedSinceSaved()) | |||||
return savedOk; | |||||
const int r = AlertWindow::showYesNoCancelBox (AlertWindow::QuestionIcon, | |||||
TRANS("Closing document..."), | |||||
TRANS("Do you want to save the changes to \"DCNM\"?") | |||||
.replace ("DCNM", getDocumentTitle()), | |||||
TRANS("Save"), | |||||
TRANS("Discard changes"), | |||||
TRANS("Cancel")); | |||||
if (r == 1) // save changes | |||||
return save (true, true); | |||||
if (r == 2) // discard changes | |||||
return savedOk; | |||||
return userCancelledSave; | |||||
} | |||||
File FileBasedDocument::getSuggestedSaveAsFile (const File& defaultFile) | |||||
{ | |||||
return defaultFile.withFileExtension (fileExtension).getNonexistentSibling (true); | |||||
} | |||||
FileBasedDocument::SaveResult FileBasedDocument::saveAsInteractive (const bool warnAboutOverwritingExistingFiles) | |||||
{ | |||||
File f; | |||||
if (documentFile.existsAsFile()) | |||||
f = documentFile; | |||||
else | |||||
f = getLastDocumentOpened(); | |||||
String legalFilename (File::createLegalFileName (getDocumentTitle())); | |||||
if (legalFilename.isEmpty()) | |||||
legalFilename = "unnamed"; | |||||
if (f.existsAsFile() || f.getParentDirectory().isDirectory()) | |||||
f = f.getSiblingFile (legalFilename); | |||||
else | |||||
f = File::getSpecialLocation (File::userDocumentsDirectory).getChildFile (legalFilename); | |||||
f = getSuggestedSaveAsFile (f); | |||||
FileChooser fc (saveFileDialogTitle, f, fileWildcard); | |||||
if (fc.browseForFileToSave (warnAboutOverwritingExistingFiles)) | |||||
{ | |||||
File chosen (fc.getResult()); | |||||
if (chosen.getFileExtension().isEmpty()) | |||||
{ | |||||
chosen = chosen.withFileExtension (fileExtension); | |||||
if (chosen.exists() && ! askToOverwriteFile (chosen)) | |||||
return userCancelledSave; | |||||
} | |||||
setLastDocumentOpened (chosen); | |||||
return saveAs (chosen, false, false, true); | |||||
} | |||||
return userCancelledSave; | |||||
} | |||||
#endif |
@@ -0,0 +1,294 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_FILEBASEDDOCUMENT_H_INCLUDED | |||||
#define JUCE_FILEBASEDDOCUMENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A class to take care of the logic involved with the loading/saving of some kind | |||||
of document. | |||||
There's quite a lot of tedious logic involved in writing all the load/save/save-as | |||||
functions you need for documents that get saved to a file, so this class attempts | |||||
to abstract most of the boring stuff. | |||||
Your subclass should just implement all the pure virtual methods, and you can | |||||
then use the higher-level public methods to do the load/save dialogs, to warn the user | |||||
about overwriting files, etc. | |||||
The document object keeps track of whether it has changed since it was last saved or | |||||
loaded, so when you change something, call its changed() method. This will set a | |||||
flag so it knows it needs saving, and will also broadcast a change message using the | |||||
ChangeBroadcaster base class. | |||||
@see ChangeBroadcaster | |||||
*/ | |||||
class JUCE_API FileBasedDocument : public ChangeBroadcaster | |||||
{ | |||||
public: | |||||
/** Creates a FileBasedDocument. | |||||
@param fileExtension the extension to use when loading/saving files, e.g. ".doc" | |||||
@param fileWildCard the wildcard to use in file dialogs, e.g. "*.doc" | |||||
@param openFileDialogTitle the title to show on an open-file dialog, e.g. "Choose a file to open.." | |||||
@param saveFileDialogTitle the title to show on an save-file dialog, e.g. "Choose a file to save as.." | |||||
*/ | |||||
FileBasedDocument (const String& fileExtension, | |||||
const String& fileWildCard, | |||||
const String& openFileDialogTitle, | |||||
const String& saveFileDialogTitle); | |||||
/** Destructor. */ | |||||
virtual ~FileBasedDocument(); | |||||
//============================================================================== | |||||
/** Returns true if the changed() method has been called since the file was | |||||
last saved or loaded. | |||||
@see setChangedFlag, changed | |||||
*/ | |||||
bool hasChangedSinceSaved() const { return changedSinceSave; } | |||||
/** Called to indicate that the document has changed and needs saving. | |||||
This method will also trigger a change message to be sent out using the | |||||
ChangeBroadcaster base class. | |||||
After calling the method, the hasChangedSinceSaved() method will return true, until | |||||
it is reset either by saving to a file or using the setChangedFlag() method. | |||||
@see hasChangedSinceSaved, setChangedFlag | |||||
*/ | |||||
virtual void changed(); | |||||
/** Sets the state of the 'changed' flag. | |||||
The 'changed' flag is set to true when the changed() method is called - use this method | |||||
to reset it or to set it without also broadcasting a change message. | |||||
@see changed, hasChangedSinceSaved | |||||
*/ | |||||
void setChangedFlag (bool hasChanged); | |||||
//============================================================================== | |||||
/** Tries to open a file. | |||||
If the file opens correctly, the document's file (see the getFile() method) is set | |||||
to this new one; if it fails, the document's file is left unchanged, and optionally | |||||
a message box is shown telling the user there was an error. | |||||
@returns A result indicating whether the new file loaded successfully, or the error | |||||
message if it failed. | |||||
@see loadDocument, loadFromUserSpecifiedFile | |||||
*/ | |||||
Result loadFrom (const File& fileToLoadFrom, | |||||
bool showMessageOnFailure); | |||||
/** Asks the user for a file and tries to load it. | |||||
This will pop up a dialog box using the title, file extension and | |||||
wildcard specified in the document's constructor, and asks the user | |||||
for a file. If they pick one, the loadFrom() method is used to | |||||
try to load it, optionally showing a message if it fails. | |||||
@returns a result indicating success; This will be a failure message if the user | |||||
cancelled or if they picked a file which failed to load correctly | |||||
@see loadFrom | |||||
*/ | |||||
Result loadFromUserSpecifiedFile (bool showMessageOnFailure); | |||||
//============================================================================== | |||||
/** A set of possible outcomes of one of the save() methods | |||||
*/ | |||||
enum SaveResult | |||||
{ | |||||
savedOk = 0, /**< indicates that a file was saved successfully. */ | |||||
userCancelledSave, /**< indicates that the user aborted the save operation. */ | |||||
failedToWriteToFile /**< indicates that it tried to write to a file but this failed. */ | |||||
}; | |||||
/** Tries to save the document to the last file it was saved or loaded from. | |||||
This will always try to write to the file, even if the document isn't flagged as | |||||
having changed. | |||||
@param askUserForFileIfNotSpecified if there's no file currently specified and this is | |||||
true, it will prompt the user to pick a file, as if | |||||
saveAsInteractive() was called. | |||||
@param showMessageOnFailure if true it will show a warning message when if the | |||||
save operation fails | |||||
@see saveIfNeededAndUserAgrees, saveAs, saveAsInteractive | |||||
*/ | |||||
SaveResult save (bool askUserForFileIfNotSpecified, | |||||
bool showMessageOnFailure); | |||||
/** If the file needs saving, it'll ask the user if that's what they want to do, and save | |||||
it if they say yes. | |||||
If you've got a document open and want to close it (e.g. to quit the app), this is the | |||||
method to call. | |||||
If the document doesn't need saving it'll return the value savedOk so | |||||
you can go ahead and delete the document. | |||||
If it does need saving it'll prompt the user, and if they say "discard changes" it'll | |||||
return savedOk, so again, you can safely delete the document. | |||||
If the user clicks "cancel", it'll return userCancelledSave, so if you can abort the | |||||
close-document operation. | |||||
And if they click "save changes", it'll try to save and either return savedOk, or | |||||
failedToWriteToFile if there was a problem. | |||||
@see save, saveAs, saveAsInteractive | |||||
*/ | |||||
SaveResult saveIfNeededAndUserAgrees(); | |||||
/** Tries to save the document to a specified file. | |||||
If this succeeds, it'll also change the document's internal file (as returned by | |||||
the getFile() method). If it fails, the file will be left unchanged. | |||||
@param newFile the file to try to write to | |||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask | |||||
the user first if they want to overwrite it | |||||
@param askUserForFileIfNotSpecified if the file is non-existent and this is true, it'll | |||||
use the saveAsInteractive() method to ask the user for a | |||||
filename | |||||
@param showMessageOnFailure if true and the write operation fails, it'll show | |||||
a message box to warn the user | |||||
@see saveIfNeededAndUserAgrees, save, saveAsInteractive | |||||
*/ | |||||
SaveResult saveAs (const File& newFile, | |||||
bool warnAboutOverwritingExistingFiles, | |||||
bool askUserForFileIfNotSpecified, | |||||
bool showMessageOnFailure); | |||||
/** Prompts the user for a filename and tries to save to it. | |||||
This will pop up a dialog box using the title, file extension and | |||||
wildcard specified in the document's constructor, and asks the user | |||||
for a file. If they pick one, the saveAs() method is used to try to save | |||||
to this file. | |||||
@param warnAboutOverwritingExistingFiles if true and the file exists, it'll ask | |||||
the user first if they want to overwrite it | |||||
@see saveIfNeededAndUserAgrees, save, saveAs | |||||
*/ | |||||
SaveResult saveAsInteractive (bool warnAboutOverwritingExistingFiles); | |||||
//============================================================================== | |||||
/** Returns the file that this document was last successfully saved or loaded from. | |||||
When the document object is created, this will be set to File::nonexistent. | |||||
It is changed when one of the load or save methods is used, or when setFile() | |||||
is used to explicitly set it. | |||||
*/ | |||||
const File& getFile() const { return documentFile; } | |||||
/** Sets the file that this document thinks it was loaded from. | |||||
This won't actually load anything - it just changes the file stored internally. | |||||
@see getFile | |||||
*/ | |||||
void setFile (const File& newFile); | |||||
protected: | |||||
//============================================================================== | |||||
/** Overload this to return the title of the document. | |||||
This is used in message boxes, filenames and file choosers, so it should be | |||||
something sensible. | |||||
*/ | |||||
virtual String getDocumentTitle() = 0; | |||||
/** This method should try to load your document from the given file. | |||||
@returns a Result object to indicate the whether there was an error. | |||||
*/ | |||||
virtual Result loadDocument (const File& file) = 0; | |||||
/** This method should try to write your document to the given file. | |||||
@returns a Result object to indicate the whether there was an error. | |||||
*/ | |||||
virtual Result saveDocument (const File& file) = 0; | |||||
/** This is used for dialog boxes to make them open at the last folder you | |||||
were using. | |||||
getLastDocumentOpened() and setLastDocumentOpened() are used to store | |||||
the last document that was used - you might want to store this value | |||||
in a static variable, or even in your application's properties. It should | |||||
be a global setting rather than a property of this object. | |||||
This method works very well in conjunction with a RecentlyOpenedFilesList | |||||
object to manage your recent-files list. | |||||
As a default value, it's ok to return File::nonexistent, and the document | |||||
object will use a sensible one instead. | |||||
@see RecentlyOpenedFilesList | |||||
*/ | |||||
virtual File getLastDocumentOpened() = 0; | |||||
/** This is used for dialog boxes to make them open at the last folder you | |||||
were using. | |||||
getLastDocumentOpened() and setLastDocumentOpened() are used to store | |||||
the last document that was used - you might want to store this value | |||||
in a static variable, or even in your application's properties. It should | |||||
be a global setting rather than a property of this object. | |||||
This method works very well in conjunction with a RecentlyOpenedFilesList | |||||
object to manage your recent-files list. | |||||
@see RecentlyOpenedFilesList | |||||
*/ | |||||
virtual void setLastDocumentOpened (const File& file) = 0; | |||||
#if JUCE_MODAL_LOOPS_PERMITTED | |||||
/** This is called by saveAsInteractive() to allow you to optionally customise the | |||||
filename that the user is presented with in the save dialog. | |||||
The defaultFile parameter is an initial suggestion based on what the class knows | |||||
about the current document - you can return a variation on this file with a different | |||||
extension, etc, or just return something completely different. | |||||
*/ | |||||
virtual File getSuggestedSaveAsFile (const File& defaultFile); | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
File documentFile; | |||||
bool changedSinceSave; | |||||
String fileExtension, fileWildcard, openFileDialogTitle, saveFileDialogTitle; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FileBasedDocument) | |||||
}; | |||||
#endif // JUCE_FILEBASEDDOCUMENT_H_INCLUDED |
@@ -0,0 +1,122 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_ACTIVEXCONTROLCOMPONENT_H_INCLUDED | |||||
#define JUCE_ACTIVEXCONTROLCOMPONENT_H_INCLUDED | |||||
#if JUCE_WINDOWS || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
A Windows-specific class that can create and embed an ActiveX control inside | |||||
itself. | |||||
To use it, create one of these, put it in place and make sure it's visible in a | |||||
window, then use createControl() to instantiate an ActiveX control. The control | |||||
will then be moved and resized to follow the movements of this component. | |||||
Of course, since the control is a heavyweight window, it'll obliterate any | |||||
juce components that may overlap this component, but that's life. | |||||
*/ | |||||
class JUCE_API ActiveXControlComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Create an initially-empty container. */ | |||||
ActiveXControlComponent(); | |||||
/** Destructor. */ | |||||
~ActiveXControlComponent(); | |||||
/** Tries to create an ActiveX control and embed it in this peer. | |||||
The peer controlIID is a pointer to an IID structure - it's treated | |||||
as a void* because when including the Juce headers, you might not always | |||||
have included windows.h first, in which case IID wouldn't be defined. | |||||
e.g. @code | |||||
const IID myIID = __uuidof (QTControl); | |||||
myControlComp->createControl (&myIID); | |||||
@endcode | |||||
*/ | |||||
bool createControl (const void* controlIID); | |||||
/** Deletes the ActiveX control, if one has been created. | |||||
*/ | |||||
void deleteControl(); | |||||
/** Returns true if a control is currently in use. */ | |||||
bool isControlOpen() const noexcept { return control != nullptr; } | |||||
/** Does a QueryInterface call on the embedded control object. | |||||
This allows you to cast the control to whatever type of COM object you need. | |||||
The iid parameter is a pointer to an IID structure - it's treated | |||||
as a void* because when including the Juce headers, you might not always | |||||
have included windows.h first, in which case IID wouldn't be defined, but | |||||
you should just pass a pointer to an IID. | |||||
e.g. @code | |||||
const IID iid = __uuidof (IOleWindow); | |||||
IOleWindow* oleWindow = (IOleWindow*) myControlComp->queryInterface (&iid); | |||||
if (oleWindow != nullptr) | |||||
{ | |||||
HWND hwnd; | |||||
oleWindow->GetWindow (&hwnd); | |||||
... | |||||
oleWindow->Release(); | |||||
} | |||||
@endcode | |||||
*/ | |||||
void* queryInterface (const void* iid) const; | |||||
/** Set this to false to stop mouse events being allowed through to the control. | |||||
*/ | |||||
void setMouseEventsAllowed (bool eventsCanReachControl); | |||||
/** Returns true if mouse events are allowed to get through to the control. | |||||
*/ | |||||
bool areMouseEventsAllowed() const noexcept { return mouseEventsAllowed; } | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
private: | |||||
class Pimpl; | |||||
friend struct ContainerDeletePolicy<Pimpl>; | |||||
ScopedPointer<Pimpl> control; | |||||
bool mouseEventsAllowed; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ActiveXControlComponent) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_ACTIVEXCONTROLCOMPONENT_H_INCLUDED |
@@ -0,0 +1,85 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_NSVIEWCOMPONENT_H_INCLUDED | |||||
#define JUCE_NSVIEWCOMPONENT_H_INCLUDED | |||||
#if JUCE_MAC || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
A Mac-specific class that can create and embed an NSView inside itself. | |||||
To use it, create one of these, put it in place and make sure it's visible in a | |||||
window, then use setView() to assign an NSView to it. The view will then be | |||||
moved and resized to follow the movements of this component. | |||||
Of course, since the view is a native object, it'll obliterate any | |||||
juce components that may overlap this component, but that's life. | |||||
*/ | |||||
class JUCE_API NSViewComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Create an initially-empty container. */ | |||||
NSViewComponent(); | |||||
/** Destructor. */ | |||||
~NSViewComponent(); | |||||
/** Assigns an NSView to this peer. | |||||
The view will be retained and released by this component for as long as | |||||
it is needed. To remove the current view, just call setView (nullptr). | |||||
Note: a void* is used here to avoid including the cocoa headers as | |||||
part of the juce.h, but the method expects an NSView*. | |||||
*/ | |||||
void setView (void* nsView); | |||||
/** Returns the current NSView. | |||||
Note: a void* is returned here to avoid the needing to include the cocoa | |||||
headers, so you should just cast the return value to an NSView*. | |||||
*/ | |||||
void* getView() const; | |||||
/** Resizes this component to fit the view that it contains. */ | |||||
void resizeToFitView(); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
/** @internal */ | |||||
static ReferenceCountedObject* attachViewToComponent (Component&, void*); | |||||
private: | |||||
ReferenceCountedObjectPtr<ReferenceCountedObject> attachment; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewComponent) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_NSVIEWCOMPONENT_H_INCLUDED |
@@ -0,0 +1,86 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_UIVIEWCOMPONENT_H_INCLUDED | |||||
#define JUCE_UIVIEWCOMPONENT_H_INCLUDED | |||||
#if JUCE_IOS || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
An iOS-specific class that can create and embed an UIView inside itself. | |||||
To use it, create one of these, put it in place and make sure it's visible in a | |||||
window, then use setView() to assign a UIView to it. The view will then be | |||||
moved and resized to follow the movements of this component. | |||||
Of course, since the view is a native object, it'll obliterate any | |||||
juce components that may overlap this component, but that's life. | |||||
*/ | |||||
class JUCE_API UIViewComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Create an initially-empty container. */ | |||||
UIViewComponent(); | |||||
/** Destructor. */ | |||||
~UIViewComponent(); | |||||
/** Assigns an UIView to this peer. | |||||
The view will be retained and released by this component for as long as | |||||
it is needed. To remove the current view, just call setView (nullptr). | |||||
Note: a void* is used here to avoid including the cocoa headers as | |||||
part of the juce.h, but the method expects an UIView*. | |||||
*/ | |||||
void setView (void* uiView); | |||||
/** Returns the current UIView. | |||||
Note: a void* is returned here to avoid the needing to include the cocoa | |||||
headers, so you should just cast the return value to an UIView*. | |||||
*/ | |||||
void* getView() const; | |||||
/** Resizes this component to fit the view that it contains. */ | |||||
void resizeToFitView(); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
private: | |||||
class Pimpl; | |||||
friend class Pimpl; | |||||
ScopedPointer<Pimpl> pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (UIViewComponent) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_UIVIEWCOMPONENT_H_INCLUDED |
@@ -0,0 +1,155 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if defined (JUCE_GUI_EXTRA_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||||
already included any other headers - just put it inside a file on its own, possibly with your config | |||||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||||
header files that the compiler may be using. | |||||
*/ | |||||
#error "Incorrect use of JUCE cpp file" | |||||
#endif | |||||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||||
// and your header search path must make it accessible to the module's files. | |||||
#include "AppConfig.h" | |||||
#include "../juce_core/native/juce_BasicNativeHeaders.h" | |||||
#include "juce_gui_extra.h" | |||||
//============================================================================== | |||||
#if JUCE_MAC | |||||
#define Point CarbonDummyPointName | |||||
#define Component CarbonDummyCompName | |||||
#import <WebKit/WebKit.h> | |||||
#import <IOKit/IOKitLib.h> | |||||
#import <IOKit/IOCFPlugIn.h> | |||||
#import <IOKit/hid/IOHIDLib.h> | |||||
#import <IOKit/hid/IOHIDKeys.h> | |||||
#import <IOKit/pwr_mgt/IOPMLib.h> | |||||
#import <Carbon/Carbon.h> // still needed for SetSystemUIMode() | |||||
#undef Point | |||||
#undef Component | |||||
#elif JUCE_IOS | |||||
//============================================================================== | |||||
#elif JUCE_WINDOWS | |||||
#include <windowsx.h> | |||||
#include <vfw.h> | |||||
#include <commdlg.h> | |||||
#if JUCE_WEB_BROWSER | |||||
#include <exdisp.h> | |||||
#include <exdispid.h> | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_LINUX | |||||
#include <X11/Xlib.h> | |||||
#include <X11/Xatom.h> | |||||
#include <X11/Xutil.h> | |||||
#undef SIZEOF | |||||
#undef KeyPress | |||||
#endif | |||||
//============================================================================== | |||||
namespace juce | |||||
{ | |||||
#if JUCE_MAC || JUCE_IOS | |||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||||
#endif | |||||
#include "documents/juce_FileBasedDocument.cpp" | |||||
#include "code_editor/juce_CodeDocument.cpp" | |||||
#include "code_editor/juce_CodeEditorComponent.cpp" | |||||
#include "code_editor/juce_CPlusPlusCodeTokeniser.cpp" | |||||
#include "code_editor/juce_XMLCodeTokeniser.cpp" | |||||
#include "code_editor/juce_LuaCodeTokeniser.cpp" | |||||
#include "misc/juce_BubbleMessageComponent.cpp" | |||||
#include "misc/juce_ColourSelector.cpp" | |||||
#include "misc/juce_KeyMappingEditorComponent.cpp" | |||||
#include "misc/juce_PreferencesPanel.cpp" | |||||
#include "misc/juce_RecentlyOpenedFilesList.cpp" | |||||
#include "misc/juce_SplashScreen.cpp" | |||||
#include "misc/juce_SystemTrayIconComponent.cpp" | |||||
#include "misc/juce_LiveConstantEditor.cpp" | |||||
} | |||||
using namespace juce; | |||||
namespace juce | |||||
{ | |||||
//============================================================================== | |||||
#if JUCE_MAC || JUCE_IOS | |||||
#include "../juce_core/native/juce_osx_ObjCHelpers.h" | |||||
#include "../juce_graphics/native/juce_mac_CoreGraphicsHelpers.h" | |||||
#if JUCE_MAC | |||||
#include "native/juce_mac_NSViewComponent.mm" | |||||
#include "native/juce_mac_AppleRemote.mm" | |||||
#include "native/juce_mac_SystemTrayIcon.cpp" | |||||
#endif | |||||
#if JUCE_IOS | |||||
#include "native/juce_ios_UIViewComponent.mm" | |||||
#endif | |||||
#if JUCE_WEB_BROWSER | |||||
#include "native/juce_mac_WebBrowserComponent.mm" | |||||
#endif | |||||
//============================================================================== | |||||
#elif JUCE_WINDOWS | |||||
#include "../juce_core/native/juce_win32_ComSmartPtr.h" | |||||
#include "../juce_events/native/juce_win32_HiddenMessageWindow.h" | |||||
#include "native/juce_win32_ActiveXComponent.cpp" | |||||
#if JUCE_WEB_BROWSER | |||||
#include "native/juce_win32_WebBrowserComponent.cpp" | |||||
#endif | |||||
#include "native/juce_win32_SystemTrayIcon.cpp" | |||||
//============================================================================== | |||||
#elif JUCE_LINUX | |||||
#if JUCE_WEB_BROWSER | |||||
#include "native/juce_linux_WebBrowserComponent.cpp" | |||||
#endif | |||||
#include "native/juce_linux_SystemTrayIcon.cpp" | |||||
//============================================================================== | |||||
#elif JUCE_ANDROID | |||||
#if JUCE_WEB_BROWSER | |||||
#include "native/juce_android_WebBrowserComponent.cpp" | |||||
#endif | |||||
#endif | |||||
#if JUCE_WEB_BROWSER | |||||
bool WebBrowserComponent::pageAboutToLoad (const String&) { return true; } | |||||
void WebBrowserComponent::pageFinishedLoading (const String&) {} | |||||
void WebBrowserComponent::windowCloseRequest() {} | |||||
#endif | |||||
} |
@@ -0,0 +1,78 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_GUI_EXTRA_H_INCLUDED | |||||
#define JUCE_GUI_EXTRA_H_INCLUDED | |||||
#include "../juce_gui_basics/juce_gui_basics.h" | |||||
//============================================================================= | |||||
/** Config: JUCE_WEB_BROWSER | |||||
This lets you disable the WebBrowserComponent class (Mac and Windows). | |||||
If you're not using any embedded web-pages, turning this off may reduce your code size. | |||||
*/ | |||||
#ifndef JUCE_WEB_BROWSER | |||||
#define JUCE_WEB_BROWSER 1 | |||||
#endif | |||||
/** Config: JUCE_ENABLE_LIVE_CONSTANT_EDITOR | |||||
This lets you turn on the JUCE_ENABLE_LIVE_CONSTANT_EDITOR support. See the documentation | |||||
for that macro for more details. | |||||
*/ | |||||
#ifndef JUCE_ENABLE_LIVE_CONSTANT_EDITOR | |||||
#if JUCE_DEBUG | |||||
#define JUCE_ENABLE_LIVE_CONSTANT_EDITOR 1 | |||||
#endif | |||||
#endif | |||||
//============================================================================= | |||||
namespace juce | |||||
{ | |||||
#include "documents/juce_FileBasedDocument.h" | |||||
#include "code_editor/juce_CodeDocument.h" | |||||
#include "code_editor/juce_CodeEditorComponent.h" | |||||
#include "code_editor/juce_CodeTokeniser.h" | |||||
#include "code_editor/juce_CPlusPlusCodeTokeniser.h" | |||||
#include "code_editor/juce_CPlusPlusCodeTokeniserFunctions.h" | |||||
#include "code_editor/juce_XMLCodeTokeniser.h" | |||||
#include "code_editor/juce_LuaCodeTokeniser.h" | |||||
#include "embedding/juce_ActiveXControlComponent.h" | |||||
#include "embedding/juce_NSViewComponent.h" | |||||
#include "embedding/juce_UIViewComponent.h" | |||||
#include "misc/juce_AppleRemote.h" | |||||
#include "misc/juce_BubbleMessageComponent.h" | |||||
#include "misc/juce_ColourSelector.h" | |||||
#include "misc/juce_KeyMappingEditorComponent.h" | |||||
#include "misc/juce_PreferencesPanel.h" | |||||
#include "misc/juce_RecentlyOpenedFilesList.h" | |||||
#include "misc/juce_SplashScreen.h" | |||||
#include "misc/juce_SystemTrayIconComponent.h" | |||||
#include "misc/juce_WebBrowserComponent.h" | |||||
#include "misc/juce_LiveConstantEditor.h" | |||||
} | |||||
#endif // JUCE_GUI_EXTRA_H_INCLUDED |
@@ -0,0 +1,25 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#include "juce_gui_extra.cpp" |
@@ -0,0 +1,114 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_APPLEREMOTE_H_INCLUDED | |||||
#define JUCE_APPLEREMOTE_H_INCLUDED | |||||
//============================================================================== | |||||
#if JUCE_MAC || DOXYGEN | |||||
/** | |||||
Receives events from an Apple IR remote control device (Only available in OSX!). | |||||
To use it, just create a subclass of this class, implementing the buttonPressed() | |||||
callback, then call start() and stop() to start or stop receiving events. | |||||
*/ | |||||
class JUCE_API AppleRemoteDevice | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
AppleRemoteDevice(); | |||||
virtual ~AppleRemoteDevice(); | |||||
//============================================================================== | |||||
/** The set of buttons that may be pressed. | |||||
@see buttonPressed | |||||
*/ | |||||
enum ButtonType | |||||
{ | |||||
menuButton = 0, /**< The menu button (if it's held for a short time). */ | |||||
playButton, /**< The play button. */ | |||||
plusButton, /**< The plus or volume-up button. */ | |||||
minusButton, /**< The minus or volume-down button. */ | |||||
rightButton, /**< The right button (if it's held for a short time). */ | |||||
leftButton, /**< The left button (if it's held for a short time). */ | |||||
rightButton_Long, /**< The right button (if it's held for a long time). */ | |||||
leftButton_Long, /**< The menu button (if it's held for a long time). */ | |||||
menuButton_Long, /**< The menu button (if it's held for a long time). */ | |||||
playButtonSleepMode, | |||||
switched | |||||
}; | |||||
//============================================================================== | |||||
/** Override this method to receive the callback about a button press. | |||||
The callback will happen on the application's message thread. | |||||
Some buttons trigger matching up and down events, in which the isDown parameter | |||||
will be true and then false. Others only send a single event when the | |||||
button is pressed. | |||||
*/ | |||||
virtual void buttonPressed (ButtonType buttonId, bool isDown) = 0; | |||||
//============================================================================== | |||||
/** Starts the device running and responding to events. | |||||
Returns true if it managed to open the device. | |||||
@param inExclusiveMode if true, the remote will be grabbed exclusively for this app, | |||||
and will not be available to any other part of the system. If | |||||
false, it will be shared with other apps. | |||||
@see stop | |||||
*/ | |||||
bool start (bool inExclusiveMode); | |||||
/** Stops the device running. | |||||
@see start | |||||
*/ | |||||
void stop(); | |||||
/** Returns true if the device has been started successfully. | |||||
*/ | |||||
bool isActive() const; | |||||
/** Returns the ID number of the remote, if it has sent one. | |||||
*/ | |||||
int getRemoteId() const { return remoteId; } | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void handleCallbackInternal(); | |||||
private: | |||||
void* device; | |||||
void* queue; | |||||
int remoteId; | |||||
bool open (bool openInExclusiveMode); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AppleRemoteDevice) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_APPLEREMOTE_H_INCLUDED |
@@ -0,0 +1,118 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
BubbleMessageComponent::BubbleMessageComponent (int fadeOutLengthMs) | |||||
: fadeOutLength (fadeOutLengthMs), mouseClickCounter (0), | |||||
expiryTime (0), deleteAfterUse (false) | |||||
{ | |||||
} | |||||
BubbleMessageComponent::~BubbleMessageComponent() | |||||
{ | |||||
} | |||||
void BubbleMessageComponent::showAt (const Rectangle<int>& pos, | |||||
const AttributedString& text, | |||||
const int numMillisecondsBeforeRemoving, | |||||
const bool removeWhenMouseClicked, | |||||
const bool deleteSelfAfterUse) | |||||
{ | |||||
createLayout (text); | |||||
setPosition (pos); | |||||
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse); | |||||
} | |||||
void BubbleMessageComponent::showAt (Component* const component, | |||||
const AttributedString& text, | |||||
const int numMillisecondsBeforeRemoving, | |||||
const bool removeWhenMouseClicked, | |||||
const bool deleteSelfAfterUse) | |||||
{ | |||||
createLayout (text); | |||||
setPosition (component); | |||||
init (numMillisecondsBeforeRemoving, removeWhenMouseClicked, deleteSelfAfterUse); | |||||
} | |||||
void BubbleMessageComponent::createLayout (const AttributedString& text) | |||||
{ | |||||
textLayout.createLayoutWithBalancedLineLengths (text, 256); | |||||
} | |||||
void BubbleMessageComponent::init (const int numMillisecondsBeforeRemoving, | |||||
const bool removeWhenMouseClicked, | |||||
const bool deleteSelfAfterUse) | |||||
{ | |||||
setAlpha (1.0f); | |||||
setVisible (true); | |||||
deleteAfterUse = deleteSelfAfterUse; | |||||
expiryTime = numMillisecondsBeforeRemoving > 0 | |||||
? (Time::getMillisecondCounter() + (uint32) numMillisecondsBeforeRemoving) : 0; | |||||
mouseClickCounter = Desktop::getInstance().getMouseButtonClickCounter(); | |||||
if (! (removeWhenMouseClicked && isShowing())) | |||||
mouseClickCounter += 0xfffff; | |||||
startTimer (77); | |||||
repaint(); | |||||
} | |||||
const float bubblePaddingX = 20.0f; | |||||
const float bubblePaddingY = 14.0f; | |||||
void BubbleMessageComponent::getContentSize (int& w, int& h) | |||||
{ | |||||
w = (int) (bubblePaddingX + textLayout.getWidth()); | |||||
h = (int) (bubblePaddingY + textLayout.getHeight()); | |||||
} | |||||
void BubbleMessageComponent::paintContent (Graphics& g, int w, int h) | |||||
{ | |||||
g.setColour (findColour (TooltipWindow::textColourId)); | |||||
textLayout.draw (g, Rectangle<float> (bubblePaddingX / 2.0f, bubblePaddingY / 2.0f, | |||||
w - bubblePaddingX, h - bubblePaddingY)); | |||||
} | |||||
void BubbleMessageComponent::timerCallback() | |||||
{ | |||||
if (Desktop::getInstance().getMouseButtonClickCounter() > mouseClickCounter) | |||||
hide (false); | |||||
else if (expiryTime != 0 && Time::getMillisecondCounter() > expiryTime) | |||||
hide (true); | |||||
} | |||||
void BubbleMessageComponent::hide (const bool fadeOut) | |||||
{ | |||||
stopTimer(); | |||||
if (fadeOut) | |||||
Desktop::getInstance().getAnimator().fadeOut (this, fadeOutLength); | |||||
else | |||||
setVisible (false); | |||||
if (deleteAfterUse) | |||||
delete this; | |||||
} |
@@ -0,0 +1,132 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_BUBBLEMESSAGECOMPONENT_H_INCLUDED | |||||
#define JUCE_BUBBLEMESSAGECOMPONENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A speech-bubble component that displays a short message. | |||||
This can be used to show a message with the tail of the speech bubble | |||||
pointing to a particular component or location on the screen. | |||||
@see BubbleComponent | |||||
*/ | |||||
class JUCE_API BubbleMessageComponent : public BubbleComponent, | |||||
private Timer | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a bubble component. | |||||
After creating one a BubbleComponent, do the following: | |||||
- add it to an appropriate parent component, or put it on the | |||||
desktop with Component::addToDesktop (0). | |||||
- use the showAt() method to show a message. | |||||
- it will make itself invisible after it times-out (and can optionally | |||||
also delete itself), or you can reuse it somewhere else by calling | |||||
showAt() again. | |||||
*/ | |||||
BubbleMessageComponent (int fadeOutLengthMs = 150); | |||||
/** Destructor. */ | |||||
~BubbleMessageComponent(); | |||||
//============================================================================== | |||||
/** Shows a message bubble at a particular position. | |||||
This shows the bubble with its stem pointing to the given location | |||||
(coordinates being relative to its parent component). | |||||
For details about exactly how it decides where to position itself, see | |||||
BubbleComponent::updatePosition(). | |||||
@param position the coords of the object to point to | |||||
@param message the text to display | |||||
@param numMillisecondsBeforeRemoving how long to leave it on the screen before removing itself | |||||
from its parent compnent. If this is 0 or less, it | |||||
will stay there until manually removed. | |||||
@param removeWhenMouseClicked if this is true, the bubble will disappear as soon as a | |||||
mouse button is pressed (anywhere on the screen) | |||||
@param deleteSelfAfterUse if true, then the component will delete itself after | |||||
it becomes invisible | |||||
*/ | |||||
void showAt (const Rectangle<int>& position, | |||||
const AttributedString& message, | |||||
int numMillisecondsBeforeRemoving, | |||||
bool removeWhenMouseClicked = true, | |||||
bool deleteSelfAfterUse = false); | |||||
/** Shows a message bubble next to a particular component. | |||||
This shows the bubble with its stem pointing at the given component. | |||||
For details about exactly how it decides where to position itself, see | |||||
BubbleComponent::updatePosition(). | |||||
@param component the component that you want to point at | |||||
@param message the text to display | |||||
@param numMillisecondsBeforeRemoving how long to leave it on the screen before removing itself | |||||
from its parent compnent. If this is 0 or less, it | |||||
will stay there until manually removed. | |||||
@param removeWhenMouseClicked if this is true, the bubble will disappear as soon as a | |||||
mouse button is pressed (anywhere on the screen) | |||||
@param deleteSelfAfterUse if true, then the component will delete itself after | |||||
it becomes invisible | |||||
*/ | |||||
void showAt (Component* component, | |||||
const AttributedString& message, | |||||
int numMillisecondsBeforeRemoving, | |||||
bool removeWhenMouseClicked = true, | |||||
bool deleteSelfAfterUse = false); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void getContentSize (int& w, int& h); | |||||
/** @internal */ | |||||
void paintContent (Graphics& g, int w, int h); | |||||
/** @internal */ | |||||
void timerCallback(); | |||||
private: | |||||
//============================================================================== | |||||
int fadeOutLength, mouseClickCounter; | |||||
TextLayout textLayout; | |||||
int64 expiryTime; | |||||
bool deleteAfterUse; | |||||
void createLayout (const AttributedString&); | |||||
void init (int numMillisecondsBeforeRemoving, | |||||
bool removeWhenMouseClicked, | |||||
bool deleteSelfAfterUse); | |||||
void hide (bool fadeOut); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BubbleMessageComponent) | |||||
}; | |||||
#endif // JUCE_BUBBLEMESSAGECOMPONENT_H_INCLUDED |
@@ -0,0 +1,566 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class ColourSelector::ColourComponentSlider : public Slider | |||||
{ | |||||
public: | |||||
ColourComponentSlider (const String& name) | |||||
: Slider (name) | |||||
{ | |||||
setRange (0.0, 255.0, 1.0); | |||||
} | |||||
String getTextFromValue (double value) | |||||
{ | |||||
return String::toHexString ((int) value).toUpperCase().paddedLeft ('0', 2); | |||||
} | |||||
double getValueFromText (const String& text) | |||||
{ | |||||
return (double) text.getHexValue32(); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (ColourComponentSlider) | |||||
}; | |||||
//============================================================================== | |||||
class ColourSelector::ColourSpaceMarker : public Component | |||||
{ | |||||
public: | |||||
ColourSpaceMarker() | |||||
{ | |||||
setInterceptsMouseClicks (false, false); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
g.setColour (Colour::greyLevel (0.1f)); | |||||
g.drawEllipse (1.0f, 1.0f, getWidth() - 2.0f, getHeight() - 2.0f, 1.0f); | |||||
g.setColour (Colour::greyLevel (0.9f)); | |||||
g.drawEllipse (2.0f, 2.0f, getWidth() - 4.0f, getHeight() - 4.0f, 1.0f); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (ColourSpaceMarker) | |||||
}; | |||||
//============================================================================== | |||||
class ColourSelector::ColourSpaceView : public Component | |||||
{ | |||||
public: | |||||
ColourSpaceView (ColourSelector& cs, float& hue, float& sat, float& val, const int edgeSize) | |||||
: owner (cs), h (hue), s (sat), v (val), lastHue (0.0f), edge (edgeSize) | |||||
{ | |||||
addAndMakeVisible (marker); | |||||
setMouseCursor (MouseCursor::CrosshairCursor); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
if (colours.isNull()) | |||||
{ | |||||
const int width = getWidth() / 2; | |||||
const int height = getHeight() / 2; | |||||
colours = Image (Image::RGB, width, height, false); | |||||
Image::BitmapData pixels (colours, Image::BitmapData::writeOnly); | |||||
for (int y = 0; y < height; ++y) | |||||
{ | |||||
const float val = 1.0f - y / (float) height; | |||||
for (int x = 0; x < width; ++x) | |||||
{ | |||||
const float sat = x / (float) width; | |||||
pixels.setPixelColour (x, y, Colour (h, sat, val, 1.0f)); | |||||
} | |||||
} | |||||
} | |||||
g.setOpacity (1.0f); | |||||
g.drawImageTransformed (colours, | |||||
RectanglePlacement (RectanglePlacement::stretchToFit) | |||||
.getTransformToFit (colours.getBounds().toFloat(), | |||||
getLocalBounds().reduced (edge).toFloat()), | |||||
false); | |||||
} | |||||
void mouseDown (const MouseEvent& e) override | |||||
{ | |||||
mouseDrag (e); | |||||
} | |||||
void mouseDrag (const MouseEvent& e) override | |||||
{ | |||||
const float sat = (e.x - edge) / (float) (getWidth() - edge * 2); | |||||
const float val = 1.0f - (e.y - edge) / (float) (getHeight() - edge * 2); | |||||
owner.setSV (sat, val); | |||||
} | |||||
void updateIfNeeded() | |||||
{ | |||||
if (lastHue != h) | |||||
{ | |||||
lastHue = h; | |||||
colours = Image::null; | |||||
repaint(); | |||||
} | |||||
updateMarker(); | |||||
} | |||||
void resized() override | |||||
{ | |||||
colours = Image::null; | |||||
updateMarker(); | |||||
} | |||||
private: | |||||
ColourSelector& owner; | |||||
float& h; | |||||
float& s; | |||||
float& v; | |||||
float lastHue; | |||||
ColourSpaceMarker marker; | |||||
const int edge; | |||||
Image colours; | |||||
void updateMarker() | |||||
{ | |||||
marker.setBounds (roundToInt ((getWidth() - edge * 2) * s), | |||||
roundToInt ((getHeight() - edge * 2) * (1.0f - v)), | |||||
edge * 2, edge * 2); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (ColourSpaceView) | |||||
}; | |||||
//============================================================================== | |||||
class ColourSelector::HueSelectorMarker : public Component | |||||
{ | |||||
public: | |||||
HueSelectorMarker() | |||||
{ | |||||
setInterceptsMouseClicks (false, false); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
const float cw = (float) getWidth(); | |||||
const float ch = (float) getHeight(); | |||||
Path p; | |||||
p.addTriangle (1.0f, 1.0f, | |||||
cw * 0.3f, ch * 0.5f, | |||||
1.0f, ch - 1.0f); | |||||
p.addTriangle (cw - 1.0f, 1.0f, | |||||
cw * 0.7f, ch * 0.5f, | |||||
cw - 1.0f, ch - 1.0f); | |||||
g.setColour (Colours::white.withAlpha (0.75f)); | |||||
g.fillPath (p); | |||||
g.setColour (Colours::black.withAlpha (0.75f)); | |||||
g.strokePath (p, PathStrokeType (1.2f)); | |||||
} | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE (HueSelectorMarker) | |||||
}; | |||||
//============================================================================== | |||||
class ColourSelector::HueSelectorComp : public Component | |||||
{ | |||||
public: | |||||
HueSelectorComp (ColourSelector& cs, float& hue, const int edgeSize) | |||||
: owner (cs), h (hue), edge (edgeSize) | |||||
{ | |||||
addAndMakeVisible (marker); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
ColourGradient cg; | |||||
cg.isRadial = false; | |||||
cg.point1.setXY (0.0f, (float) edge); | |||||
cg.point2.setXY (0.0f, (float) (getHeight() - edge)); | |||||
for (float i = 0.0f; i <= 1.0f; i += 0.02f) | |||||
cg.addColour (i, Colour (i, 1.0f, 1.0f, 1.0f)); | |||||
g.setGradientFill (cg); | |||||
g.fillRect (getLocalBounds().reduced (edge)); | |||||
} | |||||
void resized() override | |||||
{ | |||||
marker.setBounds (0, roundToInt ((getHeight() - edge * 2) * h), getWidth(), edge * 2); | |||||
} | |||||
void mouseDown (const MouseEvent& e) override | |||||
{ | |||||
mouseDrag (e); | |||||
} | |||||
void mouseDrag (const MouseEvent& e) override | |||||
{ | |||||
owner.setHue ((e.y - edge) / (float) (getHeight() - edge * 2)); | |||||
} | |||||
void updateIfNeeded() | |||||
{ | |||||
resized(); | |||||
} | |||||
private: | |||||
ColourSelector& owner; | |||||
float& h; | |||||
HueSelectorMarker marker; | |||||
const int edge; | |||||
JUCE_DECLARE_NON_COPYABLE (HueSelectorComp) | |||||
}; | |||||
//============================================================================== | |||||
class ColourSelector::SwatchComponent : public Component | |||||
{ | |||||
public: | |||||
SwatchComponent (ColourSelector& cs, int itemIndex) | |||||
: owner (cs), index (itemIndex) | |||||
{ | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
const Colour c (owner.getSwatchColour (index)); | |||||
g.fillCheckerBoard (getLocalBounds(), 6, 6, | |||||
Colour (0xffdddddd).overlaidWith (c), | |||||
Colour (0xffffffff).overlaidWith (c)); | |||||
} | |||||
void mouseDown (const MouseEvent&) override | |||||
{ | |||||
PopupMenu m; | |||||
m.addItem (1, TRANS("Use this swatch as the current colour")); | |||||
m.addSeparator(); | |||||
m.addItem (2, TRANS("Set this swatch to the current colour")); | |||||
m.showMenuAsync (PopupMenu::Options().withTargetComponent (this), | |||||
ModalCallbackFunction::forComponent (menuStaticCallback, this)); | |||||
} | |||||
private: | |||||
ColourSelector& owner; | |||||
const int index; | |||||
static void menuStaticCallback (int result, SwatchComponent* comp) | |||||
{ | |||||
if (comp != nullptr) | |||||
{ | |||||
if (result == 1) | |||||
comp->setColourFromSwatch(); | |||||
else if (result == 2) | |||||
comp->setSwatchFromColour(); | |||||
} | |||||
} | |||||
void setColourFromSwatch() | |||||
{ | |||||
owner.setCurrentColour (owner.getSwatchColour (index)); | |||||
} | |||||
void setSwatchFromColour() | |||||
{ | |||||
if (owner.getSwatchColour (index) != owner.getCurrentColour()) | |||||
{ | |||||
owner.setSwatchColour (index, owner.getCurrentColour()); | |||||
repaint(); | |||||
} | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (SwatchComponent) | |||||
}; | |||||
//============================================================================== | |||||
ColourSelector::ColourSelector (const int sectionsToShow, const int edge, const int gapAroundColourSpaceComponent) | |||||
: colour (Colours::white), | |||||
flags (sectionsToShow), | |||||
edgeGap (edge) | |||||
{ | |||||
// not much point having a selector with no components in it! | |||||
jassert ((flags & (showColourAtTop | showSliders | showColourspace)) != 0); | |||||
updateHSV(); | |||||
if ((flags & showSliders) != 0) | |||||
{ | |||||
addAndMakeVisible (sliders[0] = new ColourComponentSlider (TRANS ("red"))); | |||||
addAndMakeVisible (sliders[1] = new ColourComponentSlider (TRANS ("green"))); | |||||
addAndMakeVisible (sliders[2] = new ColourComponentSlider (TRANS ("blue"))); | |||||
addChildComponent (sliders[3] = new ColourComponentSlider (TRANS ("alpha"))); | |||||
sliders[3]->setVisible ((flags & showAlphaChannel) != 0); | |||||
for (int i = 4; --i >= 0;) | |||||
sliders[i]->addListener (this); | |||||
} | |||||
if ((flags & showColourspace) != 0) | |||||
{ | |||||
addAndMakeVisible (colourSpace = new ColourSpaceView (*this, h, s, v, gapAroundColourSpaceComponent)); | |||||
addAndMakeVisible (hueSelector = new HueSelectorComp (*this, h, gapAroundColourSpaceComponent)); | |||||
} | |||||
update(); | |||||
} | |||||
ColourSelector::~ColourSelector() | |||||
{ | |||||
dispatchPendingMessages(); | |||||
swatchComponents.clear(); | |||||
} | |||||
//============================================================================== | |||||
Colour ColourSelector::getCurrentColour() const | |||||
{ | |||||
return ((flags & showAlphaChannel) != 0) ? colour : colour.withAlpha ((uint8) 0xff); | |||||
} | |||||
void ColourSelector::setCurrentColour (Colour c) | |||||
{ | |||||
if (c != colour) | |||||
{ | |||||
colour = ((flags & showAlphaChannel) != 0) ? c : c.withAlpha ((uint8) 0xff); | |||||
updateHSV(); | |||||
update(); | |||||
} | |||||
} | |||||
void ColourSelector::setHue (float newH) | |||||
{ | |||||
newH = jlimit (0.0f, 1.0f, newH); | |||||
if (h != newH) | |||||
{ | |||||
h = newH; | |||||
colour = Colour (h, s, v, colour.getFloatAlpha()); | |||||
update(); | |||||
} | |||||
} | |||||
void ColourSelector::setSV (float newS, float newV) | |||||
{ | |||||
newS = jlimit (0.0f, 1.0f, newS); | |||||
newV = jlimit (0.0f, 1.0f, newV); | |||||
if (s != newS || v != newV) | |||||
{ | |||||
s = newS; | |||||
v = newV; | |||||
colour = Colour (h, s, v, colour.getFloatAlpha()); | |||||
update(); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void ColourSelector::updateHSV() | |||||
{ | |||||
colour.getHSB (h, s, v); | |||||
} | |||||
void ColourSelector::update() | |||||
{ | |||||
if (sliders[0] != nullptr) | |||||
{ | |||||
sliders[0]->setValue ((int) colour.getRed()); | |||||
sliders[1]->setValue ((int) colour.getGreen()); | |||||
sliders[2]->setValue ((int) colour.getBlue()); | |||||
sliders[3]->setValue ((int) colour.getAlpha()); | |||||
} | |||||
if (colourSpace != nullptr) | |||||
{ | |||||
colourSpace->updateIfNeeded(); | |||||
hueSelector->updateIfNeeded(); | |||||
} | |||||
if ((flags & showColourAtTop) != 0) | |||||
repaint (previewArea); | |||||
sendChangeMessage(); | |||||
} | |||||
//============================================================================== | |||||
void ColourSelector::paint (Graphics& g) | |||||
{ | |||||
g.fillAll (findColour (backgroundColourId)); | |||||
if ((flags & showColourAtTop) != 0) | |||||
{ | |||||
const Colour currentColour (getCurrentColour()); | |||||
g.fillCheckerBoard (previewArea, 10, 10, | |||||
Colour (0xffdddddd).overlaidWith (currentColour), | |||||
Colour (0xffffffff).overlaidWith (currentColour)); | |||||
g.setColour (Colours::white.overlaidWith (currentColour).contrasting()); | |||||
g.setFont (Font (14.0f, Font::bold)); | |||||
g.drawText (currentColour.toDisplayString ((flags & showAlphaChannel) != 0), | |||||
previewArea, Justification::centred, false); | |||||
} | |||||
if ((flags & showSliders) != 0) | |||||
{ | |||||
g.setColour (findColour (labelTextColourId)); | |||||
g.setFont (11.0f); | |||||
for (int i = 4; --i >= 0;) | |||||
{ | |||||
if (sliders[i]->isVisible()) | |||||
g.drawText (sliders[i]->getName() + ":", | |||||
0, sliders[i]->getY(), | |||||
sliders[i]->getX() - 8, sliders[i]->getHeight(), | |||||
Justification::centredRight, false); | |||||
} | |||||
} | |||||
} | |||||
void ColourSelector::resized() | |||||
{ | |||||
const int swatchesPerRow = 8; | |||||
const int swatchHeight = 22; | |||||
const int numSliders = ((flags & showAlphaChannel) != 0) ? 4 : 3; | |||||
const int numSwatches = getNumSwatches(); | |||||
const int swatchSpace = numSwatches > 0 ? edgeGap + swatchHeight * ((numSwatches + 7) / swatchesPerRow) : 0; | |||||
const int sliderSpace = ((flags & showSliders) != 0) ? jmin (22 * numSliders + edgeGap, proportionOfHeight (0.3f)) : 0; | |||||
const int topSpace = ((flags & showColourAtTop) != 0) ? jmin (30 + edgeGap * 2, proportionOfHeight (0.2f)) : edgeGap; | |||||
previewArea.setBounds (edgeGap, edgeGap, getWidth() - edgeGap * 2, topSpace - edgeGap * 2); | |||||
int y = topSpace; | |||||
if ((flags & showColourspace) != 0) | |||||
{ | |||||
const int hueWidth = jmin (50, proportionOfWidth (0.15f)); | |||||
colourSpace->setBounds (edgeGap, y, | |||||
getWidth() - hueWidth - edgeGap - 4, | |||||
getHeight() - topSpace - sliderSpace - swatchSpace - edgeGap); | |||||
hueSelector->setBounds (colourSpace->getRight() + 4, y, | |||||
getWidth() - edgeGap - (colourSpace->getRight() + 4), | |||||
colourSpace->getHeight()); | |||||
y = getHeight() - sliderSpace - swatchSpace - edgeGap; | |||||
} | |||||
if ((flags & showSliders) != 0) | |||||
{ | |||||
const int sliderHeight = jmax (4, sliderSpace / numSliders); | |||||
for (int i = 0; i < numSliders; ++i) | |||||
{ | |||||
sliders[i]->setBounds (proportionOfWidth (0.2f), y, | |||||
proportionOfWidth (0.72f), sliderHeight - 2); | |||||
y += sliderHeight; | |||||
} | |||||
} | |||||
if (numSwatches > 0) | |||||
{ | |||||
const int startX = 8; | |||||
const int xGap = 4; | |||||
const int yGap = 4; | |||||
const int swatchWidth = (getWidth() - startX * 2) / swatchesPerRow; | |||||
y += edgeGap; | |||||
if (swatchComponents.size() != numSwatches) | |||||
{ | |||||
swatchComponents.clear(); | |||||
for (int i = 0; i < numSwatches; ++i) | |||||
{ | |||||
SwatchComponent* const sc = new SwatchComponent (*this, i); | |||||
swatchComponents.add (sc); | |||||
addAndMakeVisible (sc); | |||||
} | |||||
} | |||||
int x = startX; | |||||
for (int i = 0; i < swatchComponents.size(); ++i) | |||||
{ | |||||
SwatchComponent* const sc = swatchComponents.getUnchecked(i); | |||||
sc->setBounds (x + xGap / 2, | |||||
y + yGap / 2, | |||||
swatchWidth - xGap, | |||||
swatchHeight - yGap); | |||||
if (((i + 1) % swatchesPerRow) == 0) | |||||
{ | |||||
x = startX; | |||||
y += swatchHeight; | |||||
} | |||||
else | |||||
{ | |||||
x += swatchWidth; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void ColourSelector::sliderValueChanged (Slider*) | |||||
{ | |||||
if (sliders[0] != nullptr) | |||||
setCurrentColour (Colour ((uint8) sliders[0]->getValue(), | |||||
(uint8) sliders[1]->getValue(), | |||||
(uint8) sliders[2]->getValue(), | |||||
(uint8) sliders[3]->getValue())); | |||||
} | |||||
//============================================================================== | |||||
int ColourSelector::getNumSwatches() const | |||||
{ | |||||
return 0; | |||||
} | |||||
Colour ColourSelector::getSwatchColour (const int) const | |||||
{ | |||||
jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method | |||||
return Colours::black; | |||||
} | |||||
void ColourSelector::setSwatchColour (const int, const Colour&) const | |||||
{ | |||||
jassertfalse; // if you've overridden getNumSwatches(), you also need to implement this method | |||||
} |
@@ -0,0 +1,172 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_COLOURSELECTOR_H_INCLUDED | |||||
#define JUCE_COLOURSELECTOR_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A component that lets the user choose a colour. | |||||
This shows RGB sliders and a colourspace that the user can pick colours from. | |||||
This class is also a ChangeBroadcaster, so listeners can register to be told | |||||
when the colour changes. | |||||
*/ | |||||
class JUCE_API ColourSelector : public Component, | |||||
public ChangeBroadcaster, | |||||
protected SliderListener | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Options for the type of selector to show. These are passed into the constructor. */ | |||||
enum ColourSelectorOptions | |||||
{ | |||||
showAlphaChannel = 1 << 0, /**< if set, the colour's alpha channel can be changed as well as its RGB. */ | |||||
showColourAtTop = 1 << 1, /**< if set, a swatch of the colour is shown at the top of the component. */ | |||||
showSliders = 1 << 2, /**< if set, RGB sliders are shown at the bottom of the component. */ | |||||
showColourspace = 1 << 3 /**< if set, a big HSV selector is shown. */ | |||||
}; | |||||
//============================================================================== | |||||
/** Creates a ColourSelector object. | |||||
The flags are a combination of values from the ColourSelectorOptions enum, specifying | |||||
which of the selector's features should be visible. | |||||
The edgeGap value specifies the amount of space to leave around the edge. | |||||
gapAroundColourSpaceComponent indicates how much of a gap to put around the | |||||
colourspace and hue selector components. | |||||
*/ | |||||
ColourSelector (int sectionsToShow = (showAlphaChannel | showColourAtTop | showSliders | showColourspace), | |||||
int edgeGap = 4, | |||||
int gapAroundColourSpaceComponent = 7); | |||||
/** Destructor. */ | |||||
~ColourSelector(); | |||||
//============================================================================== | |||||
/** Returns the colour that the user has currently selected. | |||||
The ColourSelector class is also a ChangeBroadcaster, so listeners can | |||||
register to be told when the colour changes. | |||||
@see setCurrentColour | |||||
*/ | |||||
Colour getCurrentColour() const; | |||||
/** Changes the colour that is currently being shown. | |||||
*/ | |||||
void setCurrentColour (Colour newColour); | |||||
//============================================================================== | |||||
/** Tells the selector how many preset colour swatches you want to have on the component. | |||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and | |||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve | |||||
their values. | |||||
*/ | |||||
virtual int getNumSwatches() const; | |||||
/** Called by the selector to find out the colour of one of the swatches. | |||||
Your subclass should return the colour of the swatch with the given index. | |||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and | |||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve | |||||
their values. | |||||
*/ | |||||
virtual Colour getSwatchColour (int index) const; | |||||
/** Called by the selector when the user puts a new colour into one of the swatches. | |||||
Your subclass should change the colour of the swatch with the given index. | |||||
To enable swatches, you'll need to override getNumSwatches(), getSwatchColour(), and | |||||
setSwatchColour(), to return the number of colours you want, and to set and retrieve | |||||
their values. | |||||
*/ | |||||
virtual void setSwatchColour (int index, const Colour& newColour) const; | |||||
//============================================================================== | |||||
/** A set of colour IDs to use to change the colour of various aspects of the keyboard. | |||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||||
methods. | |||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||||
*/ | |||||
enum ColourIds | |||||
{ | |||||
backgroundColourId = 0x1007000, /**< the colour used to fill the component's background. */ | |||||
labelTextColourId = 0x1007001 /**< the colour used for the labels next to the sliders. */ | |||||
}; | |||||
private: | |||||
//============================================================================== | |||||
class ColourSpaceView; | |||||
class HueSelectorComp; | |||||
class SwatchComponent; | |||||
class ColourComponentSlider; | |||||
class ColourSpaceMarker; | |||||
class HueSelectorMarker; | |||||
friend class ColourSpaceView; | |||||
friend struct ContainerDeletePolicy<ColourSpaceView>; | |||||
friend class HueSelectorComp; | |||||
friend struct ContainerDeletePolicy<HueSelectorComp>; | |||||
Colour colour; | |||||
float h, s, v; | |||||
ScopedPointer<Slider> sliders[4]; | |||||
ScopedPointer<ColourSpaceView> colourSpace; | |||||
ScopedPointer<HueSelectorComp> hueSelector; | |||||
OwnedArray <SwatchComponent> swatchComponents; | |||||
const int flags; | |||||
int edgeGap; | |||||
Rectangle<int> previewArea; | |||||
void setHue (float newH); | |||||
void setSV (float newS, float newV); | |||||
void updateHSV(); | |||||
void update(); | |||||
void sliderValueChanged (Slider*); | |||||
void paint (Graphics&) override; | |||||
void resized() override; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ColourSelector) | |||||
#if JUCE_CATCH_DEPRECATED_CODE_MISUSE | |||||
// This constructor is here temporarily to prevent old code compiling, because the parameters | |||||
// have changed - if you get an error here, update your code to use the new constructor instead.. | |||||
ColourSelector (bool); | |||||
#endif | |||||
}; | |||||
#endif // JUCE_COLOURSELECTOR_H_INCLUDED |
@@ -0,0 +1,472 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class KeyMappingEditorComponent::ChangeKeyButton : public Button | |||||
{ | |||||
public: | |||||
ChangeKeyButton (KeyMappingEditorComponent& kec, const CommandID command, | |||||
const String& keyName, const int keyIndex) | |||||
: Button (keyName), | |||||
owner (kec), | |||||
commandID (command), | |||||
keyNum (keyIndex) | |||||
{ | |||||
setWantsKeyboardFocus (false); | |||||
setTriggeredOnMouseDown (keyNum >= 0); | |||||
setTooltip (keyIndex < 0 ? TRANS("Adds a new key-mapping") | |||||
: TRANS("Click to change this key-mapping")); | |||||
} | |||||
void paintButton (Graphics& g, bool /*isOver*/, bool /*isDown*/) override | |||||
{ | |||||
getLookAndFeel().drawKeymapChangeButton (g, getWidth(), getHeight(), *this, | |||||
keyNum >= 0 ? getName() : String::empty); | |||||
} | |||||
static void menuCallback (int result, ChangeKeyButton* button) | |||||
{ | |||||
if (button != nullptr) | |||||
{ | |||||
switch (result) | |||||
{ | |||||
case 1: button->assignNewKey(); break; | |||||
case 2: button->owner.getMappings().removeKeyPress (button->commandID, button->keyNum); break; | |||||
default: break; | |||||
} | |||||
} | |||||
} | |||||
void clicked() override | |||||
{ | |||||
if (keyNum >= 0) | |||||
{ | |||||
// existing key clicked.. | |||||
PopupMenu m; | |||||
m.addItem (1, TRANS("Change this key-mapping")); | |||||
m.addSeparator(); | |||||
m.addItem (2, TRANS("Remove this key-mapping")); | |||||
m.showMenuAsync (PopupMenu::Options(), | |||||
ModalCallbackFunction::forComponent (menuCallback, this)); | |||||
} | |||||
else | |||||
{ | |||||
assignNewKey(); // + button pressed.. | |||||
} | |||||
} | |||||
void fitToContent (const int h) noexcept | |||||
{ | |||||
if (keyNum < 0) | |||||
setSize (h, h); | |||||
else | |||||
setSize (jlimit (h * 4, h * 8, 6 + Font (h * 0.6f).getStringWidth (getName())), h); | |||||
} | |||||
//============================================================================== | |||||
class KeyEntryWindow : public AlertWindow | |||||
{ | |||||
public: | |||||
KeyEntryWindow (KeyMappingEditorComponent& kec) | |||||
: AlertWindow (TRANS("New key-mapping"), | |||||
TRANS("Please press a key combination now..."), | |||||
AlertWindow::NoIcon), | |||||
owner (kec) | |||||
{ | |||||
addButton (TRANS("OK"), 1); | |||||
addButton (TRANS("Cancel"), 0); | |||||
// (avoid return + escape keys getting processed by the buttons..) | |||||
for (int i = getNumChildComponents(); --i >= 0;) | |||||
getChildComponent (i)->setWantsKeyboardFocus (false); | |||||
setWantsKeyboardFocus (true); | |||||
grabKeyboardFocus(); | |||||
} | |||||
bool keyPressed (const KeyPress& key) override | |||||
{ | |||||
lastPress = key; | |||||
String message (TRANS("Key") + ": " + owner.getDescriptionForKeyPress (key)); | |||||
const CommandID previousCommand = owner.getMappings().findCommandForKeyPress (key); | |||||
if (previousCommand != 0) | |||||
message << "\n\n(" | |||||
<< TRANS("Currently assigned to \"CMDN\"") | |||||
.replace ("CMDN", owner.getCommandManager().getNameOfCommand (previousCommand)) | |||||
<< ')'; | |||||
setMessage (message); | |||||
return true; | |||||
} | |||||
bool keyStateChanged (bool) override | |||||
{ | |||||
return true; | |||||
} | |||||
KeyPress lastPress; | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
JUCE_DECLARE_NON_COPYABLE (KeyEntryWindow) | |||||
}; | |||||
static void assignNewKeyCallback (int result, ChangeKeyButton* button, KeyPress newKey) | |||||
{ | |||||
if (result != 0 && button != nullptr) | |||||
button->setNewKey (newKey, true); | |||||
} | |||||
void setNewKey (const KeyPress& newKey, bool dontAskUser) | |||||
{ | |||||
if (newKey.isValid()) | |||||
{ | |||||
const CommandID previousCommand = owner.getMappings().findCommandForKeyPress (newKey); | |||||
if (previousCommand == 0 || dontAskUser) | |||||
{ | |||||
owner.getMappings().removeKeyPress (newKey); | |||||
if (keyNum >= 0) | |||||
owner.getMappings().removeKeyPress (commandID, keyNum); | |||||
owner.getMappings().addKeyPress (commandID, newKey, keyNum); | |||||
} | |||||
else | |||||
{ | |||||
AlertWindow::showOkCancelBox (AlertWindow::WarningIcon, | |||||
TRANS("Change key-mapping"), | |||||
TRANS("This key is already assigned to the command \"CMDN\"") | |||||
.replace ("CMDN", owner.getCommandManager().getNameOfCommand (previousCommand)) | |||||
+ "\n\n" | |||||
+ TRANS("Do you want to re-assign it to this new command instead?"), | |||||
TRANS("Re-assign"), | |||||
TRANS("Cancel"), | |||||
this, | |||||
ModalCallbackFunction::forComponent (assignNewKeyCallback, | |||||
this, KeyPress (newKey))); | |||||
} | |||||
} | |||||
} | |||||
static void keyChosen (int result, ChangeKeyButton* button) | |||||
{ | |||||
if (result != 0 && button != nullptr && button->currentKeyEntryWindow != nullptr) | |||||
{ | |||||
button->currentKeyEntryWindow->setVisible (false); | |||||
button->setNewKey (button->currentKeyEntryWindow->lastPress, false); | |||||
} | |||||
button->currentKeyEntryWindow = nullptr; | |||||
} | |||||
void assignNewKey() | |||||
{ | |||||
currentKeyEntryWindow = new KeyEntryWindow (owner); | |||||
currentKeyEntryWindow->enterModalState (true, ModalCallbackFunction::forComponent (keyChosen, this)); | |||||
} | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
const CommandID commandID; | |||||
const int keyNum; | |||||
ScopedPointer<KeyEntryWindow> currentKeyEntryWindow; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChangeKeyButton) | |||||
}; | |||||
//============================================================================== | |||||
class KeyMappingEditorComponent::ItemComponent : public Component | |||||
{ | |||||
public: | |||||
ItemComponent (KeyMappingEditorComponent& kec, const CommandID command) | |||||
: owner (kec), commandID (command) | |||||
{ | |||||
setInterceptsMouseClicks (false, true); | |||||
const bool isReadOnly = owner.isCommandReadOnly (commandID); | |||||
const Array<KeyPress> keyPresses (owner.getMappings().getKeyPressesAssignedToCommand (commandID)); | |||||
for (int i = 0; i < jmin ((int) maxNumAssignments, keyPresses.size()); ++i) | |||||
addKeyPressButton (owner.getDescriptionForKeyPress (keyPresses.getReference (i)), i, isReadOnly); | |||||
addKeyPressButton (String::empty, -1, isReadOnly); | |||||
} | |||||
void addKeyPressButton (const String& desc, const int index, const bool isReadOnly) | |||||
{ | |||||
ChangeKeyButton* const b = new ChangeKeyButton (owner, commandID, desc, index); | |||||
keyChangeButtons.add (b); | |||||
b->setEnabled (! isReadOnly); | |||||
b->setVisible (keyChangeButtons.size() <= (int) maxNumAssignments); | |||||
addChildComponent (b); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
g.setFont (getHeight() * 0.7f); | |||||
g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId)); | |||||
g.drawFittedText (TRANS (owner.getCommandManager().getNameOfCommand (commandID)), | |||||
4, 0, jmax (40, getChildComponent (0)->getX() - 5), getHeight(), | |||||
Justification::centredLeft, true); | |||||
} | |||||
void resized() override | |||||
{ | |||||
int x = getWidth() - 4; | |||||
for (int i = keyChangeButtons.size(); --i >= 0;) | |||||
{ | |||||
ChangeKeyButton* const b = keyChangeButtons.getUnchecked(i); | |||||
b->fitToContent (getHeight() - 2); | |||||
b->setTopRightPosition (x, 1); | |||||
x = b->getX() - 5; | |||||
} | |||||
} | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
OwnedArray<ChangeKeyButton> keyChangeButtons; | |||||
const CommandID commandID; | |||||
enum { maxNumAssignments = 3 }; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ItemComponent) | |||||
}; | |||||
//============================================================================== | |||||
class KeyMappingEditorComponent::MappingItem : public TreeViewItem | |||||
{ | |||||
public: | |||||
MappingItem (KeyMappingEditorComponent& kec, const CommandID command) | |||||
: owner (kec), commandID (command) | |||||
{} | |||||
String getUniqueName() const override { return String ((int) commandID) + "_id"; } | |||||
bool mightContainSubItems() override { return false; } | |||||
int getItemHeight() const override { return 20; } | |||||
Component* createItemComponent() override { return new ItemComponent (owner, commandID); } | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
const CommandID commandID; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MappingItem) | |||||
}; | |||||
//============================================================================== | |||||
class KeyMappingEditorComponent::CategoryItem : public TreeViewItem | |||||
{ | |||||
public: | |||||
CategoryItem (KeyMappingEditorComponent& kec, const String& name) | |||||
: owner (kec), categoryName (name) | |||||
{} | |||||
String getUniqueName() const override { return categoryName + "_cat"; } | |||||
bool mightContainSubItems() override { return true; } | |||||
int getItemHeight() const override { return 22; } | |||||
void paintItem (Graphics& g, int width, int height) override | |||||
{ | |||||
g.setFont (Font (height * 0.7f, Font::bold)); | |||||
g.setColour (owner.findColour (KeyMappingEditorComponent::textColourId)); | |||||
g.drawText (TRANS (categoryName), 2, 0, width - 2, height, Justification::centredLeft, true); | |||||
} | |||||
void itemOpennessChanged (bool isNowOpen) override | |||||
{ | |||||
if (isNowOpen) | |||||
{ | |||||
if (getNumSubItems() == 0) | |||||
{ | |||||
const Array<CommandID> commands (owner.getCommandManager().getCommandsInCategory (categoryName)); | |||||
for (int i = 0; i < commands.size(); ++i) | |||||
if (owner.shouldCommandBeIncluded (commands.getUnchecked(i))) | |||||
addSubItem (new MappingItem (owner, commands.getUnchecked(i))); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
clearSubItems(); | |||||
} | |||||
} | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
String categoryName; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CategoryItem) | |||||
}; | |||||
//============================================================================== | |||||
class KeyMappingEditorComponent::TopLevelItem : public TreeViewItem, | |||||
public ButtonListener, | |||||
private ChangeListener | |||||
{ | |||||
public: | |||||
TopLevelItem (KeyMappingEditorComponent& kec) : owner (kec) | |||||
{ | |||||
setLinesDrawnForSubItems (false); | |||||
owner.getMappings().addChangeListener (this); | |||||
} | |||||
~TopLevelItem() | |||||
{ | |||||
owner.getMappings().removeChangeListener (this); | |||||
} | |||||
bool mightContainSubItems() { return true; } | |||||
String getUniqueName() const { return "keys"; } | |||||
void changeListenerCallback (ChangeBroadcaster*) override | |||||
{ | |||||
const OpennessRestorer opennessRestorer (*this); | |||||
clearSubItems(); | |||||
const StringArray categories (owner.getCommandManager().getCommandCategories()); | |||||
for (int i = 0; i < categories.size(); ++i) | |||||
{ | |||||
const Array<CommandID> commands (owner.getCommandManager().getCommandsInCategory (categories[i])); | |||||
int count = 0; | |||||
for (int j = 0; j < commands.size(); ++j) | |||||
if (owner.shouldCommandBeIncluded (commands.getUnchecked(j))) | |||||
++count; | |||||
if (count > 0) | |||||
addSubItem (new CategoryItem (owner, categories[i])); | |||||
} | |||||
} | |||||
static void resetToDefaultsCallback (int result, KeyMappingEditorComponent* owner) | |||||
{ | |||||
if (result != 0 && owner != nullptr) | |||||
owner->getMappings().resetToDefaultMappings(); | |||||
} | |||||
void buttonClicked (Button*) override | |||||
{ | |||||
AlertWindow::showOkCancelBox (AlertWindow::QuestionIcon, | |||||
TRANS("Reset to defaults"), | |||||
TRANS("Are you sure you want to reset all the key-mappings to their default state?"), | |||||
TRANS("Reset"), | |||||
String::empty, | |||||
&owner, | |||||
ModalCallbackFunction::forComponent (resetToDefaultsCallback, &owner)); | |||||
} | |||||
private: | |||||
KeyMappingEditorComponent& owner; | |||||
}; | |||||
//============================================================================== | |||||
KeyMappingEditorComponent::KeyMappingEditorComponent (KeyPressMappingSet& mappingManager, | |||||
const bool showResetToDefaultButton) | |||||
: mappings (mappingManager), | |||||
resetButton (TRANS ("reset to defaults")) | |||||
{ | |||||
treeItem = new TopLevelItem (*this); | |||||
if (showResetToDefaultButton) | |||||
{ | |||||
addAndMakeVisible (resetButton); | |||||
resetButton.addListener (treeItem); | |||||
} | |||||
addAndMakeVisible (tree); | |||||
tree.setColour (TreeView::backgroundColourId, findColour (backgroundColourId)); | |||||
tree.setRootItemVisible (false); | |||||
tree.setDefaultOpenness (true); | |||||
tree.setRootItem (treeItem); | |||||
tree.setIndentSize (12); | |||||
} | |||||
KeyMappingEditorComponent::~KeyMappingEditorComponent() | |||||
{ | |||||
tree.setRootItem (nullptr); | |||||
} | |||||
//============================================================================== | |||||
void KeyMappingEditorComponent::setColours (Colour mainBackground, | |||||
Colour textColour) | |||||
{ | |||||
setColour (backgroundColourId, mainBackground); | |||||
setColour (textColourId, textColour); | |||||
tree.setColour (TreeView::backgroundColourId, mainBackground); | |||||
} | |||||
void KeyMappingEditorComponent::parentHierarchyChanged() | |||||
{ | |||||
treeItem->changeListenerCallback (nullptr); | |||||
} | |||||
void KeyMappingEditorComponent::resized() | |||||
{ | |||||
int h = getHeight(); | |||||
if (resetButton.isVisible()) | |||||
{ | |||||
const int buttonHeight = 20; | |||||
h -= buttonHeight + 8; | |||||
int x = getWidth() - 8; | |||||
resetButton.changeWidthToFitText (buttonHeight); | |||||
resetButton.setTopRightPosition (x, h + 6); | |||||
} | |||||
tree.setBounds (0, 0, getWidth(), h); | |||||
} | |||||
//============================================================================== | |||||
bool KeyMappingEditorComponent::shouldCommandBeIncluded (const CommandID commandID) | |||||
{ | |||||
const ApplicationCommandInfo* const ci = mappings.getCommandManager().getCommandForID (commandID); | |||||
return ci != nullptr && (ci->flags & ApplicationCommandInfo::hiddenFromKeyEditor) == 0; | |||||
} | |||||
bool KeyMappingEditorComponent::isCommandReadOnly (const CommandID commandID) | |||||
{ | |||||
const ApplicationCommandInfo* const ci = mappings.getCommandManager().getCommandForID (commandID); | |||||
return ci != nullptr && (ci->flags & ApplicationCommandInfo::readOnlyInKeyEditor) != 0; | |||||
} | |||||
String KeyMappingEditorComponent::getDescriptionForKeyPress (const KeyPress& key) | |||||
{ | |||||
return key.getTextDescription(); | |||||
} |
@@ -0,0 +1,136 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_KEYMAPPINGEDITORCOMPONENT_H_INCLUDED | |||||
#define JUCE_KEYMAPPINGEDITORCOMPONENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A component to allow editing of the keymaps stored by a KeyPressMappingSet | |||||
object. | |||||
@see KeyPressMappingSet | |||||
*/ | |||||
class JUCE_API KeyMappingEditorComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a KeyMappingEditorComponent. | |||||
@param mappingSet this is the set of mappings to display and edit. Make sure the | |||||
mappings object is not deleted before this component! | |||||
@param showResetToDefaultButton if true, then at the bottom of the list, the | |||||
component will include a 'reset to defaults' button. | |||||
*/ | |||||
KeyMappingEditorComponent (KeyPressMappingSet& mappingSet, | |||||
bool showResetToDefaultButton); | |||||
/** Destructor. */ | |||||
~KeyMappingEditorComponent(); | |||||
//============================================================================== | |||||
/** Sets up the colours to use for parts of the component. | |||||
@param mainBackground colour to use for most of the background | |||||
@param textColour colour to use for the text | |||||
*/ | |||||
void setColours (Colour mainBackground, | |||||
Colour textColour); | |||||
/** Returns the KeyPressMappingSet that this component is acting upon. */ | |||||
KeyPressMappingSet& getMappings() const noexcept { return mappings; } | |||||
/** Returns the ApplicationCommandManager that this component is connected to. */ | |||||
ApplicationCommandManager& getCommandManager() const noexcept { return mappings.getCommandManager(); } | |||||
//============================================================================== | |||||
/** Can be overridden if some commands need to be excluded from the list. | |||||
By default this will use the KeyPressMappingSet's shouldCommandBeVisibleInEditor() | |||||
method to decide what to return, but you can override it to handle special cases. | |||||
*/ | |||||
virtual bool shouldCommandBeIncluded (CommandID commandID); | |||||
/** Can be overridden to indicate that some commands are shown as read-only. | |||||
By default this will use the KeyPressMappingSet's shouldCommandBeReadOnlyInEditor() | |||||
method to decide what to return, but you can override it to handle special cases. | |||||
*/ | |||||
virtual bool isCommandReadOnly (CommandID commandID); | |||||
/** This can be overridden to let you change the format of the string used | |||||
to describe a keypress. | |||||
This is handy if you're using non-standard KeyPress objects, e.g. for custom | |||||
keys that are triggered by something else externally. If you override the | |||||
method, be sure to let the base class's method handle keys you're not | |||||
interested in. | |||||
*/ | |||||
virtual String getDescriptionForKeyPress (const KeyPress& key); | |||||
//============================================================================== | |||||
/** A set of colour IDs to use to change the colour of various aspects of the editor. | |||||
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour() | |||||
methods. | |||||
To change the colours of the menu that pops up | |||||
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour | |||||
*/ | |||||
enum ColourIds | |||||
{ | |||||
backgroundColourId = 0x100ad00, /**< The background colour to fill the editor background. */ | |||||
textColourId = 0x100ad01, /**< The colour for the text. */ | |||||
}; | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void parentHierarchyChanged() override; | |||||
/** @internal */ | |||||
void resized() override; | |||||
private: | |||||
//============================================================================== | |||||
KeyPressMappingSet& mappings; | |||||
TreeView tree; | |||||
TextButton resetButton; | |||||
class TopLevelItem; | |||||
class ChangeKeyButton; | |||||
class MappingItem; | |||||
class CategoryItem; | |||||
class ItemComponent; | |||||
friend class TopLevelItem; | |||||
friend struct ContainerDeletePolicy<ChangeKeyButton>; | |||||
friend struct ContainerDeletePolicy<TopLevelItem>; | |||||
ScopedPointer<TopLevelItem> treeItem; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KeyMappingEditorComponent) | |||||
}; | |||||
#endif // JUCE_KEYMAPPINGEDITORCOMPONENT_H_INCLUDED |
@@ -0,0 +1,466 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR | |||||
namespace LiveConstantEditor | |||||
{ | |||||
//============================================================================== | |||||
class AllComponentRepainter : private Timer, | |||||
private DeletedAtShutdown | |||||
{ | |||||
public: | |||||
AllComponentRepainter() {} | |||||
static AllComponentRepainter& getInstance() | |||||
{ | |||||
static AllComponentRepainter* instance = new AllComponentRepainter(); | |||||
return *instance; | |||||
} | |||||
void trigger() | |||||
{ | |||||
if (! isTimerRunning()) | |||||
startTimer (100); | |||||
} | |||||
private: | |||||
void timerCallback() override | |||||
{ | |||||
stopTimer(); | |||||
for (int i = TopLevelWindow::getNumTopLevelWindows(); --i >= 0;) | |||||
if (Component* c = TopLevelWindow::getTopLevelWindow(i)) | |||||
repaintAndResizeAllComps (c); | |||||
} | |||||
static void repaintAndResizeAllComps (Component::SafePointer<Component> c) | |||||
{ | |||||
if (c->isVisible()) | |||||
{ | |||||
c->repaint(); | |||||
c->resized(); | |||||
for (int i = c->getNumChildComponents(); --i >= 0;) | |||||
if (c != nullptr) | |||||
if (Component* child = c->getChildComponent(i)) | |||||
repaintAndResizeAllComps (child); | |||||
} | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
int64 parseInt (String s) | |||||
{ | |||||
s = s.retainCharacters ("0123456789abcdefABCDEFx"); | |||||
if (s.startsWith ("0x")) | |||||
return s.substring(2).getHexValue64(); | |||||
return s.getLargeIntValue(); | |||||
} | |||||
double parseDouble (const String& s) | |||||
{ | |||||
return s.retainCharacters ("0123456789.eE-").getDoubleValue(); | |||||
} | |||||
String intToString (int v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); } | |||||
String intToString (int64 v, bool preferHex) { return preferHex ? "0x" + String::toHexString (v) : String (v); } | |||||
//============================================================================== | |||||
LiveValueBase::LiveValueBase (const char* file, int line) | |||||
: sourceFile (file), sourceLine (line) | |||||
{ | |||||
name = File (sourceFile).getFileName() + " : " + String (sourceLine); | |||||
} | |||||
LiveValueBase::~LiveValueBase() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
LivePropertyEditorBase::LivePropertyEditorBase (LiveValueBase& v, CodeDocument& d) | |||||
: value (v), resetButton ("reset"), document (d), sourceEditor (document, &tokeniser), wasHex (false) | |||||
{ | |||||
setSize (600, 100); | |||||
addAndMakeVisible (name); | |||||
addAndMakeVisible (resetButton); | |||||
addAndMakeVisible (valueEditor); | |||||
addAndMakeVisible (sourceEditor); | |||||
findOriginalValueInCode(); | |||||
selectOriginalValue(); | |||||
name.setFont (13.0f); | |||||
name.setText (v.name, dontSendNotification); | |||||
valueEditor.setText (v.getStringValue (wasHex), dontSendNotification); | |||||
valueEditor.addListener (this); | |||||
sourceEditor.setReadOnly (true); | |||||
resetButton.addListener (this); | |||||
} | |||||
void LivePropertyEditorBase::paint (Graphics& g) | |||||
{ | |||||
g.setColour (Colours::white); | |||||
g.fillRect (getLocalBounds().removeFromBottom (1)); | |||||
} | |||||
void LivePropertyEditorBase::resized() | |||||
{ | |||||
Rectangle<int> r (getLocalBounds().reduced (0, 3).withTrimmedBottom (1)); | |||||
Rectangle<int> left (r.removeFromLeft (jmax (200, r.getWidth() / 3))); | |||||
Rectangle<int> top (left.removeFromTop (25)); | |||||
resetButton.setBounds (top.removeFromRight (35).reduced (0, 3)); | |||||
name.setBounds (top); | |||||
valueEditor.setBounds (left.removeFromTop (25)); | |||||
left.removeFromTop (2); | |||||
if (customComp != nullptr) | |||||
customComp->setBounds (left); | |||||
r.removeFromLeft (4); | |||||
sourceEditor.setBounds (r); | |||||
} | |||||
void LivePropertyEditorBase::textEditorTextChanged (TextEditor&) | |||||
{ | |||||
applyNewValue (valueEditor.getText()); | |||||
} | |||||
void LivePropertyEditorBase::buttonClicked (Button*) | |||||
{ | |||||
applyNewValue (value.getOriginalStringValue (wasHex)); | |||||
} | |||||
void LivePropertyEditorBase::applyNewValue (const String& s) | |||||
{ | |||||
value.setStringValue (s); | |||||
document.replaceSection (valueStart.getPosition(), valueEnd.getPosition(), value.getCodeValue (wasHex)); | |||||
document.clearUndoHistory(); | |||||
selectOriginalValue(); | |||||
valueEditor.setText (s, dontSendNotification); | |||||
AllComponentRepainter::getInstance().trigger(); | |||||
} | |||||
void LivePropertyEditorBase::selectOriginalValue() | |||||
{ | |||||
sourceEditor.selectRegion (valueStart, valueEnd); | |||||
} | |||||
void LivePropertyEditorBase::findOriginalValueInCode() | |||||
{ | |||||
CodeDocument::Position pos (document, value.sourceLine, 0); | |||||
String line (pos.getLineText()); | |||||
String::CharPointerType p (line.getCharPointer()); | |||||
p = CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")); | |||||
if (p.isEmpty()) | |||||
{ | |||||
// Not sure how this would happen - some kind of mix-up between source code and line numbers.. | |||||
jassertfalse; | |||||
return; | |||||
} | |||||
p += (int) (sizeof ("JUCE_LIVE_CONSTANT") - 1); | |||||
p = p.findEndOfWhitespace(); | |||||
if (! CharacterFunctions::find (p, CharPointer_ASCII ("JUCE_LIVE_CONSTANT")).isEmpty()) | |||||
{ | |||||
// Aargh! You've added two JUCE_LIVE_CONSTANT macros on the same line! | |||||
// They're identified by their line number, so you must make sure each | |||||
// one goes on a separate line! | |||||
jassertfalse; | |||||
} | |||||
if (p.getAndAdvance() == '(') | |||||
{ | |||||
String::CharPointerType start (p), end (p); | |||||
int depth = 1; | |||||
while (! end.isEmpty()) | |||||
{ | |||||
const juce_wchar c = end.getAndAdvance(); | |||||
if (c == '(') ++depth; | |||||
if (c == ')') --depth; | |||||
if (depth == 0) | |||||
{ | |||||
--end; | |||||
break; | |||||
} | |||||
} | |||||
if (end > start) | |||||
{ | |||||
valueStart = CodeDocument::Position (document, value.sourceLine, (int) (start - line.getCharPointer())); | |||||
valueEnd = CodeDocument::Position (document, value.sourceLine, (int) (end - line.getCharPointer())); | |||||
valueStart.setPositionMaintained (true); | |||||
valueEnd.setPositionMaintained (true); | |||||
wasHex = String (start, end).containsIgnoreCase ("0x"); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class ValueListHolderComponent : public Component | |||||
{ | |||||
public: | |||||
ValueListHolderComponent (ValueList& l) : valueList (l) | |||||
{ | |||||
setVisible (true); | |||||
} | |||||
void addItem (int width, LiveValueBase& v, CodeDocument& doc) | |||||
{ | |||||
addAndMakeVisible (editors.add (v.createPropertyComponent (doc))); | |||||
layout (width); | |||||
} | |||||
void layout (int width) | |||||
{ | |||||
setSize (width, editors.size() * itemHeight); | |||||
resized(); | |||||
} | |||||
void resized() override | |||||
{ | |||||
Rectangle<int> r (getLocalBounds().reduced (2, 0)); | |||||
for (int i = 0; i < editors.size(); ++i) | |||||
editors.getUnchecked(i)->setBounds (r.removeFromTop (itemHeight)); | |||||
} | |||||
enum { itemHeight = 120 }; | |||||
ValueList& valueList; | |||||
OwnedArray<LivePropertyEditorBase> editors; | |||||
}; | |||||
//============================================================================== | |||||
class ValueList::EditorWindow : public DocumentWindow, | |||||
private DeletedAtShutdown | |||||
{ | |||||
public: | |||||
EditorWindow (ValueList& list) | |||||
: DocumentWindow ("Live Values", Colours::lightgrey, DocumentWindow::closeButton) | |||||
{ | |||||
setLookAndFeel (&lookAndFeel); | |||||
setUsingNativeTitleBar (true); | |||||
viewport.setViewedComponent (new ValueListHolderComponent (list), true); | |||||
viewport.setSize (700, 600); | |||||
viewport.setScrollBarsShown (true, false); | |||||
setContentNonOwned (&viewport, true); | |||||
setResizable (true, false); | |||||
setResizeLimits (500, 400, 10000, 10000); | |||||
centreWithSize (getWidth(), getHeight()); | |||||
setVisible (true); | |||||
} | |||||
void closeButtonPressed() override | |||||
{ | |||||
setVisible (false); | |||||
} | |||||
void updateItems (ValueList& list) | |||||
{ | |||||
if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent())) | |||||
{ | |||||
while (l->getNumChildComponents() < list.values.size()) | |||||
{ | |||||
if (LiveValueBase* v = list.values [l->getNumChildComponents()]) | |||||
l->addItem (viewport.getMaximumVisibleWidth(), *v, list.getDocument (v->sourceFile)); | |||||
else | |||||
break; | |||||
} | |||||
setVisible (true); | |||||
} | |||||
} | |||||
void resized() override | |||||
{ | |||||
DocumentWindow::resized(); | |||||
if (ValueListHolderComponent* l = dynamic_cast<ValueListHolderComponent*> (viewport.getViewedComponent())) | |||||
l->layout (viewport.getMaximumVisibleWidth()); | |||||
} | |||||
Viewport viewport; | |||||
LookAndFeel_V3 lookAndFeel; | |||||
}; | |||||
//============================================================================== | |||||
ValueList::ValueList() {} | |||||
ValueList::~ValueList() {} | |||||
ValueList& ValueList::getInstance() | |||||
{ | |||||
static ValueList* i = new ValueList(); | |||||
return *i; | |||||
} | |||||
void ValueList::addValue (LiveValueBase* v) | |||||
{ | |||||
values.add (v); | |||||
triggerAsyncUpdate(); | |||||
} | |||||
void ValueList::handleAsyncUpdate() | |||||
{ | |||||
if (editorWindow == nullptr) | |||||
editorWindow = new EditorWindow (*this); | |||||
editorWindow->updateItems (*this); | |||||
} | |||||
CodeDocument& ValueList::getDocument (const File& file) | |||||
{ | |||||
const int index = documentFiles.indexOf (file.getFullPathName()); | |||||
if (index >= 0) | |||||
return *documents.getUnchecked (index); | |||||
CodeDocument* doc = documents.add (new CodeDocument()); | |||||
documentFiles.add (file); | |||||
doc->replaceAllContent (file.loadFileAsString()); | |||||
doc->clearUndoHistory(); | |||||
return *doc; | |||||
} | |||||
//============================================================================== | |||||
struct ColourEditorComp : public Component, | |||||
private ChangeListener | |||||
{ | |||||
ColourEditorComp (LivePropertyEditorBase& e) : editor (e) | |||||
{ | |||||
setMouseCursor (MouseCursor::PointingHandCursor); | |||||
} | |||||
Colour getColour() const | |||||
{ | |||||
return Colour ((int) parseInt (editor.value.getStringValue (false))); | |||||
} | |||||
void paint (Graphics& g) override | |||||
{ | |||||
g.fillCheckerBoard (getLocalBounds(), 6, 6, | |||||
Colour (0xffdddddd).overlaidWith (getColour()), | |||||
Colour (0xffffffff).overlaidWith (getColour())); | |||||
} | |||||
void mouseDown (const MouseEvent&) override | |||||
{ | |||||
ColourSelector* colourSelector = new ColourSelector(); | |||||
colourSelector->setName ("Colour"); | |||||
colourSelector->setCurrentColour (getColour()); | |||||
colourSelector->addChangeListener (this); | |||||
colourSelector->setColour (ColourSelector::backgroundColourId, Colours::transparentBlack); | |||||
colourSelector->setSize (300, 400); | |||||
CallOutBox::launchAsynchronously (colourSelector, getScreenBounds(), nullptr); | |||||
} | |||||
void changeListenerCallback (ChangeBroadcaster* source) override | |||||
{ | |||||
if (ColourSelector* cs = dynamic_cast<ColourSelector*> (source)) | |||||
editor.applyNewValue (getAsString (cs->getCurrentColour(), true)); | |||||
repaint(); | |||||
} | |||||
LivePropertyEditorBase& editor; | |||||
}; | |||||
Component* createColourEditor (LivePropertyEditorBase& editor) | |||||
{ | |||||
return new ColourEditorComp (editor); | |||||
} | |||||
//============================================================================== | |||||
class SliderComp : public Component, | |||||
private Slider::Listener | |||||
{ | |||||
public: | |||||
SliderComp (LivePropertyEditorBase& e, bool useFloat) | |||||
: editor (e), isFloat (useFloat) | |||||
{ | |||||
slider.setTextBoxStyle (Slider::NoTextBox, true, 0, 0); | |||||
addAndMakeVisible (slider); | |||||
updateRange(); | |||||
slider.addListener (this); | |||||
} | |||||
void updateRange() | |||||
{ | |||||
double v = isFloat ? parseDouble (editor.value.getStringValue (false)) | |||||
: (double) parseInt (editor.value.getStringValue (false)); | |||||
double range = isFloat ? 10 : 100; | |||||
slider.setRange (v - range, v + range); | |||||
slider.setValue (v, dontSendNotification); | |||||
} | |||||
private: | |||||
LivePropertyEditorBase& editor; | |||||
Slider slider; | |||||
bool isFloat; | |||||
void sliderValueChanged (Slider*) | |||||
{ | |||||
editor.applyNewValue (isFloat ? getAsString ((double) slider.getValue(), editor.wasHex) | |||||
: getAsString ((int64) slider.getValue(), editor.wasHex)); | |||||
} | |||||
void sliderDragStarted (Slider*) {} | |||||
void sliderDragEnded (Slider*) { updateRange(); } | |||||
void resized() | |||||
{ | |||||
slider.setBounds (getLocalBounds().removeFromTop (25)); | |||||
} | |||||
}; | |||||
Component* createIntegerSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, false); } | |||||
Component* createFloatSlider (LivePropertyEditorBase& editor) { return new SliderComp (editor, true); } | |||||
} | |||||
#endif |
@@ -0,0 +1,298 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_LIVECONSTANTEDITOR_H_INCLUDED | |||||
#define JUCE_LIVECONSTANTEDITOR_H_INCLUDED | |||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR && ! DOXYGEN | |||||
//============================================================================== | |||||
/** You can safely ignore all the stuff in this namespace - it's a bunch of boilerplate | |||||
code used to implement the JUCE_LIVE_CONSTANT functionality. | |||||
*/ | |||||
namespace LiveConstantEditor | |||||
{ | |||||
int64 parseInt (String); | |||||
double parseDouble (const String&); | |||||
String intToString (int, bool preferHex); | |||||
String intToString (int64, bool preferHex); | |||||
template <typename Type> | |||||
static void setFromString (Type& v, const String& s) { v = static_cast<Type> (s); } | |||||
inline void setFromString (char& v, const String& s) { v = (char) parseInt (s); } | |||||
inline void setFromString (unsigned char& v, const String& s) { v = (unsigned char) parseInt (s); } | |||||
inline void setFromString (short& v, const String& s) { v = (short) parseInt (s); } | |||||
inline void setFromString (unsigned short& v, const String& s) { v = (unsigned short) parseInt (s); } | |||||
inline void setFromString (int& v, const String& s) { v = (int) parseInt (s); } | |||||
inline void setFromString (unsigned int& v, const String& s) { v = (unsigned int) parseInt (s); } | |||||
inline void setFromString (long& v, const String& s) { v = (long) parseInt (s); } | |||||
inline void setFromString (unsigned long& v, const String& s) { v = (unsigned long) parseInt (s); } | |||||
inline void setFromString (int64& v, const String& s) { v = (int64) parseInt (s); } | |||||
inline void setFromString (uint64& v, const String& s) { v = (uint64) parseInt (s); } | |||||
inline void setFromString (double& v, const String& s) { v = parseDouble (s); } | |||||
inline void setFromString (float& v, const String& s) { v = (float) parseDouble (s); } | |||||
inline void setFromString (String& v, const String& s) { v = s; } | |||||
inline void setFromString (Colour& v, const String& s) { v = Colour ((int) parseInt (s)); } | |||||
template <typename Type> | |||||
inline String getAsString (const Type& v, bool) { return String (v); } | |||||
inline String getAsString (char v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (unsigned char v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (short v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (unsigned short v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (int v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (unsigned int v, bool preferHex) { return intToString ((int) v, preferHex); } | |||||
inline String getAsString (int64 v, bool preferHex) { return intToString ((int64) v, preferHex); } | |||||
inline String getAsString (uint64 v, bool preferHex) { return intToString ((int64) v, preferHex); } | |||||
inline String getAsString (Colour v, bool) { return intToString ((int) v.getARGB(), true); } | |||||
template <typename Type> | |||||
inline String getAsCode (Type& v, bool preferHex) { return getAsString (v, preferHex); } | |||||
inline String getAsCode (Colour v, bool) { return "Colour (0x" + String::toHexString ((int) v.getARGB()).paddedLeft ('0', 8) + ")"; } | |||||
inline String getAsCode (const String& v, bool) { return "\"" + v + "\""; } | |||||
inline String getAsCode (const char* v, bool) { return getAsCode (String (v), false); } | |||||
template <typename Type> | |||||
inline const char* castToCharPointer (const Type&) { return ""; } | |||||
inline const char* castToCharPointer (const String& s) { return s.toRawUTF8(); } | |||||
struct LivePropertyEditorBase; | |||||
//============================================================================== | |||||
struct JUCE_API LiveValueBase | |||||
{ | |||||
LiveValueBase (const char* file, int line); | |||||
virtual ~LiveValueBase(); | |||||
virtual LivePropertyEditorBase* createPropertyComponent (CodeDocument&) = 0; | |||||
virtual String getStringValue (bool preferHex) const = 0; | |||||
virtual String getCodeValue (bool preferHex) const = 0; | |||||
virtual void setStringValue (const String&) = 0; | |||||
virtual String getOriginalStringValue (bool preferHex) const = 0; | |||||
String name, sourceFile; | |||||
int sourceLine; | |||||
JUCE_DECLARE_NON_COPYABLE (LiveValueBase) | |||||
}; | |||||
//============================================================================== | |||||
struct JUCE_API LivePropertyEditorBase : public Component, | |||||
private TextEditor::Listener, | |||||
private ButtonListener | |||||
{ | |||||
LivePropertyEditorBase (LiveValueBase&, CodeDocument&); | |||||
void paint (Graphics&) override; | |||||
void resized() override; | |||||
void textEditorTextChanged (TextEditor&) override; | |||||
void buttonClicked (Button*) override; | |||||
void applyNewValue (const String&); | |||||
void selectOriginalValue(); | |||||
void findOriginalValueInCode(); | |||||
LiveValueBase& value; | |||||
Label name; | |||||
TextEditor valueEditor; | |||||
TextButton resetButton; | |||||
CodeDocument& document; | |||||
CPlusPlusCodeTokeniser tokeniser; | |||||
CodeEditorComponent sourceEditor; | |||||
CodeDocument::Position valueStart, valueEnd; | |||||
ScopedPointer<Component> customComp; | |||||
bool wasHex; | |||||
JUCE_DECLARE_NON_COPYABLE (LivePropertyEditorBase) | |||||
}; | |||||
//============================================================================== | |||||
Component* createColourEditor (LivePropertyEditorBase&); | |||||
Component* createIntegerSlider (LivePropertyEditorBase&); | |||||
Component* createFloatSlider (LivePropertyEditorBase&); | |||||
template <typename Type> struct CustomEditor { static Component* create (LivePropertyEditorBase&) { return nullptr; } }; | |||||
template<> struct CustomEditor<char> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<unsigned char> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<int> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<unsigned int> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<short> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<unsigned short> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<int64> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<uint64> { static Component* create (LivePropertyEditorBase& e) { return createIntegerSlider (e); } }; | |||||
template<> struct CustomEditor<float> { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; | |||||
template<> struct CustomEditor<double> { static Component* create (LivePropertyEditorBase& e) { return createFloatSlider (e); } }; | |||||
template<> struct CustomEditor<Colour> { static Component* create (LivePropertyEditorBase& e) { return createColourEditor (e); } }; | |||||
template <typename Type> | |||||
struct LivePropertyEditor : public LivePropertyEditorBase | |||||
{ | |||||
template <typename ValueType> | |||||
LivePropertyEditor (ValueType& v, CodeDocument& d) : LivePropertyEditorBase (v, d) | |||||
{ | |||||
addAndMakeVisible (customComp = CustomEditor<Type>::create (*this)); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
template <typename Type> | |||||
struct LiveValue : public LiveValueBase | |||||
{ | |||||
LiveValue (const char* file, int line, const Type& initialValue) | |||||
: LiveValueBase (file, line), value (initialValue), originalValue (initialValue) | |||||
{} | |||||
operator Type() const noexcept { return value; } | |||||
operator const char*() const { return castToCharPointer (value); } | |||||
LivePropertyEditorBase* createPropertyComponent (CodeDocument& doc) override | |||||
{ | |||||
return new LivePropertyEditor<Type> (*this, doc); | |||||
} | |||||
String getStringValue (bool preferHex) const override { return getAsString (value, preferHex); } | |||||
String getCodeValue (bool preferHex) const override { return getAsCode (value, preferHex); } | |||||
String getOriginalStringValue (bool preferHex) const override { return getAsString (originalValue, preferHex); } | |||||
void setStringValue (const String& s) override { setFromString (value, s); } | |||||
Type value, originalValue; | |||||
JUCE_DECLARE_NON_COPYABLE (LiveValue) | |||||
}; | |||||
//============================================================================== | |||||
class JUCE_API ValueList : private AsyncUpdater, | |||||
private DeletedAtShutdown | |||||
{ | |||||
public: | |||||
ValueList(); | |||||
~ValueList(); | |||||
static ValueList& getInstance(); | |||||
template <typename Type> | |||||
LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue) | |||||
{ | |||||
const ScopedLock sl (lock); | |||||
typedef LiveValue<Type> ValueType; | |||||
for (int i = 0; i < values.size(); ++i) | |||||
{ | |||||
LiveValueBase* v = values.getUnchecked(i); | |||||
if (v->sourceLine == line && v->sourceFile == file) | |||||
return *static_cast<ValueType*> (v); | |||||
} | |||||
ValueType* v = new ValueType (file, line, initialValue); | |||||
addValue (v); | |||||
return *v; | |||||
} | |||||
private: | |||||
OwnedArray<LiveValueBase> values; | |||||
OwnedArray<CodeDocument> documents; | |||||
Array<File> documentFiles; | |||||
class EditorWindow; | |||||
friend class EditorWindow; | |||||
friend struct ContainerDeletePolicy<EditorWindow>; | |||||
Component::SafePointer<EditorWindow> editorWindow; | |||||
CriticalSection lock; | |||||
CodeDocument& getDocument (const File&); | |||||
void addValue (LiveValueBase*); | |||||
void handleAsyncUpdate() override; | |||||
}; | |||||
template <typename Type> | |||||
inline LiveValue<Type>& getValue (const char* file, int line, const Type& initialValue) | |||||
{ | |||||
return ValueList::getInstance().getValue (file, line, initialValue); | |||||
} | |||||
inline LiveValue<String>& getValue (const char* file, int line, const char* initialValue) | |||||
{ | |||||
return getValue (file, line, String (initialValue)); | |||||
} | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
#if JUCE_ENABLE_LIVE_CONSTANT_EDITOR || DOXYGEN | |||||
/** | |||||
This macro wraps a primitive constant value in some cunning boilerplate code that allows | |||||
its value to be interactively tweaked in a popup window while your application is running. | |||||
In a release build, this macro disappears and is replaced by only the constant that it | |||||
wraps, but if JUCE_ENABLE_LIVE_CONSTANT_EDITOR is enabled, it injects a class wrapper | |||||
that automatically pops-up a window containing an editor that allows the value to be | |||||
tweaked at run-time. The editor window will also force all visible components to be | |||||
resized and repainted whenever a value is changed, so that if you use this to wrap | |||||
a colour or layout parameter, you'll be able to immediately see the effects of changing it. | |||||
The editor will also load the original source-file that contains each JUCE_LIVE_CONSTANT | |||||
macro, and will display a preview of the modified source code as you adjust the values. | |||||
Things to note: | |||||
- Only one of these per line! The __FILE__ and __LINE__ macros are used to identify | |||||
the value, so things will get confused if you have more than one per line | |||||
- Obviously because it needs to load the source code based on the __FILE__ macro, | |||||
it'll only work if the source files are stored locally in the same location as they | |||||
were when you compiled the program. | |||||
- It's only designed to cope with simple types: primitives, string literals, and | |||||
the Colour class, so if you try using it for other classes or complex expressions, | |||||
good luck! | |||||
- The editor window will get popped up whenever a new value is used for the first | |||||
time. You can close the window, but there's no way to get it back without restarting | |||||
the app! | |||||
e.g. in this example the colours, font size, and text used in the paint method can | |||||
all be adjusted live: | |||||
@code | |||||
void MyComp::paint (Graphics& g) override | |||||
{ | |||||
g.fillAll (JUCE_LIVE_CONSTANT (Colour (0xffddddff))); | |||||
Colour fontColour = JUCE_LIVE_CONSTANT (Colour (0xff005500)); | |||||
float fontSize = JUCE_LIVE_CONSTANT (16.0f); | |||||
g.setColour (fontColour); | |||||
g.setFont (fontSize); | |||||
g.drawFittedText (JUCE_LIVE_CONSTANT ("Hello world!"), | |||||
getLocalBounds(), Justification::centred, 2); | |||||
} | |||||
@endcode | |||||
*/ | |||||
#define JUCE_LIVE_CONSTANT(initialValue) \ | |||||
(LiveConstantEditor::getValue (__FILE__, __LINE__ - 1, initialValue)) | |||||
#else | |||||
#define JUCE_LIVE_CONSTANT(initialValue) \ | |||||
(initialValue) | |||||
#endif | |||||
#endif // JUCE_LIVECONSTANTEDITOR_H_INCLUDED |
@@ -0,0 +1,150 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
PreferencesPanel::PreferencesPanel() | |||||
: buttonSize (70) | |||||
{ | |||||
} | |||||
PreferencesPanel::~PreferencesPanel() | |||||
{ | |||||
} | |||||
int PreferencesPanel::getButtonSize() const noexcept | |||||
{ | |||||
return buttonSize; | |||||
} | |||||
void PreferencesPanel::setButtonSize (int newSize) | |||||
{ | |||||
buttonSize = newSize; | |||||
resized(); | |||||
} | |||||
//============================================================================== | |||||
void PreferencesPanel::addSettingsPage (const String& title, | |||||
const Drawable* icon, | |||||
const Drawable* overIcon, | |||||
const Drawable* downIcon) | |||||
{ | |||||
DrawableButton* const button = new DrawableButton (title, DrawableButton::ImageAboveTextLabel); | |||||
buttons.add (button); | |||||
button->setImages (icon, overIcon, downIcon); | |||||
button->setRadioGroupId (1); | |||||
button->addListener (this); | |||||
button->setClickingTogglesState (true); | |||||
button->setWantsKeyboardFocus (false); | |||||
addAndMakeVisible (button); | |||||
resized(); | |||||
if (currentPage == nullptr) | |||||
setCurrentPage (title); | |||||
} | |||||
void PreferencesPanel::addSettingsPage (const String& title, const void* imageData, const int imageDataSize) | |||||
{ | |||||
DrawableImage icon, iconOver, iconDown; | |||||
icon.setImage (ImageCache::getFromMemory (imageData, imageDataSize)); | |||||
iconOver.setImage (ImageCache::getFromMemory (imageData, imageDataSize)); | |||||
iconOver.setOverlayColour (Colours::black.withAlpha (0.12f)); | |||||
iconDown.setImage (ImageCache::getFromMemory (imageData, imageDataSize)); | |||||
iconDown.setOverlayColour (Colours::black.withAlpha (0.25f)); | |||||
addSettingsPage (title, &icon, &iconOver, &iconDown); | |||||
} | |||||
//============================================================================== | |||||
void PreferencesPanel::showInDialogBox (const String& dialogTitle, int dialogWidth, int dialogHeight, Colour backgroundColour) | |||||
{ | |||||
setSize (dialogWidth, dialogHeight); | |||||
DialogWindow::LaunchOptions o; | |||||
o.content.setNonOwned (this); | |||||
o.dialogTitle = dialogTitle; | |||||
o.dialogBackgroundColour = backgroundColour; | |||||
o.escapeKeyTriggersCloseButton = false; | |||||
o.useNativeTitleBar = false; | |||||
o.resizable = false; | |||||
o.launchAsync(); | |||||
} | |||||
//============================================================================== | |||||
void PreferencesPanel::resized() | |||||
{ | |||||
for (int i = 0; i < buttons.size(); ++i) | |||||
buttons.getUnchecked(i)->setBounds (i * buttonSize, 0, buttonSize, buttonSize); | |||||
if (currentPage != nullptr) | |||||
currentPage->setBounds (getLocalBounds().withTop (buttonSize + 5)); | |||||
} | |||||
void PreferencesPanel::paint (Graphics& g) | |||||
{ | |||||
g.setColour (Colours::grey); | |||||
g.fillRect (0, buttonSize + 2, getWidth(), 1); | |||||
} | |||||
void PreferencesPanel::setCurrentPage (const String& pageName) | |||||
{ | |||||
if (currentPageName != pageName) | |||||
{ | |||||
currentPageName = pageName; | |||||
currentPage = nullptr; | |||||
currentPage = createComponentForPage (pageName); | |||||
if (currentPage != nullptr) | |||||
{ | |||||
addAndMakeVisible (currentPage); | |||||
currentPage->toBack(); | |||||
resized(); | |||||
} | |||||
for (int i = 0; i < buttons.size(); ++i) | |||||
{ | |||||
if (buttons.getUnchecked(i)->getName() == pageName) | |||||
{ | |||||
buttons.getUnchecked(i)->setToggleState (true, dontSendNotification); | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
void PreferencesPanel::buttonClicked (Button*) | |||||
{ | |||||
for (int i = 0; i < buttons.size(); ++i) | |||||
{ | |||||
if (buttons.getUnchecked(i)->getToggleState()) | |||||
{ | |||||
setCurrentPage (buttons.getUnchecked(i)->getName()); | |||||
break; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,148 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_PREFERENCESPANEL_H_INCLUDED | |||||
#define JUCE_PREFERENCESPANEL_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
A component with a set of buttons at the top for changing between pages of | |||||
preferences. | |||||
This is just a handy way of writing a Mac-style preferences panel where you | |||||
have a row of buttons along the top for the different preference categories, | |||||
each button having an icon above its name. Clicking these will show an | |||||
appropriate prefs page below it. | |||||
You can either put one of these inside your own component, or just use the | |||||
showInDialogBox() method to show it in a window and run it modally. | |||||
To use it, just add a set of named pages with the addSettingsPage() method, | |||||
and implement the createComponentForPage() method to create suitable components | |||||
for each of these pages. | |||||
*/ | |||||
class JUCE_API PreferencesPanel : public Component, | |||||
private ButtonListener // (can't use Button::Listener due to idiotic VC2005 bug) | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty panel. | |||||
Use addSettingsPage() to add some pages to it in your constructor. | |||||
*/ | |||||
PreferencesPanel(); | |||||
/** Destructor. */ | |||||
~PreferencesPanel(); | |||||
//============================================================================== | |||||
/** Creates a page using a set of drawables to define the page's icon. | |||||
Note that the other version of this method is much easier if you're using | |||||
an image instead of a custom drawable. | |||||
@param pageTitle the name of this preferences page - you'll need to | |||||
make sure your createComponentForPage() method creates | |||||
a suitable component when it is passed this name | |||||
@param normalIcon the drawable to display in the page's button normally | |||||
@param overIcon the drawable to display in the page's button when the mouse is over | |||||
@param downIcon the drawable to display in the page's button when the button is down | |||||
@see DrawableButton | |||||
*/ | |||||
void addSettingsPage (const String& pageTitle, | |||||
const Drawable* normalIcon, | |||||
const Drawable* overIcon, | |||||
const Drawable* downIcon); | |||||
/** Creates a page using a set of drawables to define the page's icon. | |||||
The other version of this method gives you more control over the icon, but this | |||||
one is much easier if you're just loading it from a file. | |||||
@param pageTitle the name of this preferences page - you'll need to | |||||
make sure your createComponentForPage() method creates | |||||
a suitable component when it is passed this name | |||||
@param imageData a block of data containing an image file, e.g. a jpeg, png or gif. | |||||
For this to look good, you'll probably want to use a nice | |||||
transparent png file. | |||||
@param imageDataSize the size of the image data, in bytes | |||||
*/ | |||||
void addSettingsPage (const String& pageTitle, | |||||
const void* imageData, | |||||
int imageDataSize); | |||||
/** Utility method to display this panel in a DialogWindow. | |||||
Calling this will create a DialogWindow containing this panel with the | |||||
given size and title, and will run it modally, returning when the user | |||||
closes the dialog box. | |||||
*/ | |||||
void showInDialogBox (const String& dialogTitle, | |||||
int dialogWidth, | |||||
int dialogHeight, | |||||
Colour backgroundColour = Colours::white); | |||||
//============================================================================== | |||||
/** Subclasses must override this to return a component for each preferences page. | |||||
The subclass should return a pointer to a new component representing the named | |||||
page, which the panel will then display. | |||||
The panel will delete the component later when the user goes to another page | |||||
or deletes the panel. | |||||
*/ | |||||
virtual Component* createComponentForPage (const String& pageName) = 0; | |||||
//============================================================================== | |||||
/** Changes the current page being displayed. */ | |||||
void setCurrentPage (const String& pageName); | |||||
/** Returns the size of the buttons shown along the top. */ | |||||
int getButtonSize() const noexcept; | |||||
/** Changes the size of the buttons shown along the top. */ | |||||
void setButtonSize (int newSize); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void resized() override; | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
/** @internal */ | |||||
void buttonClicked (Button*) override; | |||||
private: | |||||
//============================================================================== | |||||
String currentPageName; | |||||
ScopedPointer <Component> currentPage; | |||||
OwnedArray<DrawableButton> buttons; | |||||
int buttonSize; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PreferencesPanel) | |||||
}; | |||||
#endif // JUCE_PREFERENCESPANEL_H_INCLUDED |
@@ -0,0 +1,146 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
RecentlyOpenedFilesList::RecentlyOpenedFilesList() | |||||
: maxNumberOfItems (10) | |||||
{ | |||||
} | |||||
RecentlyOpenedFilesList::~RecentlyOpenedFilesList() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void RecentlyOpenedFilesList::setMaxNumberOfItems (const int newMaxNumber) | |||||
{ | |||||
maxNumberOfItems = jmax (1, newMaxNumber); | |||||
files.removeRange (maxNumberOfItems, getNumFiles()); | |||||
} | |||||
int RecentlyOpenedFilesList::getNumFiles() const | |||||
{ | |||||
return files.size(); | |||||
} | |||||
File RecentlyOpenedFilesList::getFile (const int index) const | |||||
{ | |||||
return File (files [index]); | |||||
} | |||||
void RecentlyOpenedFilesList::clear() | |||||
{ | |||||
files.clear(); | |||||
} | |||||
void RecentlyOpenedFilesList::addFile (const File& file) | |||||
{ | |||||
removeFile (file); | |||||
files.insert (0, file.getFullPathName()); | |||||
setMaxNumberOfItems (maxNumberOfItems); | |||||
} | |||||
void RecentlyOpenedFilesList::removeFile (const File& file) | |||||
{ | |||||
files.removeString (file.getFullPathName()); | |||||
} | |||||
void RecentlyOpenedFilesList::removeNonExistentFiles() | |||||
{ | |||||
for (int i = getNumFiles(); --i >= 0;) | |||||
if (! getFile(i).exists()) | |||||
files.remove (i); | |||||
} | |||||
//============================================================================== | |||||
int RecentlyOpenedFilesList::createPopupMenuItems (PopupMenu& menuToAddTo, | |||||
const int baseItemId, | |||||
const bool showFullPaths, | |||||
const bool dontAddNonExistentFiles, | |||||
const File** filesToAvoid) | |||||
{ | |||||
int num = 0; | |||||
for (int i = 0; i < getNumFiles(); ++i) | |||||
{ | |||||
const File f (getFile(i)); | |||||
if ((! dontAddNonExistentFiles) || f.exists()) | |||||
{ | |||||
bool needsAvoiding = false; | |||||
if (filesToAvoid != nullptr) | |||||
{ | |||||
for (const File** avoid = filesToAvoid; *avoid != nullptr; ++avoid) | |||||
{ | |||||
if (f == **avoid) | |||||
{ | |||||
needsAvoiding = true; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
if (! needsAvoiding) | |||||
{ | |||||
menuToAddTo.addItem (baseItemId + i, | |||||
showFullPaths ? f.getFullPathName() | |||||
: f.getFileName()); | |||||
++num; | |||||
} | |||||
} | |||||
} | |||||
return num; | |||||
} | |||||
//============================================================================== | |||||
String RecentlyOpenedFilesList::toString() const | |||||
{ | |||||
return files.joinIntoString ("\n"); | |||||
} | |||||
void RecentlyOpenedFilesList::restoreFromString (const String& stringifiedVersion) | |||||
{ | |||||
clear(); | |||||
files.addLines (stringifiedVersion); | |||||
setMaxNumberOfItems (maxNumberOfItems); | |||||
} | |||||
//============================================================================== | |||||
void RecentlyOpenedFilesList::registerRecentFileNatively (const File& file) | |||||
{ | |||||
#if JUCE_MAC | |||||
JUCE_AUTORELEASEPOOL | |||||
{ | |||||
[[NSDocumentController sharedDocumentController] | |||||
noteNewRecentDocumentURL: [NSURL fileURLWithPath: juceStringToNS (file.getFullPathName())]]; | |||||
} | |||||
#else | |||||
(void) file; | |||||
#endif | |||||
} |
@@ -0,0 +1,164 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_RECENTLYOPENEDFILESLIST_H_INCLUDED | |||||
#define JUCE_RECENTLYOPENEDFILESLIST_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Manages a set of files for use as a list of recently-opened documents. | |||||
This is a handy class for holding your list of recently-opened documents, with | |||||
helpful methods for things like purging any non-existent files, automatically | |||||
adding them to a menu, and making persistence easy. | |||||
@see File, FileBasedDocument | |||||
*/ | |||||
class JUCE_API RecentlyOpenedFilesList | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates an empty list. | |||||
*/ | |||||
RecentlyOpenedFilesList(); | |||||
/** Destructor. */ | |||||
~RecentlyOpenedFilesList(); | |||||
//============================================================================== | |||||
/** Sets a limit for the number of files that will be stored in the list. | |||||
When addFile() is called, then if there is no more space in the list, the | |||||
least-recently added file will be dropped. | |||||
@see getMaxNumberOfItems | |||||
*/ | |||||
void setMaxNumberOfItems (int newMaxNumber); | |||||
/** Returns the number of items that this list will store. | |||||
@see setMaxNumberOfItems | |||||
*/ | |||||
int getMaxNumberOfItems() const noexcept { return maxNumberOfItems; } | |||||
/** Returns the number of files in the list. | |||||
The most recently added file is always at index 0. | |||||
*/ | |||||
int getNumFiles() const; | |||||
/** Returns one of the files in the list. | |||||
The most recently added file is always at index 0. | |||||
*/ | |||||
File getFile (int index) const; | |||||
/** Returns an array of all the absolute pathnames in the list. | |||||
*/ | |||||
const StringArray& getAllFilenames() const noexcept { return files; } | |||||
/** Clears all the files from the list. */ | |||||
void clear(); | |||||
/** Adds a file to the list. | |||||
The file will be added at index 0. If this file is already in the list, it will | |||||
be moved up to index 0, but a file can only appear once in the list. | |||||
If the list already contains the maximum number of items that is permitted, the | |||||
least-recently added file will be dropped from the end. | |||||
*/ | |||||
void addFile (const File& file); | |||||
/** Removes a file from the list. */ | |||||
void removeFile (const File& file); | |||||
/** Checks each of the files in the list, removing any that don't exist. | |||||
You might want to call this after reloading a list of files, or before putting them | |||||
on a menu. | |||||
*/ | |||||
void removeNonExistentFiles(); | |||||
/** Tells the OS to add a file to the OS-managed list of recent documents for this app. | |||||
Not all OSes maintain a list of recent files for an application, so this | |||||
function will have no effect on some OSes. Currently it's just implemented for OSX. | |||||
*/ | |||||
static void registerRecentFileNatively (const File& file); | |||||
//============================================================================== | |||||
/** Adds entries to a menu, representing each of the files in the list. | |||||
This is handy for creating an "open recent file..." menu in your app. The | |||||
menu items are numbered consecutively starting with the baseItemId value, | |||||
and can either be added as complete pathnames, or just the last part of the | |||||
filename. | |||||
If dontAddNonExistentFiles is true, then each file will be checked and only those | |||||
that exist will be added. | |||||
If filesToAvoid is non-zero, then it is considered to be a zero-terminated array of | |||||
pointers to file objects. Any files that appear in this list will not be added to the | |||||
menu - the reason for this is that you might have a number of files already open, so | |||||
might not want these to be shown in the menu. | |||||
It returns the number of items that were added. | |||||
*/ | |||||
int createPopupMenuItems (PopupMenu& menuToAddItemsTo, | |||||
int baseItemId, | |||||
bool showFullPaths, | |||||
bool dontAddNonExistentFiles, | |||||
const File** filesToAvoid = nullptr); | |||||
//============================================================================== | |||||
/** Returns a string that encapsulates all the files in the list. | |||||
The string that is returned can later be passed into restoreFromString() in | |||||
order to recreate the list. This is handy for persisting your list, e.g. in | |||||
a PropertiesFile object. | |||||
@see restoreFromString | |||||
*/ | |||||
String toString() const; | |||||
/** Restores the list from a previously stringified version of the list. | |||||
Pass in a stringified version created with toString() in order to persist/restore | |||||
your list. | |||||
@see toString | |||||
*/ | |||||
void restoreFromString (const String& stringifiedVersion); | |||||
private: | |||||
//============================================================================== | |||||
StringArray files; | |||||
int maxNumberOfItems; | |||||
JUCE_LEAK_DETECTOR (RecentlyOpenedFilesList) | |||||
}; | |||||
#endif // JUCE_RECENTLYOPENEDFILESLIST_H_INCLUDED |
@@ -0,0 +1,83 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
SplashScreen::SplashScreen (const String& title, const Image& image, bool useDropShadow) | |||||
: Component (title), | |||||
backgroundImage (image), | |||||
clickCountToDelete (0) | |||||
{ | |||||
// You must supply a valid image here! | |||||
jassert (backgroundImage.isValid()); | |||||
setOpaque (! backgroundImage.hasAlphaChannel()); | |||||
makeVisible (image.getWidth(), image.getHeight(), useDropShadow); | |||||
} | |||||
SplashScreen::SplashScreen (const String& title, int width, int height, bool useDropShadow) | |||||
: Component (title), | |||||
clickCountToDelete (0) | |||||
{ | |||||
makeVisible (width, height, useDropShadow); | |||||
} | |||||
void SplashScreen::makeVisible (int w, int h, bool useDropShadow) | |||||
{ | |||||
clickCountToDelete = Desktop::getInstance().getMouseButtonClickCounter(); | |||||
creationTime = Time::getCurrentTime(); | |||||
setAlwaysOnTop (true); | |||||
setVisible (true); | |||||
centreWithSize (w, h); | |||||
addToDesktop (useDropShadow ? ComponentPeer::windowHasDropShadow : 0); | |||||
toFront (false); | |||||
} | |||||
SplashScreen::~SplashScreen() {} | |||||
void SplashScreen::deleteAfterDelay (RelativeTime timeout, bool removeOnMouseClick) | |||||
{ | |||||
// Note that this method must be safe to call from non-GUI threads | |||||
if (! removeOnMouseClick) | |||||
clickCountToDelete = std::numeric_limits<int>::max(); | |||||
minimumVisibleTime = timeout; | |||||
startTimer (50); | |||||
} | |||||
void SplashScreen::paint (Graphics& g) | |||||
{ | |||||
g.setOpacity (1.0f); | |||||
g.drawImage (backgroundImage, | |||||
0, 0, getWidth(), getHeight(), | |||||
0, 0, backgroundImage.getWidth(), backgroundImage.getHeight()); | |||||
} | |||||
void SplashScreen::timerCallback() | |||||
{ | |||||
if (Time::getCurrentTime() > creationTime + minimumVisibleTime | |||||
|| Desktop::getInstance().getMouseButtonClickCounter() > clickCountToDelete) | |||||
delete this; | |||||
} |
@@ -0,0 +1,155 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_SPLASHSCREEN_H_INCLUDED | |||||
#define JUCE_SPLASHSCREEN_H_INCLUDED | |||||
//============================================================================== | |||||
/** A component for showing a splash screen while your app starts up. | |||||
This will automatically position itself, and can be told to delete itself after | |||||
being on-screen for a minimum length of time. | |||||
To use it, just create one of these in your JUCEApplicationBase::initialise() method, | |||||
and when your initialisation tasks have finished running, call its deleteAfterDelay() | |||||
method to make it automatically get rid of itself. | |||||
Note that although you could call deleteAfterDelay() as soon as you create the | |||||
SplashScreen object, if you've got a long initialisation procedure, you probably | |||||
don't want the splash to time-out and disappear before the initialisation has | |||||
finished, which is why it makes sense to not call this method until the end of | |||||
your init tasks. | |||||
E.g. @code | |||||
void MyApp::initialise (const String& commandLine) | |||||
{ | |||||
splash = new SplashScreen ("Welcome to my app!", | |||||
ImageFileFormat::loadFrom (File ("/foobar/splash.jpg")), | |||||
true); | |||||
// now kick off your initialisation work on some kind of thread or task, and | |||||
launchBackgroundInitialisationThread(); | |||||
} | |||||
void MyApp::myInitialisationWorkFinished() | |||||
{ | |||||
// ..assuming this is some kind of callback method that is triggered when | |||||
// your background initialisation threads have finished, and it's time to open | |||||
// your main window, etc.. | |||||
splash->deleteAfterDelay (RelativeTime::seconds (4), false); | |||||
...etc... | |||||
} | |||||
@endcode | |||||
*/ | |||||
class JUCE_API SplashScreen : public Component, | |||||
private Timer, | |||||
private DeletedAtShutdown | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a SplashScreen object. | |||||
When called, the constructor will position the SplashScreen in the centre of the | |||||
display, and after the time specified, it will automatically delete itself. | |||||
Bear in mind that if you call this during your JUCEApplicationBase::initialise() | |||||
method and then block the message thread by performing some kind of task, then | |||||
obviously neither your splash screen nor any other GUI will appear until you | |||||
allow the message thread to resume and do its work. So if you have time-consuming | |||||
tasks to do during startup, use a background thread for them. | |||||
After creating one of these (or your subclass of it), you should do your app's | |||||
initialisation work, and then call the deleteAfterDelay() method to tell this object | |||||
to delete itself after the user has had chance to get a good look at it. | |||||
If you're writing a custom splash screen class, there's another protected constructor | |||||
that your subclass can call, which doesn't take an image. | |||||
@param title the name to give the component | |||||
@param backgroundImage an image to draw on the component. The component's size | |||||
will be set to the size of this image, and if the image is | |||||
semi-transparent, the component will be made non-opaque | |||||
@param useDropShadow if true, the window will have a drop shadow | |||||
*/ | |||||
SplashScreen (const String& title, | |||||
const Image& backgroundImage, | |||||
bool useDropShadow); | |||||
/** Destructor. */ | |||||
~SplashScreen(); | |||||
/** Tells the component to auto-delete itself after a timeout period, or when the | |||||
mouse is clicked. | |||||
You should call this after finishing your app's initialisation work. | |||||
Note that although you could call deleteAfterDelay() as soon as you create the | |||||
SplashScreen object, if you've got a long initialisation procedure, you probably | |||||
don't want the splash to time-out and disappear before your initialisation has | |||||
finished, which is why it makes sense to not call this method and start the | |||||
self-delete timer until you're ready. | |||||
It's safe to call this method from a non-GUI thread as long as there's no danger that | |||||
the object may be being deleted at the same time. | |||||
@param minimumTotalTimeToDisplayFor how long the splash screen should stay visible for. | |||||
Note that this time is measured from the construction-time of this | |||||
object, not from the time that the deleteAfterDelay() method is | |||||
called, so if you call this method after a long initialisation | |||||
period, it may be deleted without any further delay. | |||||
@param removeOnMouseClick if true, the window will be deleted as soon as the user clicks | |||||
the mouse (anywhere) | |||||
*/ | |||||
void deleteAfterDelay (RelativeTime minimumTotalTimeToDisplayFor, | |||||
bool removeOnMouseClick); | |||||
protected: | |||||
//============================================================================== | |||||
/** This constructor is for use by custom sub-classes that don't want to provide an image. */ | |||||
SplashScreen (const String& title, int width, int height, bool useDropShadow); | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
private: | |||||
//============================================================================== | |||||
Image backgroundImage; | |||||
Time creationTime; | |||||
RelativeTime minimumVisibleTime; | |||||
int clickCountToDelete; | |||||
void timerCallback() override; | |||||
void makeVisible (int w, int h, bool shadow); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SplashScreen) | |||||
}; | |||||
#endif // JUCE_SPLASHSCREEN_H_INCLUDED |
@@ -0,0 +1,36 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_WINDOWS || JUCE_LINUX || JUCE_MAC | |||||
SystemTrayIconComponent::SystemTrayIconComponent() | |||||
{ | |||||
addToDesktop (0); | |||||
} | |||||
SystemTrayIconComponent::~SystemTrayIconComponent() | |||||
{ | |||||
} | |||||
#endif |
@@ -0,0 +1,94 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_SYSTEMTRAYICONCOMPONENT_H_INCLUDED | |||||
#define JUCE_SYSTEMTRAYICONCOMPONENT_H_INCLUDED | |||||
#if JUCE_WINDOWS || JUCE_LINUX || JUCE_MAC || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
On Windows and Linux only, this component sits in the taskbar tray as a small icon. | |||||
To use it, just create one of these components, but don't attempt to make it | |||||
visible, add it to a parent, or put it on the desktop. | |||||
You can then call setIconImage() to create an icon for it in the taskbar. | |||||
To change the icon's tooltip, you can use setIconTooltip(). | |||||
To respond to mouse-events, you can override the normal mouseDown(), | |||||
mouseUp(), mouseDoubleClick() and mouseMove() methods, and although the x, y | |||||
position will not be valid, you can use this to respond to clicks. Traditionally | |||||
you'd use a left-click to show your application's window, and a right-click | |||||
to show a pop-up menu. | |||||
*/ | |||||
class JUCE_API SystemTrayIconComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
SystemTrayIconComponent(); | |||||
/** Destructor. */ | |||||
~SystemTrayIconComponent(); | |||||
//============================================================================== | |||||
/** Changes the image shown in the taskbar. */ | |||||
void setIconImage (const Image& newImage); | |||||
/** Changes the icon's tooltip (if the current OS supports this). */ | |||||
void setIconTooltip (const String& tooltip); | |||||
/** Highlights the icon (if the current OS supports this). */ | |||||
void setHighlighted (bool); | |||||
/** Shows a floating text bubble pointing to the icon (if the current OS supports this). */ | |||||
void showInfoBubble (const String& title, const String& content); | |||||
/** Hides the icon's floating text bubble (if the current OS supports this). */ | |||||
void hideInfoBubble(); | |||||
/** Returns the raw handle to whatever kind of internal OS structure is | |||||
involved in showing this icon. | |||||
@see ComponentPeer::getNativeHandle() | |||||
*/ | |||||
void* getNativeHandle() const; | |||||
#if JUCE_LINUX | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
#endif | |||||
private: | |||||
//============================================================================== | |||||
JUCE_PUBLIC_IN_DLL_BUILD (class Pimpl) | |||||
ScopedPointer<Pimpl> pimpl; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SystemTrayIconComponent) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_SYSTEMTRAYICONCOMPONENT_H_INCLUDED |
@@ -0,0 +1,128 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_WEBBROWSERCOMPONENT_H_INCLUDED | |||||
#define JUCE_WEBBROWSERCOMPONENT_H_INCLUDED | |||||
#if JUCE_WEB_BROWSER || DOXYGEN | |||||
//============================================================================== | |||||
/** | |||||
A component that displays an embedded web browser. | |||||
The browser itself will be platform-dependent. On the Mac, probably Safari, on | |||||
Windows, probably IE. | |||||
*/ | |||||
class JUCE_API WebBrowserComponent : public Component | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
/** Creates a WebBrowserComponent. | |||||
Once it's created and visible, send the browser to a URL using goToURL(). | |||||
@param unloadPageWhenBrowserIsHidden if this is true, then when the browser | |||||
component is taken offscreen, it'll clear the current page | |||||
and replace it with a blank page - this can be handy to stop | |||||
the browser using resources in the background when it's not | |||||
actually being used. | |||||
*/ | |||||
explicit WebBrowserComponent (bool unloadPageWhenBrowserIsHidden = true); | |||||
/** Destructor. */ | |||||
~WebBrowserComponent(); | |||||
//============================================================================== | |||||
/** Sends the browser to a particular URL. | |||||
@param url the URL to go to. | |||||
@param headers an optional set of parameters to put in the HTTP header. If | |||||
you supply this, it should be a set of string in the form | |||||
"HeaderKey: HeaderValue" | |||||
@param postData an optional block of data that will be attached to the HTTP | |||||
POST request | |||||
*/ | |||||
void goToURL (const String& url, | |||||
const StringArray* headers = nullptr, | |||||
const MemoryBlock* postData = nullptr); | |||||
/** Stops the current page loading. */ | |||||
void stop(); | |||||
/** Sends the browser back one page. */ | |||||
void goBack(); | |||||
/** Sends the browser forward one page. */ | |||||
void goForward(); | |||||
/** Refreshes the browser. */ | |||||
void refresh(); | |||||
//============================================================================== | |||||
/** This callback is called when the browser is about to navigate | |||||
to a new location. | |||||
You can override this method to perform some action when the user | |||||
tries to go to a particular URL. To allow the operation to carry on, | |||||
return true, or return false to stop the navigation happening. | |||||
*/ | |||||
virtual bool pageAboutToLoad (const String& newURL); | |||||
/** This callback happens when the browser has finished loading a page. */ | |||||
virtual void pageFinishedLoading (const String& url); | |||||
/** This callback occurs when a script or other activity in the browser asks for | |||||
the window to be closed. | |||||
*/ | |||||
virtual void windowCloseRequest(); | |||||
//============================================================================== | |||||
/** @internal */ | |||||
void paint (Graphics&) override; | |||||
/** @internal */ | |||||
void resized() override; | |||||
/** @internal */ | |||||
void parentHierarchyChanged() override; | |||||
/** @internal */ | |||||
void visibilityChanged() override; | |||||
private: | |||||
//============================================================================== | |||||
class Pimpl; | |||||
Pimpl* browser; | |||||
bool blankPageShown, unloadPageWhenBrowserIsHidden; | |||||
String lastURL; | |||||
StringArray lastHeaders; | |||||
MemoryBlock lastPostData; | |||||
void reloadLastURL(); | |||||
void checkWindowAssociation(); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent) | |||||
}; | |||||
#endif | |||||
#endif // JUCE_WEBBROWSERCOMPONENT_H_INCLUDED |
@@ -0,0 +1,109 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) | |||||
: browser (nullptr), | |||||
blankPageShown (false), | |||||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) | |||||
{ | |||||
setOpaque (true); | |||||
} | |||||
WebBrowserComponent::~WebBrowserComponent() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
lastURL = url; | |||||
lastHeaders.clear(); | |||||
if (headers != nullptr) | |||||
lastHeaders = *headers; | |||||
lastPostData.setSize (0); | |||||
if (postData != nullptr) | |||||
lastPostData = *postData; | |||||
blankPageShown = false; | |||||
} | |||||
void WebBrowserComponent::stop() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::goBack() | |||||
{ | |||||
lastURL.clear(); | |||||
blankPageShown = false; | |||||
} | |||||
void WebBrowserComponent::goForward() | |||||
{ | |||||
lastURL.clear(); | |||||
} | |||||
void WebBrowserComponent::refresh() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::paint (Graphics& g) | |||||
{ | |||||
g.fillAll (Colours::white); | |||||
} | |||||
void WebBrowserComponent::checkWindowAssociation() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::reloadLastURL() | |||||
{ | |||||
if (lastURL.isNotEmpty()) | |||||
{ | |||||
goToURL (lastURL, &lastHeaders, &lastPostData); | |||||
lastURL.clear(); | |||||
} | |||||
} | |||||
void WebBrowserComponent::parentHierarchyChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} | |||||
void WebBrowserComponent::resized() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::visibilityChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} |
@@ -0,0 +1,127 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class UIViewComponent::Pimpl : public ComponentMovementWatcher | |||||
{ | |||||
public: | |||||
Pimpl (UIView* const v, Component& comp) | |||||
: ComponentMovementWatcher (&comp), | |||||
view (v), | |||||
owner (comp), | |||||
currentPeer (nullptr) | |||||
{ | |||||
[view retain]; | |||||
if (owner.isShowing()) | |||||
componentPeerChanged(); | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
[view removeFromSuperview]; | |||||
[view release]; | |||||
} | |||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override | |||||
{ | |||||
Component* const topComp = owner.getTopLevelComponent(); | |||||
if (topComp->getPeer() != nullptr) | |||||
{ | |||||
const Point<int> pos (topComp->getLocalPoint (&owner, Point<int>())); | |||||
[view setFrame: CGRectMake ((float) pos.x, (float) pos.y, | |||||
(float) owner.getWidth(), (float) owner.getHeight())]; | |||||
} | |||||
} | |||||
void componentPeerChanged() override | |||||
{ | |||||
ComponentPeer* const peer = owner.getPeer(); | |||||
if (currentPeer != peer) | |||||
{ | |||||
if ([view superview] != nil) | |||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views | |||||
// override the call and use it as a sign that they're being deleted, which breaks everything.. | |||||
currentPeer = peer; | |||||
if (peer != nullptr) | |||||
{ | |||||
UIView* peerView = (UIView*) peer->getNativeHandle(); | |||||
[peerView addSubview: view]; | |||||
componentMovedOrResized (false, false); | |||||
} | |||||
} | |||||
[view setHidden: ! owner.isShowing()]; | |||||
} | |||||
void componentVisibilityChanged() override | |||||
{ | |||||
componentPeerChanged(); | |||||
} | |||||
Rectangle<int> getViewBounds() const | |||||
{ | |||||
CGRect r = [view frame]; | |||||
return Rectangle<int> ((int) r.size.width, (int) r.size.height); | |||||
} | |||||
UIView* const view; | |||||
private: | |||||
Component& owner; | |||||
ComponentPeer* currentPeer; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
}; | |||||
//============================================================================== | |||||
UIViewComponent::UIViewComponent() {} | |||||
UIViewComponent::~UIViewComponent() {} | |||||
void UIViewComponent::setView (void* const view) | |||||
{ | |||||
if (view != getView()) | |||||
{ | |||||
pimpl = nullptr; | |||||
if (view != nullptr) | |||||
pimpl = new Pimpl ((UIView*) view, *this); | |||||
} | |||||
} | |||||
void* UIViewComponent::getView() const | |||||
{ | |||||
return pimpl == nullptr ? nullptr : pimpl->view; | |||||
} | |||||
void UIViewComponent::resizeToFitView() | |||||
{ | |||||
if (pimpl != nullptr) | |||||
setBounds (pimpl->getViewBounds()); | |||||
} | |||||
void UIViewComponent::paint (Graphics&) {} |
@@ -0,0 +1,142 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
extern Display* display; | |||||
//============================================================================== | |||||
class SystemTrayIconComponent::Pimpl | |||||
{ | |||||
public: | |||||
Pimpl (const Image& im, Window windowH) : image (im) | |||||
{ | |||||
ScopedXLock xlock; | |||||
Screen* const screen = XDefaultScreenOfDisplay (display); | |||||
const int screenNumber = XScreenNumberOfScreen (screen); | |||||
String screenAtom ("_NET_SYSTEM_TRAY_S"); | |||||
screenAtom << screenNumber; | |||||
Atom selectionAtom = XInternAtom (display, screenAtom.toUTF8(), false); | |||||
XGrabServer (display); | |||||
Window managerWin = XGetSelectionOwner (display, selectionAtom); | |||||
if (managerWin != None) | |||||
XSelectInput (display, managerWin, StructureNotifyMask); | |||||
XUngrabServer (display); | |||||
XFlush (display); | |||||
if (managerWin != None) | |||||
{ | |||||
XEvent ev = { 0 }; | |||||
ev.xclient.type = ClientMessage; | |||||
ev.xclient.window = managerWin; | |||||
ev.xclient.message_type = XInternAtom (display, "_NET_SYSTEM_TRAY_OPCODE", False); | |||||
ev.xclient.format = 32; | |||||
ev.xclient.data.l[0] = CurrentTime; | |||||
ev.xclient.data.l[1] = 0 /*SYSTEM_TRAY_REQUEST_DOCK*/; | |||||
ev.xclient.data.l[2] = windowH; | |||||
ev.xclient.data.l[3] = 0; | |||||
ev.xclient.data.l[4] = 0; | |||||
XSendEvent (display, managerWin, False, NoEventMask, &ev); | |||||
XSync (display, False); | |||||
} | |||||
// For older KDE's ... | |||||
long atomData = 1; | |||||
Atom trayAtom = XInternAtom (display, "KWM_DOCKWINDOW", false); | |||||
XChangeProperty (display, windowH, trayAtom, trayAtom, 32, PropModeReplace, (unsigned char*) &atomData, 1); | |||||
// For more recent KDE's... | |||||
trayAtom = XInternAtom (display, "_KDE_NET_WM_SYSTEM_TRAY_WINDOW_FOR", false); | |||||
XChangeProperty (display, windowH, trayAtom, XA_WINDOW, 32, PropModeReplace, (unsigned char*) &windowH, 1); | |||||
// A minimum size must be specified for GNOME and Xfce, otherwise the icon is displayed with a width of 1 | |||||
XSizeHints* hints = XAllocSizeHints(); | |||||
hints->flags = PMinSize; | |||||
hints->min_width = 22; | |||||
hints->min_height = 22; | |||||
XSetWMNormalHints (display, windowH, hints); | |||||
XFree (hints); | |||||
} | |||||
Image image; | |||||
private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
}; | |||||
//============================================================================== | |||||
void SystemTrayIconComponent::setIconImage (const Image& newImage) | |||||
{ | |||||
pimpl = nullptr; | |||||
if (newImage.isValid()) | |||||
{ | |||||
if (! isOnDesktop()) | |||||
addToDesktop (0); | |||||
pimpl = new Pimpl (newImage, (Window) getWindowHandle()); | |||||
setVisible (true); | |||||
toFront (false); | |||||
} | |||||
repaint(); | |||||
} | |||||
void SystemTrayIconComponent::paint (Graphics& g) | |||||
{ | |||||
if (pimpl != nullptr) | |||||
g.drawImageWithin (pimpl->image, 0, 0, getWidth(), getHeight(), | |||||
RectanglePlacement::xLeft | RectanglePlacement::yTop | RectanglePlacement::onlyReduceInSize, false); | |||||
} | |||||
void SystemTrayIconComponent::setIconTooltip (const String& /*tooltip*/) | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void SystemTrayIconComponent::setHighlighted (bool) | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/) | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void SystemTrayIconComponent::hideInfoBubble() | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void* SystemTrayIconComponent::getNativeHandle() const | |||||
{ | |||||
return getWindowHandle(); | |||||
} |
@@ -0,0 +1,114 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
/* | |||||
Sorry.. This class isn't implemented on Linux! | |||||
*/ | |||||
//============================================================================== | |||||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) | |||||
: browser (0), | |||||
blankPageShown (false), | |||||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) | |||||
{ | |||||
setOpaque (true); | |||||
} | |||||
WebBrowserComponent::~WebBrowserComponent() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
lastURL = url; | |||||
lastHeaders.clear(); | |||||
if (headers != nullptr) | |||||
lastHeaders = *headers; | |||||
lastPostData.setSize (0); | |||||
if (postData != nullptr) | |||||
lastPostData = *postData; | |||||
blankPageShown = false; | |||||
} | |||||
void WebBrowserComponent::stop() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::goBack() | |||||
{ | |||||
lastURL.clear(); | |||||
blankPageShown = false; | |||||
} | |||||
void WebBrowserComponent::goForward() | |||||
{ | |||||
lastURL.clear(); | |||||
} | |||||
void WebBrowserComponent::refresh() | |||||
{ | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::paint (Graphics& g) | |||||
{ | |||||
g.fillAll (Colours::white); | |||||
} | |||||
void WebBrowserComponent::checkWindowAssociation() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::reloadLastURL() | |||||
{ | |||||
if (lastURL.isNotEmpty()) | |||||
{ | |||||
goToURL (lastURL, &lastHeaders, &lastPostData); | |||||
lastURL.clear(); | |||||
} | |||||
} | |||||
void WebBrowserComponent::parentHierarchyChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} | |||||
void WebBrowserComponent::resized() | |||||
{ | |||||
} | |||||
void WebBrowserComponent::visibilityChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} |
@@ -0,0 +1,263 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
AppleRemoteDevice::AppleRemoteDevice() | |||||
: device (0), | |||||
queue (0), | |||||
remoteId (0) | |||||
{ | |||||
} | |||||
AppleRemoteDevice::~AppleRemoteDevice() | |||||
{ | |||||
stop(); | |||||
} | |||||
namespace | |||||
{ | |||||
io_object_t getAppleRemoteDevice() | |||||
{ | |||||
CFMutableDictionaryRef dict = IOServiceMatching ("AppleIRController"); | |||||
io_iterator_t iter = 0; | |||||
io_object_t iod = 0; | |||||
if (IOServiceGetMatchingServices (kIOMasterPortDefault, dict, &iter) == kIOReturnSuccess | |||||
&& iter != 0) | |||||
{ | |||||
iod = IOIteratorNext (iter); | |||||
} | |||||
IOObjectRelease (iter); | |||||
return iod; | |||||
} | |||||
bool createAppleRemoteInterface (io_object_t iod, void** device) | |||||
{ | |||||
jassert (*device == nullptr); | |||||
io_name_t classname; | |||||
if (IOObjectGetClass (iod, classname) == kIOReturnSuccess) | |||||
{ | |||||
IOCFPlugInInterface** cfPlugInInterface = nullptr; | |||||
SInt32 score = 0; | |||||
if (IOCreatePlugInInterfaceForService (iod, | |||||
kIOHIDDeviceUserClientTypeID, | |||||
kIOCFPlugInInterfaceID, | |||||
&cfPlugInInterface, | |||||
&score) == kIOReturnSuccess) | |||||
{ | |||||
HRESULT hr = (*cfPlugInInterface)->QueryInterface (cfPlugInInterface, | |||||
CFUUIDGetUUIDBytes (kIOHIDDeviceInterfaceID), | |||||
device); | |||||
(void) hr; | |||||
(*cfPlugInInterface)->Release (cfPlugInInterface); | |||||
} | |||||
} | |||||
return *device != 0; | |||||
} | |||||
void appleRemoteQueueCallback (void* const target, const IOReturn result, void*, void*) | |||||
{ | |||||
if (result == kIOReturnSuccess) | |||||
((AppleRemoteDevice*) target)->handleCallbackInternal(); | |||||
} | |||||
} | |||||
bool AppleRemoteDevice::start (const bool inExclusiveMode) | |||||
{ | |||||
if (queue != 0) | |||||
return true; | |||||
stop(); | |||||
bool result = false; | |||||
io_object_t iod = getAppleRemoteDevice(); | |||||
if (iod != 0) | |||||
{ | |||||
if (createAppleRemoteInterface (iod, &device) && open (inExclusiveMode)) | |||||
result = true; | |||||
else | |||||
stop(); | |||||
IOObjectRelease (iod); | |||||
} | |||||
return result; | |||||
} | |||||
void AppleRemoteDevice::stop() | |||||
{ | |||||
if (queue != 0) | |||||
{ | |||||
(*(IOHIDQueueInterface**) queue)->stop ((IOHIDQueueInterface**) queue); | |||||
(*(IOHIDQueueInterface**) queue)->dispose ((IOHIDQueueInterface**) queue); | |||||
(*(IOHIDQueueInterface**) queue)->Release ((IOHIDQueueInterface**) queue); | |||||
queue = 0; | |||||
} | |||||
if (device != 0) | |||||
{ | |||||
(*(IOHIDDeviceInterface**) device)->close ((IOHIDDeviceInterface**) device); | |||||
(*(IOHIDDeviceInterface**) device)->Release ((IOHIDDeviceInterface**) device); | |||||
device = 0; | |||||
} | |||||
} | |||||
bool AppleRemoteDevice::isActive() const | |||||
{ | |||||
return queue != 0; | |||||
} | |||||
bool AppleRemoteDevice::open (const bool openInExclusiveMode) | |||||
{ | |||||
Array <int> cookies; | |||||
CFArrayRef elements; | |||||
IOHIDDeviceInterface122** const device122 = (IOHIDDeviceInterface122**) device; | |||||
if ((*device122)->copyMatchingElements (device122, 0, &elements) != kIOReturnSuccess) | |||||
return false; | |||||
for (int i = 0; i < CFArrayGetCount (elements); ++i) | |||||
{ | |||||
CFDictionaryRef element = (CFDictionaryRef) CFArrayGetValueAtIndex (elements, i); | |||||
// get the cookie | |||||
CFTypeRef object = CFDictionaryGetValue (element, CFSTR (kIOHIDElementCookieKey)); | |||||
if (object == 0 || CFGetTypeID (object) != CFNumberGetTypeID()) | |||||
continue; | |||||
long number; | |||||
if (! CFNumberGetValue ((CFNumberRef) object, kCFNumberLongType, &number)) | |||||
continue; | |||||
cookies.add ((int) number); | |||||
} | |||||
CFRelease (elements); | |||||
if ((*(IOHIDDeviceInterface**) device) | |||||
->open ((IOHIDDeviceInterface**) device, | |||||
openInExclusiveMode ? kIOHIDOptionsTypeSeizeDevice | |||||
: kIOHIDOptionsTypeNone) == KERN_SUCCESS) | |||||
{ | |||||
queue = (*(IOHIDDeviceInterface**) device)->allocQueue ((IOHIDDeviceInterface**) device); | |||||
if (queue != 0) | |||||
{ | |||||
(*(IOHIDQueueInterface**) queue)->create ((IOHIDQueueInterface**) queue, 0, 12); | |||||
for (int i = 0; i < cookies.size(); ++i) | |||||
{ | |||||
IOHIDElementCookie cookie = (IOHIDElementCookie) cookies.getUnchecked(i); | |||||
(*(IOHIDQueueInterface**) queue)->addElement ((IOHIDQueueInterface**) queue, cookie, 0); | |||||
} | |||||
CFRunLoopSourceRef eventSource; | |||||
if ((*(IOHIDQueueInterface**) queue) | |||||
->createAsyncEventSource ((IOHIDQueueInterface**) queue, &eventSource) == KERN_SUCCESS) | |||||
{ | |||||
if ((*(IOHIDQueueInterface**) queue)->setEventCallout ((IOHIDQueueInterface**) queue, | |||||
appleRemoteQueueCallback, this, 0) == KERN_SUCCESS) | |||||
{ | |||||
CFRunLoopAddSource (CFRunLoopGetCurrent(), eventSource, kCFRunLoopDefaultMode); | |||||
(*(IOHIDQueueInterface**) queue)->start ((IOHIDQueueInterface**) queue); | |||||
return true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
void AppleRemoteDevice::handleCallbackInternal() | |||||
{ | |||||
int totalValues = 0; | |||||
AbsoluteTime nullTime = { 0, 0 }; | |||||
char cookies [12]; | |||||
int numCookies = 0; | |||||
while (numCookies < numElementsInArray (cookies)) | |||||
{ | |||||
IOHIDEventStruct e; | |||||
if ((*(IOHIDQueueInterface**) queue)->getNextEvent ((IOHIDQueueInterface**) queue, &e, nullTime, 0) != kIOReturnSuccess) | |||||
break; | |||||
if ((int) e.elementCookie == 19) | |||||
{ | |||||
remoteId = e.value; | |||||
buttonPressed (switched, false); | |||||
} | |||||
else | |||||
{ | |||||
totalValues += e.value; | |||||
cookies [numCookies++] = (char) (pointer_sized_int) e.elementCookie; | |||||
} | |||||
} | |||||
cookies [numCookies++] = 0; | |||||
//DBG (String::toHexString ((uint8*) cookies, numCookies, 1) + " " + String (totalValues)); | |||||
static const char buttonPatterns[] = | |||||
{ | |||||
0x1f, 0x14, 0x12, 0x1f, 0x14, 0x12, 0, | |||||
0x1f, 0x15, 0x12, 0x1f, 0x15, 0x12, 0, | |||||
0x1f, 0x1d, 0x1c, 0x12, 0, | |||||
0x1f, 0x1e, 0x1c, 0x12, 0, | |||||
0x1f, 0x16, 0x12, 0x1f, 0x16, 0x12, 0, | |||||
0x1f, 0x17, 0x12, 0x1f, 0x17, 0x12, 0, | |||||
0x1f, 0x12, 0x04, 0x02, 0, | |||||
0x1f, 0x12, 0x03, 0x02, 0, | |||||
0x1f, 0x12, 0x1f, 0x12, 0, | |||||
0x23, 0x1f, 0x12, 0x23, 0x1f, 0x12, 0, | |||||
19, 0 | |||||
}; | |||||
int buttonNum = (int) menuButton; | |||||
int i = 0; | |||||
while (i < numElementsInArray (buttonPatterns)) | |||||
{ | |||||
if (strcmp (cookies, buttonPatterns + i) == 0) | |||||
{ | |||||
buttonPressed ((ButtonType) buttonNum, totalValues > 0); | |||||
break; | |||||
} | |||||
i += (int) strlen (buttonPatterns + i) + 1; | |||||
++buttonNum; | |||||
} | |||||
} |
@@ -0,0 +1,332 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#ifndef JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_H_INCLUDED | |||||
#define JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Creates a floating carbon window that can be used to hold a carbon UI. | |||||
This is a handy class that's designed to be inlined where needed, e.g. | |||||
in the audio plugin hosting code. | |||||
*/ | |||||
class CarbonViewWrapperComponent : public Component, | |||||
public ComponentMovementWatcher, | |||||
public Timer | |||||
{ | |||||
public: | |||||
CarbonViewWrapperComponent() | |||||
: ComponentMovementWatcher (this), | |||||
keepPluginWindowWhenHidden (false), | |||||
wrapperWindow (0), | |||||
carbonWindow (0), | |||||
embeddedView (0), | |||||
recursiveResize (false), | |||||
repaintChildOnCreation (true) | |||||
{ | |||||
} | |||||
~CarbonViewWrapperComponent() | |||||
{ | |||||
jassert (embeddedView == 0); // must call deleteWindow() in the subclass's destructor! | |||||
} | |||||
virtual HIViewRef attachView (WindowRef windowRef, HIViewRef rootView) = 0; | |||||
virtual void removeView (HIViewRef embeddedView) = 0; | |||||
virtual void handleMouseDown (int, int) {} | |||||
virtual void handlePaint() {} | |||||
virtual bool getEmbeddedViewSize (int& w, int& h) | |||||
{ | |||||
if (embeddedView == 0) | |||||
return false; | |||||
HIRect bounds; | |||||
HIViewGetBounds (embeddedView, &bounds); | |||||
w = jmax (1, roundToInt (bounds.size.width)); | |||||
h = jmax (1, roundToInt (bounds.size.height)); | |||||
return true; | |||||
} | |||||
void createWindow() | |||||
{ | |||||
if (wrapperWindow == 0) | |||||
{ | |||||
Rect r; | |||||
r.left = (short) getScreenX(); | |||||
r.top = (short) getScreenY(); | |||||
r.right = (short) (r.left + getWidth()); | |||||
r.bottom = (short) (r.top + getHeight()); | |||||
CreateNewWindow (kDocumentWindowClass, | |||||
(WindowAttributes) (kWindowStandardHandlerAttribute | kWindowCompositingAttribute | |||||
| kWindowNoShadowAttribute | kWindowNoTitleBarAttribute), | |||||
&r, &wrapperWindow); | |||||
jassert (wrapperWindow != 0); | |||||
if (wrapperWindow == 0) | |||||
return; | |||||
carbonWindow = [[NSWindow alloc] initWithWindowRef: wrapperWindow]; | |||||
[getOwnerWindow() addChildWindow: carbonWindow | |||||
ordered: NSWindowAbove]; | |||||
embeddedView = attachView (wrapperWindow, HIViewGetRoot (wrapperWindow)); | |||||
// Check for the plugin creating its own floating window, and if there is one, | |||||
// we need to reparent it to make it visible.. | |||||
if (NSWindow* floatingChildWindow = [[carbonWindow childWindows] objectAtIndex: 0]) | |||||
[getOwnerWindow() addChildWindow: floatingChildWindow | |||||
ordered: NSWindowAbove]; | |||||
EventTypeSpec windowEventTypes[] = | |||||
{ | |||||
{ kEventClassWindow, kEventWindowGetClickActivation }, | |||||
{ kEventClassWindow, kEventWindowHandleDeactivate }, | |||||
{ kEventClassWindow, kEventWindowBoundsChanging }, | |||||
{ kEventClassMouse, kEventMouseDown }, | |||||
{ kEventClassMouse, kEventMouseMoved }, | |||||
{ kEventClassMouse, kEventMouseDragged }, | |||||
{ kEventClassMouse, kEventMouseUp }, | |||||
{ kEventClassWindow, kEventWindowDrawContent }, | |||||
{ kEventClassWindow, kEventWindowShown }, | |||||
{ kEventClassWindow, kEventWindowHidden } | |||||
}; | |||||
EventHandlerUPP upp = NewEventHandlerUPP (carbonEventCallback); | |||||
InstallWindowEventHandler (wrapperWindow, upp, | |||||
sizeof (windowEventTypes) / sizeof (EventTypeSpec), | |||||
windowEventTypes, this, &eventHandlerRef); | |||||
setOurSizeToEmbeddedViewSize(); | |||||
setEmbeddedWindowToOurSize(); | |||||
creationTime = Time::getCurrentTime(); | |||||
} | |||||
} | |||||
void deleteWindow() | |||||
{ | |||||
removeView (embeddedView); | |||||
embeddedView = 0; | |||||
if (wrapperWindow != 0) | |||||
{ | |||||
NSWindow* ownerWindow = getOwnerWindow(); | |||||
if ([[ownerWindow childWindows] count] > 0) | |||||
{ | |||||
[ownerWindow removeChildWindow: carbonWindow]; | |||||
[carbonWindow close]; | |||||
} | |||||
RemoveEventHandler (eventHandlerRef); | |||||
DisposeWindow (wrapperWindow); | |||||
wrapperWindow = 0; | |||||
} | |||||
} | |||||
//============================================================================== | |||||
void setOurSizeToEmbeddedViewSize() | |||||
{ | |||||
int w, h; | |||||
if (getEmbeddedViewSize (w, h)) | |||||
{ | |||||
if (w != getWidth() || h != getHeight()) | |||||
{ | |||||
startTimer (50); | |||||
setSize (w, h); | |||||
if (Component* p = getParentComponent()) | |||||
p->setSize (w, h); | |||||
} | |||||
else | |||||
{ | |||||
startTimer (jlimit (50, 500, getTimerInterval() + 20)); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
stopTimer(); | |||||
} | |||||
} | |||||
void setEmbeddedWindowToOurSize() | |||||
{ | |||||
if (! recursiveResize) | |||||
{ | |||||
recursiveResize = true; | |||||
if (embeddedView != 0) | |||||
{ | |||||
HIRect r; | |||||
r.origin.x = 0; | |||||
r.origin.y = 0; | |||||
r.size.width = (float) getWidth(); | |||||
r.size.height = (float) getHeight(); | |||||
HIViewSetFrame (embeddedView, &r); | |||||
} | |||||
if (wrapperWindow != 0) | |||||
{ | |||||
jassert (getTopLevelComponent()->getDesktopScaleFactor() == 1.0f); | |||||
Rectangle<int> screenBounds (getScreenBounds() * Desktop::getInstance().getGlobalScaleFactor()); | |||||
Rect wr; | |||||
wr.left = (short) screenBounds.getX(); | |||||
wr.top = (short) screenBounds.getY(); | |||||
wr.right = (short) screenBounds.getRight(); | |||||
wr.bottom = (short) screenBounds.getBottom(); | |||||
SetWindowBounds (wrapperWindow, kWindowContentRgn, &wr); | |||||
// This group stuff is mainly a workaround for Mackie plugins like FinalMix.. | |||||
WindowGroupRef group = GetWindowGroup (wrapperWindow); | |||||
WindowRef attachedWindow; | |||||
if (GetIndexedWindow (group, 2, kWindowGroupContentsReturnWindows, &attachedWindow) == noErr) | |||||
{ | |||||
SelectWindow (attachedWindow); | |||||
ActivateWindow (attachedWindow, TRUE); | |||||
HideWindow (wrapperWindow); | |||||
} | |||||
ShowWindow (wrapperWindow); | |||||
} | |||||
recursiveResize = false; | |||||
} | |||||
} | |||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override | |||||
{ | |||||
setEmbeddedWindowToOurSize(); | |||||
} | |||||
// (overridden to intercept movements of the top-level window) | |||||
void componentMovedOrResized (Component& component, bool wasMoved, bool wasResized) override | |||||
{ | |||||
ComponentMovementWatcher::componentMovedOrResized (component, wasMoved, wasResized); | |||||
if (&component == getTopLevelComponent()) | |||||
setEmbeddedWindowToOurSize(); | |||||
} | |||||
void componentPeerChanged() override | |||||
{ | |||||
deleteWindow(); | |||||
createWindow(); | |||||
} | |||||
void componentVisibilityChanged() override | |||||
{ | |||||
if (isShowing()) | |||||
createWindow(); | |||||
else if (! keepPluginWindowWhenHidden) | |||||
deleteWindow(); | |||||
setEmbeddedWindowToOurSize(); | |||||
} | |||||
static void recursiveHIViewRepaint (HIViewRef view) | |||||
{ | |||||
HIViewSetNeedsDisplay (view, true); | |||||
HIViewRef child = HIViewGetFirstSubview (view); | |||||
while (child != 0) | |||||
{ | |||||
recursiveHIViewRepaint (child); | |||||
child = HIViewGetNextView (child); | |||||
} | |||||
} | |||||
void timerCallback() override | |||||
{ | |||||
if (isShowing()) | |||||
{ | |||||
setOurSizeToEmbeddedViewSize(); | |||||
// To avoid strange overpainting problems when the UI is first opened, we'll | |||||
// repaint it a few times during the first second that it's on-screen.. | |||||
if (repaintChildOnCreation && (Time::getCurrentTime() - creationTime).inMilliseconds() < 1000) | |||||
recursiveHIViewRepaint (HIViewGetRoot (wrapperWindow)); | |||||
} | |||||
} | |||||
void setRepaintsChildHIViewWhenCreated (bool b) noexcept | |||||
{ | |||||
repaintChildOnCreation = b; | |||||
} | |||||
OSStatus carbonEventHandler (EventHandlerCallRef /*nextHandlerRef*/, EventRef event) | |||||
{ | |||||
switch (GetEventKind (event)) | |||||
{ | |||||
case kEventWindowHandleDeactivate: | |||||
ActivateWindow (wrapperWindow, TRUE); | |||||
return noErr; | |||||
case kEventWindowGetClickActivation: | |||||
{ | |||||
getTopLevelComponent()->toFront (false); | |||||
[carbonWindow makeKeyAndOrderFront: nil]; | |||||
ClickActivationResult howToHandleClick = kActivateAndHandleClick; | |||||
SetEventParameter (event, kEventParamClickActivation, typeClickActivationResult, | |||||
sizeof (ClickActivationResult), &howToHandleClick); | |||||
if (embeddedView != 0) | |||||
HIViewSetNeedsDisplay (embeddedView, true); | |||||
return noErr; | |||||
} | |||||
} | |||||
return eventNotHandledErr; | |||||
} | |||||
static pascal OSStatus carbonEventCallback (EventHandlerCallRef nextHandlerRef, EventRef event, void* userData) | |||||
{ | |||||
return ((CarbonViewWrapperComponent*) userData)->carbonEventHandler (nextHandlerRef, event); | |||||
} | |||||
bool keepPluginWindowWhenHidden; | |||||
protected: | |||||
WindowRef wrapperWindow; | |||||
NSWindow* carbonWindow; | |||||
HIViewRef embeddedView; | |||||
bool recursiveResize, repaintChildOnCreation; | |||||
Time creationTime; | |||||
EventHandlerRef eventHandlerRef; | |||||
NSWindow* getOwnerWindow() const { return [((NSView*) getWindowHandle()) window]; } | |||||
}; | |||||
#endif // JUCE_MAC_CARBONVIEWWRAPPERCOMPONENT_H_INCLUDED |
@@ -0,0 +1,183 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class NSViewAttachment : public ReferenceCountedObject, | |||||
public ComponentMovementWatcher | |||||
{ | |||||
public: | |||||
NSViewAttachment (NSView* const v, Component& comp) | |||||
: ComponentMovementWatcher (&comp), | |||||
view (v), owner (comp), | |||||
currentPeer (nullptr), frameChangeCallback (nullptr) | |||||
{ | |||||
[view retain]; | |||||
[view setPostsFrameChangedNotifications: YES]; | |||||
if (owner.isShowing()) | |||||
componentPeerChanged(); | |||||
static ViewFrameChangeCallbackClass cls; | |||||
frameChangeCallback = [cls.createInstance() init]; | |||||
ViewFrameChangeCallbackClass::setTarget (frameChangeCallback, &owner); | |||||
[[NSNotificationCenter defaultCenter] addObserver: frameChangeCallback | |||||
selector: @selector (frameChanged:) | |||||
name: NSViewFrameDidChangeNotification | |||||
object: view]; | |||||
} | |||||
~NSViewAttachment() | |||||
{ | |||||
[[NSNotificationCenter defaultCenter] removeObserver: frameChangeCallback]; | |||||
[frameChangeCallback release]; | |||||
removeFromParent(); | |||||
[view release]; | |||||
} | |||||
void componentMovedOrResized (Component& comp, bool wasMoved, bool wasResized) override | |||||
{ | |||||
ComponentMovementWatcher::componentMovedOrResized (comp, wasMoved, wasResized); | |||||
// The ComponentMovementWatcher version of this method avoids calling | |||||
// us when the top-level comp is resized, but for an NSView we need to know this | |||||
// because with inverted coordinates, we need to update the position even if the | |||||
// top-left pos hasn't changed | |||||
if (comp.isOnDesktop() && wasResized) | |||||
componentMovedOrResized (wasMoved, wasResized); | |||||
} | |||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override | |||||
{ | |||||
if (ComponentPeer* const peer = owner.getTopLevelComponent()->getPeer()) | |||||
{ | |||||
NSRect r = makeNSRect (peer->getAreaCoveredBy (owner)); | |||||
r.origin.y = [[view superview] frame].size.height - (r.origin.y + r.size.height); | |||||
[view setFrame: r]; | |||||
} | |||||
} | |||||
void componentPeerChanged() override | |||||
{ | |||||
ComponentPeer* const peer = owner.getPeer(); | |||||
if (currentPeer != peer) | |||||
{ | |||||
removeFromParent(); | |||||
currentPeer = peer; | |||||
if (peer != nullptr) | |||||
{ | |||||
NSView* const peerView = (NSView*) peer->getNativeHandle(); | |||||
[peerView addSubview: view]; | |||||
componentMovedOrResized (false, false); | |||||
} | |||||
} | |||||
[view setHidden: ! owner.isShowing()]; | |||||
} | |||||
void componentVisibilityChanged() override | |||||
{ | |||||
componentPeerChanged(); | |||||
} | |||||
NSView* const view; | |||||
private: | |||||
Component& owner; | |||||
ComponentPeer* currentPeer; | |||||
id frameChangeCallback; | |||||
void removeFromParent() | |||||
{ | |||||
if ([view superview] != nil) | |||||
[view removeFromSuperview]; // Must be careful not to call this unless it's required - e.g. some Apple AU views | |||||
// override the call and use it as a sign that they're being deleted, which breaks everything.. | |||||
} | |||||
//============================================================================== | |||||
struct ViewFrameChangeCallbackClass : public ObjCClass<NSObject> | |||||
{ | |||||
ViewFrameChangeCallbackClass() : ObjCClass<NSObject> ("JUCE_NSViewCallback_") | |||||
{ | |||||
addIvar<Component*> ("target"); | |||||
addMethod (@selector (frameChanged:), frameChanged, "v@:@"); | |||||
registerClass(); | |||||
} | |||||
static void setTarget (id self, Component* c) | |||||
{ | |||||
object_setInstanceVariable (self, "target", c); | |||||
} | |||||
private: | |||||
static void frameChanged (id self, SEL, NSNotification*) | |||||
{ | |||||
if (Component* const target = getIvar<Component*> (self, "target")) | |||||
target->childBoundsChanged (nullptr); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE (ViewFrameChangeCallbackClass); | |||||
}; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NSViewAttachment) | |||||
}; | |||||
//============================================================================== | |||||
NSViewComponent::NSViewComponent() {} | |||||
NSViewComponent::~NSViewComponent() {} | |||||
void NSViewComponent::setView (void* const view) | |||||
{ | |||||
if (view != getView()) | |||||
{ | |||||
attachment = nullptr; | |||||
if (view != nullptr) | |||||
attachment = attachViewToComponent (*this, view); | |||||
} | |||||
} | |||||
void* NSViewComponent::getView() const | |||||
{ | |||||
return attachment != nullptr ? static_cast<NSViewAttachment*> (attachment.get())->view | |||||
: nullptr; | |||||
} | |||||
void NSViewComponent::resizeToFitView() | |||||
{ | |||||
if (attachment != nullptr) | |||||
{ | |||||
NSRect r = [static_cast<NSViewAttachment*> (attachment.get())->view frame]; | |||||
setBounds (Rectangle<int> ((int) r.size.width, (int) r.size.height)); | |||||
} | |||||
} | |||||
void NSViewComponent::paint (Graphics&) {} | |||||
ReferenceCountedObject* NSViewComponent::attachViewToComponent (Component& comp, void* const view) | |||||
{ | |||||
return new NSViewAttachment ((NSView*) view, comp); | |||||
} |
@@ -0,0 +1,241 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
namespace MouseCursorHelpers | |||||
{ | |||||
extern NSImage* createNSImage (const Image&); | |||||
} | |||||
class SystemTrayIconComponent::Pimpl | |||||
{ | |||||
public: | |||||
Pimpl (SystemTrayIconComponent& iconComp, const Image& im) | |||||
: owner (iconComp), statusItem (nil), | |||||
statusIcon (MouseCursorHelpers::createNSImage (im)), | |||||
isHighlighted (false) | |||||
{ | |||||
static SystemTrayViewClass cls; | |||||
view = [cls.createInstance() init]; | |||||
SystemTrayViewClass::setOwner (view, this); | |||||
SystemTrayViewClass::setImage (view, statusIcon); | |||||
setIconSize(); | |||||
statusItem = [[[NSStatusBar systemStatusBar] statusItemWithLength: NSSquareStatusItemLength] retain]; | |||||
[statusItem setView: view]; | |||||
[[NSNotificationCenter defaultCenter] addObserver: view | |||||
selector: @selector (frameChanged:) | |||||
name: NSWindowDidMoveNotification | |||||
object: nil]; | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
[[NSStatusBar systemStatusBar] removeStatusItem: statusItem]; | |||||
[statusItem release]; | |||||
[view release]; | |||||
[statusIcon release]; | |||||
} | |||||
void updateIcon (const Image& newImage) | |||||
{ | |||||
[statusIcon release]; | |||||
statusIcon = MouseCursorHelpers::createNSImage (newImage); | |||||
setIconSize(); | |||||
SystemTrayViewClass::setImage (view, statusIcon); | |||||
} | |||||
void setHighlighted (bool shouldHighlight) | |||||
{ | |||||
isHighlighted = shouldHighlight; | |||||
[view setNeedsDisplay: true]; | |||||
} | |||||
void handleStatusItemAction (NSEvent* e) | |||||
{ | |||||
NSEventType type = [e type]; | |||||
const bool isLeft = (type == NSLeftMouseDown || type == NSLeftMouseUp); | |||||
const bool isRight = (type == NSRightMouseDown || type == NSRightMouseUp); | |||||
if (owner.isCurrentlyBlockedByAnotherModalComponent()) | |||||
{ | |||||
if (isLeft || isRight) | |||||
if (Component* const current = Component::getCurrentlyModalComponent()) | |||||
current->inputAttemptWhenModal(); | |||||
} | |||||
else | |||||
{ | |||||
ModifierKeys eventMods (ModifierKeys::getCurrentModifiersRealtime()); | |||||
if (([e modifierFlags] & NSCommandKeyMask) != 0) | |||||
eventMods = eventMods.withFlags (ModifierKeys::commandModifier); | |||||
const Time now (Time::getCurrentTime()); | |||||
MouseInputSource mouseSource = Desktop::getInstance().getMainMouseSource(); | |||||
if (isLeft || isRight) // Only mouse up is sent by the OS, so simulate a down/up | |||||
{ | |||||
owner.mouseDown (MouseEvent (mouseSource, Point<int>(), | |||||
eventMods.withFlags (isLeft ? ModifierKeys::leftButtonModifier | |||||
: ModifierKeys::rightButtonModifier), | |||||
&owner, &owner, now, | |||||
Point<int>(), now, 1, false)); | |||||
owner.mouseUp (MouseEvent (mouseSource, Point<int>(), eventMods.withoutMouseButtons(), | |||||
&owner, &owner, now, | |||||
Point<int>(), now, 1, false)); | |||||
} | |||||
else if (type == NSMouseMoved) | |||||
{ | |||||
owner.mouseMove (MouseEvent (mouseSource, Point<int>(), eventMods, | |||||
&owner, &owner, now, | |||||
Point<int>(), now, 1, false)); | |||||
} | |||||
} | |||||
} | |||||
SystemTrayIconComponent& owner; | |||||
NSStatusItem* statusItem; | |||||
private: | |||||
NSImage* statusIcon; | |||||
NSControl* view; | |||||
bool isHighlighted; | |||||
void setIconSize() | |||||
{ | |||||
[statusIcon setSize: NSMakeSize (20.0f, 20.0f)]; | |||||
} | |||||
struct SystemTrayViewClass : public ObjCClass <NSControl> | |||||
{ | |||||
SystemTrayViewClass() : ObjCClass <NSControl> ("JUCESystemTrayView_") | |||||
{ | |||||
addIvar<Pimpl*> ("owner"); | |||||
addIvar<NSImage*> ("image"); | |||||
addMethod (@selector (mouseDown:), handleEventDown, "v@:@"); | |||||
addMethod (@selector (rightMouseDown:), handleEventDown, "v@:@"); | |||||
addMethod (@selector (drawRect:), drawRect, "v@:@"); | |||||
addMethod (@selector (frameChanged:), frameChanged, "v@:@"); | |||||
registerClass(); | |||||
} | |||||
static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); } | |||||
static NSImage* getImage (id self) { return getIvar<NSImage*> (self, "image"); } | |||||
static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); } | |||||
static void setImage (id self, NSImage* image) { object_setInstanceVariable (self, "image", image); } | |||||
private: | |||||
static void handleEventDown (id self, SEL, NSEvent* e) | |||||
{ | |||||
if (Pimpl* const owner = getOwner (self)) | |||||
{ | |||||
owner->setHighlighted (! owner->isHighlighted); | |||||
owner->handleStatusItemAction (e); | |||||
} | |||||
} | |||||
static void drawRect (id self, SEL, NSRect) | |||||
{ | |||||
NSRect bounds = [self bounds]; | |||||
if (Pimpl* const owner = getOwner (self)) | |||||
[owner->statusItem drawStatusBarBackgroundInRect: bounds | |||||
withHighlight: owner->isHighlighted]; | |||||
if (NSImage* const im = getImage (self)) | |||||
{ | |||||
NSSize imageSize = [im size]; | |||||
[im drawInRect: NSMakeRect (bounds.origin.x + ((bounds.size.width - imageSize.width) / 2.0f), | |||||
bounds.origin.y + ((bounds.size.height - imageSize.height) / 2.0f), | |||||
imageSize.width, imageSize.height) | |||||
fromRect: NSZeroRect | |||||
operation: NSCompositeSourceOver | |||||
fraction: 1.0f]; | |||||
} | |||||
} | |||||
static void frameChanged (id self, SEL, NSNotification*) | |||||
{ | |||||
if (Pimpl* const owner = getOwner (self)) | |||||
{ | |||||
NSRect r = [[[owner->statusItem view] window] frame]; | |||||
NSRect sr = [[[NSScreen screens] objectAtIndex: 0] frame]; | |||||
r.origin.y = sr.size.height - r.origin.y - r.size.height; | |||||
owner->owner.setBounds (convertToRectInt (r)); | |||||
} | |||||
} | |||||
}; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
}; | |||||
//============================================================================== | |||||
void SystemTrayIconComponent::setIconImage (const Image& newImage) | |||||
{ | |||||
if (newImage.isValid()) | |||||
{ | |||||
if (pimpl == nullptr) | |||||
pimpl = new Pimpl (*this, newImage); | |||||
else | |||||
pimpl->updateIcon (newImage); | |||||
} | |||||
else | |||||
{ | |||||
pimpl = nullptr; | |||||
} | |||||
} | |||||
void SystemTrayIconComponent::setIconTooltip (const String&) | |||||
{ | |||||
// xxx not yet implemented! | |||||
} | |||||
void SystemTrayIconComponent::setHighlighted (bool highlight) | |||||
{ | |||||
if (pimpl != nullptr) | |||||
pimpl->setHighlighted (highlight); | |||||
} | |||||
void SystemTrayIconComponent::showInfoBubble (const String& /*title*/, const String& /*content*/) | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void SystemTrayIconComponent::hideInfoBubble() | |||||
{ | |||||
// xxx Not implemented! | |||||
} | |||||
void* SystemTrayIconComponent::getNativeHandle() const | |||||
{ | |||||
return pimpl != nullptr ? pimpl->statusItem : nullptr; | |||||
} |
@@ -0,0 +1,354 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
#if JUCE_MAC | |||||
struct DownloadClickDetectorClass : public ObjCClass <NSObject> | |||||
{ | |||||
DownloadClickDetectorClass() : ObjCClass <NSObject> ("JUCEWebClickDetector_") | |||||
{ | |||||
addIvar <WebBrowserComponent*> ("owner"); | |||||
addMethod (@selector (webView:decidePolicyForNavigationAction:request:frame:decisionListener:), | |||||
decidePolicyForNavigationAction, "v@:@@@@@"); | |||||
addMethod (@selector (webView:didFinishLoadForFrame:), didFinishLoadForFrame, "v@:@@"); | |||||
addMethod (@selector (webView:willCloseFrame:), willCloseFrame, "v@:@@"); | |||||
registerClass(); | |||||
} | |||||
static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); } | |||||
static WebBrowserComponent* getOwner (id self) { return getIvar<WebBrowserComponent*> (self, "owner"); } | |||||
private: | |||||
static void decidePolicyForNavigationAction (id self, SEL, WebView*, NSDictionary* actionInformation, | |||||
NSURLRequest*, WebFrame*, id <WebPolicyDecisionListener> listener) | |||||
{ | |||||
NSURL* url = [actionInformation valueForKey: nsStringLiteral ("WebActionOriginalURLKey")]; | |||||
if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([url absoluteString]))) | |||||
[listener use]; | |||||
else | |||||
[listener ignore]; | |||||
} | |||||
static void didFinishLoadForFrame (id self, SEL, WebView* sender, WebFrame* frame) | |||||
{ | |||||
if ([frame isEqual: [sender mainFrame]]) | |||||
{ | |||||
NSURL* url = [[[frame dataSource] request] URL]; | |||||
getOwner (self)->pageFinishedLoading (nsStringToJuce ([url absoluteString])); | |||||
} | |||||
} | |||||
static void willCloseFrame (id self, SEL, WebView*, WebFrame*) | |||||
{ | |||||
getOwner (self)->windowCloseRequest(); | |||||
} | |||||
}; | |||||
#else | |||||
} // (juce namespace) | |||||
//============================================================================== | |||||
@interface WebViewTapDetector : NSObject <UIGestureRecognizerDelegate> | |||||
{ | |||||
} | |||||
- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer | |||||
shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer; | |||||
@end | |||||
@implementation WebViewTapDetector | |||||
- (BOOL) gestureRecognizer: (UIGestureRecognizer*) gestureRecognizer | |||||
shouldRecognizeSimultaneouslyWithGestureRecognizer: (UIGestureRecognizer*) otherGestureRecognizer | |||||
{ | |||||
(void) gestureRecognizer; | |||||
(void) otherGestureRecognizer; | |||||
return YES; | |||||
} | |||||
@end | |||||
//============================================================================== | |||||
@interface WebViewURLChangeDetector : NSObject <UIWebViewDelegate> | |||||
{ | |||||
juce::WebBrowserComponent* ownerComponent; | |||||
} | |||||
- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent; | |||||
- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request navigationType: (UIWebViewNavigationType) navigationType; | |||||
@end | |||||
@implementation WebViewURLChangeDetector | |||||
- (WebViewURLChangeDetector*) initWithWebBrowserOwner: (juce::WebBrowserComponent*) ownerComponent_ | |||||
{ | |||||
[super init]; | |||||
ownerComponent = ownerComponent_; | |||||
return self; | |||||
} | |||||
- (BOOL) webView: (UIWebView*) webView shouldStartLoadWithRequest: (NSURLRequest*) request navigationType: (UIWebViewNavigationType) navigationType | |||||
{ | |||||
(void) webView; | |||||
(void) navigationType; | |||||
return ownerComponent->pageAboutToLoad (nsStringToJuce (request.URL.absoluteString)); | |||||
} | |||||
@end | |||||
namespace juce { | |||||
#endif | |||||
//============================================================================== | |||||
class WebBrowserComponent::Pimpl | |||||
#if JUCE_MAC | |||||
: public NSViewComponent | |||||
#else | |||||
: public UIViewComponent | |||||
#endif | |||||
{ | |||||
public: | |||||
Pimpl (WebBrowserComponent* owner) | |||||
{ | |||||
#if JUCE_MAC | |||||
webView = [[WebView alloc] initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) | |||||
frameName: nsEmptyString() | |||||
groupName: nsEmptyString()]; | |||||
setView (webView); | |||||
static DownloadClickDetectorClass cls; | |||||
clickListener = [cls.createInstance() init]; | |||||
DownloadClickDetectorClass::setOwner (clickListener, owner); | |||||
[webView setPolicyDelegate: clickListener]; | |||||
[webView setFrameLoadDelegate: clickListener]; | |||||
#else | |||||
webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)]; | |||||
setView (webView); | |||||
tapDetector = [[WebViewTapDetector alloc] init]; | |||||
urlDetector = [[WebViewURLChangeDetector alloc] initWithWebBrowserOwner: owner]; | |||||
gestureRecogniser = nil; | |||||
webView.delegate = urlDetector; | |||||
#endif | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
#if JUCE_MAC | |||||
[webView setPolicyDelegate: nil]; | |||||
[webView setFrameLoadDelegate: nil]; | |||||
[clickListener release]; | |||||
#else | |||||
webView.delegate = nil; | |||||
[webView removeGestureRecognizer: gestureRecogniser]; | |||||
[gestureRecogniser release]; | |||||
[tapDetector release]; | |||||
[urlDetector release]; | |||||
#endif | |||||
setView (nil); | |||||
} | |||||
void goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
NSMutableURLRequest* r | |||||
= [NSMutableURLRequest requestWithURL: [NSURL URLWithString: juceStringToNS (url)] | |||||
cachePolicy: NSURLRequestUseProtocolCachePolicy | |||||
timeoutInterval: 30.0]; | |||||
if (postData != nullptr && postData->getSize() > 0) | |||||
{ | |||||
[r setHTTPMethod: nsStringLiteral ("POST")]; | |||||
[r setHTTPBody: [NSData dataWithBytes: postData->getData() | |||||
length: postData->getSize()]]; | |||||
} | |||||
if (headers != nullptr) | |||||
{ | |||||
for (int i = 0; i < headers->size(); ++i) | |||||
{ | |||||
const String headerName ((*headers)[i].upToFirstOccurrenceOf (":", false, false).trim()); | |||||
const String headerValue ((*headers)[i].fromFirstOccurrenceOf (":", false, false).trim()); | |||||
[r setValue: juceStringToNS (headerValue) | |||||
forHTTPHeaderField: juceStringToNS (headerName)]; | |||||
} | |||||
} | |||||
stop(); | |||||
#if JUCE_MAC | |||||
[[webView mainFrame] loadRequest: r]; | |||||
#else | |||||
[webView loadRequest: r]; | |||||
#endif | |||||
} | |||||
void goBack() { [webView goBack]; } | |||||
void goForward() { [webView goForward]; } | |||||
#if JUCE_MAC | |||||
void stop() { [webView stopLoading: nil]; } | |||||
void refresh() { [webView reload: nil]; } | |||||
#else | |||||
void stop() { [webView stopLoading]; } | |||||
void refresh() { [webView reload]; } | |||||
#endif | |||||
void mouseMove (const MouseEvent&) | |||||
{ | |||||
// WebKit doesn't capture mouse-moves itself, so it seems the only way to make | |||||
// them work is to push them via this non-public method.. | |||||
if ([webView respondsToSelector: @selector (_updateMouseoverWithFakeEvent)]) | |||||
[webView performSelector: @selector (_updateMouseoverWithFakeEvent)]; | |||||
} | |||||
private: | |||||
#if JUCE_MAC | |||||
WebView* webView; | |||||
NSObject* clickListener; | |||||
#else | |||||
UIWebView* webView; | |||||
WebViewTapDetector* tapDetector; | |||||
WebViewURLChangeDetector* urlDetector; | |||||
UITapGestureRecognizer* gestureRecogniser; | |||||
#endif | |||||
}; | |||||
//============================================================================== | |||||
WebBrowserComponent::WebBrowserComponent (const bool unloadWhenHidden) | |||||
: browser (nullptr), | |||||
blankPageShown (false), | |||||
unloadPageWhenBrowserIsHidden (unloadWhenHidden) | |||||
{ | |||||
setOpaque (true); | |||||
addAndMakeVisible (browser = new Pimpl (this)); | |||||
} | |||||
WebBrowserComponent::~WebBrowserComponent() | |||||
{ | |||||
deleteAndZero (browser); | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
lastURL = url; | |||||
lastHeaders.clear(); | |||||
if (headers != nullptr) | |||||
lastHeaders = *headers; | |||||
lastPostData.setSize (0); | |||||
if (postData != nullptr) | |||||
lastPostData = *postData; | |||||
blankPageShown = false; | |||||
browser->goToURL (url, headers, postData); | |||||
} | |||||
void WebBrowserComponent::stop() | |||||
{ | |||||
browser->stop(); | |||||
} | |||||
void WebBrowserComponent::goBack() | |||||
{ | |||||
lastURL.clear(); | |||||
blankPageShown = false; | |||||
browser->goBack(); | |||||
} | |||||
void WebBrowserComponent::goForward() | |||||
{ | |||||
lastURL.clear(); | |||||
browser->goForward(); | |||||
} | |||||
void WebBrowserComponent::refresh() | |||||
{ | |||||
browser->refresh(); | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::paint (Graphics&) | |||||
{ | |||||
} | |||||
void WebBrowserComponent::checkWindowAssociation() | |||||
{ | |||||
if (isShowing()) | |||||
{ | |||||
reloadLastURL(); | |||||
if (blankPageShown) | |||||
goBack(); | |||||
} | |||||
else | |||||
{ | |||||
if (unloadPageWhenBrowserIsHidden && ! blankPageShown) | |||||
{ | |||||
// when the component becomes invisible, some stuff like flash | |||||
// carries on playing audio, so we need to force it onto a blank | |||||
// page to avoid this, (and send it back when it's made visible again). | |||||
blankPageShown = true; | |||||
browser->goToURL ("about:blank", 0, 0); | |||||
} | |||||
} | |||||
} | |||||
void WebBrowserComponent::reloadLastURL() | |||||
{ | |||||
if (lastURL.isNotEmpty()) | |||||
{ | |||||
goToURL (lastURL, &lastHeaders, &lastPostData); | |||||
lastURL.clear(); | |||||
} | |||||
} | |||||
void WebBrowserComponent::parentHierarchyChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} | |||||
void WebBrowserComponent::resized() | |||||
{ | |||||
browser->setSize (getWidth(), getHeight()); | |||||
} | |||||
void WebBrowserComponent::visibilityChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} |
@@ -0,0 +1,402 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
extern int64 getMouseEventTime(); | |||||
namespace ActiveXHelpers | |||||
{ | |||||
//============================================================================== | |||||
class JuceIStorage : public ComBaseClassHelper <IStorage> | |||||
{ | |||||
public: | |||||
JuceIStorage() {} | |||||
JUCE_COMRESULT CreateStream (const WCHAR*, DWORD, DWORD, DWORD, IStream**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT OpenStream (const WCHAR*, void*, DWORD, DWORD, IStream**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT CreateStorage (const WCHAR*, DWORD, DWORD, DWORD, IStorage**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT OpenStorage (const WCHAR*, IStorage*, DWORD, SNB, DWORD, IStorage**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT CopyTo (DWORD, IID const*, SNB, IStorage*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT MoveElementTo (const OLECHAR*,IStorage*, const OLECHAR*, DWORD) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT Commit (DWORD) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT Revert() { return E_NOTIMPL; } | |||||
JUCE_COMRESULT EnumElements (DWORD, void*, DWORD, IEnumSTATSTG**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT DestroyElement (const OLECHAR*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT RenameElement (const WCHAR*, const WCHAR*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetElementTimes (const WCHAR*, FILETIME const*, FILETIME const*, FILETIME const*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetClass (REFCLSID) { return S_OK; } | |||||
JUCE_COMRESULT SetStateBits (DWORD, DWORD) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT Stat (STATSTG*, DWORD) { return E_NOTIMPL; } | |||||
}; | |||||
//============================================================================== | |||||
class JuceOleInPlaceFrame : public ComBaseClassHelper <IOleInPlaceFrame> | |||||
{ | |||||
public: | |||||
JuceOleInPlaceFrame (HWND hwnd) : window (hwnd) {} | |||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; } | |||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT GetBorder (LPRECT) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT RequestBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetBorderSpace (LPCBORDERWIDTHS) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetActiveObject (IOleInPlaceActiveObject*, LPCOLESTR) { return S_OK; } | |||||
JUCE_COMRESULT InsertMenus (HMENU, LPOLEMENUGROUPWIDTHS) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetMenu (HMENU, HOLEMENU, HWND) { return S_OK; } | |||||
JUCE_COMRESULT RemoveMenus (HMENU) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT SetStatusText (LPCOLESTR) { return S_OK; } | |||||
JUCE_COMRESULT EnableModeless (BOOL) { return S_OK; } | |||||
JUCE_COMRESULT TranslateAccelerator (LPMSG, WORD) { return E_NOTIMPL; } | |||||
private: | |||||
HWND window; | |||||
}; | |||||
//============================================================================== | |||||
class JuceIOleInPlaceSite : public ComBaseClassHelper <IOleInPlaceSite> | |||||
{ | |||||
public: | |||||
JuceIOleInPlaceSite (HWND hwnd) | |||||
: window (hwnd), | |||||
frame (new JuceOleInPlaceFrame (window)) | |||||
{} | |||||
~JuceIOleInPlaceSite() | |||||
{ | |||||
frame->Release(); | |||||
} | |||||
JUCE_COMRESULT GetWindow (HWND* lphwnd) { *lphwnd = window; return S_OK; } | |||||
JUCE_COMRESULT ContextSensitiveHelp (BOOL) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT CanInPlaceActivate() { return S_OK; } | |||||
JUCE_COMRESULT OnInPlaceActivate() { return S_OK; } | |||||
JUCE_COMRESULT OnUIActivate() { return S_OK; } | |||||
JUCE_COMRESULT GetWindowContext (LPOLEINPLACEFRAME* lplpFrame, LPOLEINPLACEUIWINDOW* lplpDoc, LPRECT, LPRECT, LPOLEINPLACEFRAMEINFO lpFrameInfo) | |||||
{ | |||||
/* Note: if you call AddRef on the frame here, then some types of object (e.g. web browser control) cause leaks.. | |||||
If you don't call AddRef then others crash (e.g. QuickTime).. Bit of a catch-22, so letting it leak is probably preferable. | |||||
*/ | |||||
if (lplpFrame != nullptr) { frame->AddRef(); *lplpFrame = frame; } | |||||
if (lplpDoc != nullptr) *lplpDoc = nullptr; | |||||
lpFrameInfo->fMDIApp = FALSE; | |||||
lpFrameInfo->hwndFrame = window; | |||||
lpFrameInfo->haccel = 0; | |||||
lpFrameInfo->cAccelEntries = 0; | |||||
return S_OK; | |||||
} | |||||
JUCE_COMRESULT Scroll (SIZE) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT OnUIDeactivate (BOOL) { return S_OK; } | |||||
JUCE_COMRESULT OnInPlaceDeactivate() { return S_OK; } | |||||
JUCE_COMRESULT DiscardUndoState() { return E_NOTIMPL; } | |||||
JUCE_COMRESULT DeactivateAndUndo() { return E_NOTIMPL; } | |||||
JUCE_COMRESULT OnPosRectChange (LPCRECT) { return S_OK; } | |||||
private: | |||||
HWND window; | |||||
JuceOleInPlaceFrame* frame; | |||||
}; | |||||
//============================================================================== | |||||
class JuceIOleClientSite : public ComBaseClassHelper <IOleClientSite> | |||||
{ | |||||
public: | |||||
JuceIOleClientSite (HWND window) | |||||
: inplaceSite (new JuceIOleInPlaceSite (window)) | |||||
{} | |||||
~JuceIOleClientSite() | |||||
{ | |||||
inplaceSite->Release(); | |||||
} | |||||
JUCE_COMRESULT QueryInterface (REFIID type, void** result) | |||||
{ | |||||
if (type == IID_IOleInPlaceSite) | |||||
{ | |||||
inplaceSite->AddRef(); | |||||
*result = static_cast <IOleInPlaceSite*> (inplaceSite); | |||||
return S_OK; | |||||
} | |||||
return ComBaseClassHelper <IOleClientSite>::QueryInterface (type, result); | |||||
} | |||||
JUCE_COMRESULT SaveObject() { return E_NOTIMPL; } | |||||
JUCE_COMRESULT GetMoniker (DWORD, DWORD, IMoniker**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT GetContainer (LPOLECONTAINER* ppContainer) { *ppContainer = nullptr; return E_NOINTERFACE; } | |||||
JUCE_COMRESULT ShowObject() { return S_OK; } | |||||
JUCE_COMRESULT OnShowWindow (BOOL) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT RequestNewObjectLayout() { return E_NOTIMPL; } | |||||
private: | |||||
JuceIOleInPlaceSite* inplaceSite; | |||||
}; | |||||
//============================================================================== | |||||
static Array<ActiveXControlComponent*> activeXComps; | |||||
HWND getHWND (const ActiveXControlComponent* const component) | |||||
{ | |||||
HWND hwnd = 0; | |||||
const IID iid = IID_IOleWindow; | |||||
if (IOleWindow* const window = (IOleWindow*) component->queryInterface (&iid)) | |||||
{ | |||||
window->GetWindow (&hwnd); | |||||
window->Release(); | |||||
} | |||||
return hwnd; | |||||
} | |||||
void offerActiveXMouseEventToPeer (ComponentPeer* const peer, HWND hwnd, UINT message, LPARAM lParam) | |||||
{ | |||||
RECT activeXRect, peerRect; | |||||
GetWindowRect (hwnd, &activeXRect); | |||||
GetWindowRect ((HWND) peer->getNativeHandle(), &peerRect); | |||||
switch (message) | |||||
{ | |||||
case WM_MOUSEMOVE: | |||||
case WM_LBUTTONDOWN: | |||||
case WM_MBUTTONDOWN: | |||||
case WM_RBUTTONDOWN: | |||||
case WM_LBUTTONUP: | |||||
case WM_MBUTTONUP: | |||||
case WM_RBUTTONUP: | |||||
peer->handleMouseEvent (0, Point<int> (GET_X_LPARAM (lParam) + activeXRect.left - peerRect.left, | |||||
GET_Y_LPARAM (lParam) + activeXRect.top - peerRect.top), | |||||
ModifierKeys::getCurrentModifiersRealtime(), | |||||
getMouseEventTime()); | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
class ActiveXControlComponent::Pimpl : public ComponentMovementWatcher | |||||
{ | |||||
public: | |||||
Pimpl (HWND hwnd, ActiveXControlComponent& activeXComp) | |||||
: ComponentMovementWatcher (&activeXComp), | |||||
owner (activeXComp), | |||||
controlHWND (0), | |||||
storage (new ActiveXHelpers::JuceIStorage()), | |||||
clientSite (new ActiveXHelpers::JuceIOleClientSite (hwnd)), | |||||
control (nullptr), | |||||
originalWndProc (0) | |||||
{ | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
if (control != nullptr) | |||||
{ | |||||
control->Close (OLECLOSE_NOSAVE); | |||||
control->Release(); | |||||
} | |||||
clientSite->Release(); | |||||
storage->Release(); | |||||
} | |||||
void setControlBounds (const Rectangle<int>& bounds) const | |||||
{ | |||||
if (controlHWND != 0) | |||||
MoveWindow (controlHWND, bounds.getX(), bounds.getY(), bounds.getWidth(), bounds.getHeight(), TRUE); | |||||
} | |||||
void setControlVisible (bool shouldBeVisible) const | |||||
{ | |||||
if (controlHWND != 0) | |||||
ShowWindow (controlHWND, shouldBeVisible ? SW_SHOWNA : SW_HIDE); | |||||
} | |||||
//============================================================================== | |||||
void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override | |||||
{ | |||||
if (ComponentPeer* const peer = owner.getTopLevelComponent()->getPeer()) | |||||
setControlBounds (peer->getAreaCoveredBy (owner)); | |||||
} | |||||
void componentPeerChanged() override | |||||
{ | |||||
componentMovedOrResized (true, true); | |||||
} | |||||
void componentVisibilityChanged() override | |||||
{ | |||||
setControlVisible (owner.isShowing()); | |||||
componentPeerChanged(); | |||||
} | |||||
// intercepts events going to an activeX control, so we can sneakily use the mouse events | |||||
static LRESULT CALLBACK activeXHookWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |||||
{ | |||||
for (int i = ActiveXHelpers::activeXComps.size(); --i >= 0;) | |||||
{ | |||||
const ActiveXControlComponent* const ax = ActiveXHelpers::activeXComps.getUnchecked(i); | |||||
if (ax->control != nullptr && ax->control->controlHWND == hwnd) | |||||
{ | |||||
switch (message) | |||||
{ | |||||
case WM_MOUSEMOVE: | |||||
case WM_LBUTTONDOWN: | |||||
case WM_MBUTTONDOWN: | |||||
case WM_RBUTTONDOWN: | |||||
case WM_LBUTTONUP: | |||||
case WM_MBUTTONUP: | |||||
case WM_RBUTTONUP: | |||||
case WM_LBUTTONDBLCLK: | |||||
case WM_MBUTTONDBLCLK: | |||||
case WM_RBUTTONDBLCLK: | |||||
if (ax->isShowing()) | |||||
{ | |||||
if (ComponentPeer* const peer = ax->getPeer()) | |||||
{ | |||||
ActiveXHelpers::offerActiveXMouseEventToPeer (peer, hwnd, message, lParam); | |||||
if (! ax->areMouseEventsAllowed()) | |||||
return 0; | |||||
} | |||||
} | |||||
break; | |||||
default: | |||||
break; | |||||
} | |||||
return CallWindowProc (ax->control->originalWndProc, hwnd, message, wParam, lParam); | |||||
} | |||||
} | |||||
return DefWindowProc (hwnd, message, wParam, lParam); | |||||
} | |||||
ActiveXControlComponent& owner; | |||||
HWND controlHWND; | |||||
IStorage* storage; | |||||
IOleClientSite* clientSite; | |||||
IOleObject* control; | |||||
WNDPROC originalWndProc; | |||||
}; | |||||
//============================================================================== | |||||
ActiveXControlComponent::ActiveXControlComponent() | |||||
: mouseEventsAllowed (true) | |||||
{ | |||||
ActiveXHelpers::activeXComps.add (this); | |||||
} | |||||
ActiveXControlComponent::~ActiveXControlComponent() | |||||
{ | |||||
deleteControl(); | |||||
ActiveXHelpers::activeXComps.removeFirstMatchingValue (this); | |||||
} | |||||
void ActiveXControlComponent::paint (Graphics& g) | |||||
{ | |||||
if (control == nullptr) | |||||
g.fillAll (Colours::lightgrey); | |||||
} | |||||
bool ActiveXControlComponent::createControl (const void* controlIID) | |||||
{ | |||||
deleteControl(); | |||||
if (ComponentPeer* const peer = getPeer()) | |||||
{ | |||||
const Rectangle<int> bounds (peer->getAreaCoveredBy (*this)); | |||||
HWND hwnd = (HWND) peer->getNativeHandle(); | |||||
ScopedPointer<Pimpl> newControl (new Pimpl (hwnd, *this)); | |||||
HRESULT hr; | |||||
if ((hr = OleCreate (*(const IID*) controlIID, IID_IOleObject, 1 /*OLERENDER_DRAW*/, 0, | |||||
newControl->clientSite, newControl->storage, | |||||
(void**) &(newControl->control))) == S_OK) | |||||
{ | |||||
newControl->control->SetHostNames (L"JUCE", 0); | |||||
if (OleSetContainedObject (newControl->control, TRUE) == S_OK) | |||||
{ | |||||
RECT rect; | |||||
rect.left = bounds.getX(); | |||||
rect.top = bounds.getY(); | |||||
rect.right = bounds.getRight(); | |||||
rect.bottom = bounds.getBottom(); | |||||
if (newControl->control->DoVerb (OLEIVERB_SHOW, 0, newControl->clientSite, 0, hwnd, &rect) == S_OK) | |||||
{ | |||||
control = newControl; | |||||
control->controlHWND = ActiveXHelpers::getHWND (this); | |||||
if (control->controlHWND != 0) | |||||
{ | |||||
control->setControlBounds (bounds); | |||||
control->originalWndProc = (WNDPROC) GetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC); | |||||
SetWindowLongPtr ((HWND) control->controlHWND, GWLP_WNDPROC, (LONG_PTR) Pimpl::activeXHookWndProc); | |||||
} | |||||
return true; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
// the component must have already been added to a real window when you call this! | |||||
jassertfalse; | |||||
} | |||||
return false; | |||||
} | |||||
void ActiveXControlComponent::deleteControl() | |||||
{ | |||||
control = nullptr; | |||||
} | |||||
void* ActiveXControlComponent::queryInterface (const void* iid) const | |||||
{ | |||||
void* result = nullptr; | |||||
if (control != nullptr && control->control != nullptr | |||||
&& SUCCEEDED (control->control->QueryInterface (*(const IID*) iid, &result))) | |||||
return result; | |||||
return nullptr; | |||||
} | |||||
void ActiveXControlComponent::setMouseEventsAllowed (const bool eventsCanReachControl) | |||||
{ | |||||
mouseEventsAllowed = eventsCanReachControl; | |||||
} |
@@ -0,0 +1,235 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
extern void* getUser32Function (const char*); | |||||
namespace IconConverters | |||||
{ | |||||
extern HICON createHICONFromImage (const Image&, BOOL isIcon, int hotspotX, int hotspotY); | |||||
} | |||||
//============================================================================== | |||||
class SystemTrayIconComponent::Pimpl | |||||
{ | |||||
public: | |||||
Pimpl (SystemTrayIconComponent& owner_, HICON hicon, HWND hwnd) | |||||
: owner (owner_), | |||||
originalWndProc ((WNDPROC) GetWindowLongPtr (hwnd, GWLP_WNDPROC)), | |||||
taskbarCreatedMessage (RegisterWindowMessage (TEXT ("TaskbarCreated"))) | |||||
{ | |||||
SetWindowLongPtr (hwnd, GWLP_WNDPROC, (LONG_PTR) hookedWndProc); | |||||
zerostruct (iconData); | |||||
iconData.cbSize = sizeof (iconData); | |||||
iconData.hWnd = hwnd; | |||||
iconData.uID = (UINT) (pointer_sized_int) hwnd; | |||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; | |||||
iconData.uCallbackMessage = WM_TRAYNOTIFY; | |||||
iconData.hIcon = hicon; | |||||
notify (NIM_ADD); | |||||
// In order to receive the "TaskbarCreated" message, we need to request that it's not filtered out. | |||||
// (Need to load dynamically, as ChangeWindowMessageFilter is only available in Vista and later) | |||||
typedef BOOL (WINAPI* ChangeWindowMessageFilterType) (UINT, DWORD); | |||||
if (ChangeWindowMessageFilterType changeWindowMessageFilter | |||||
= (ChangeWindowMessageFilterType) getUser32Function ("ChangeWindowMessageFilter")) | |||||
changeWindowMessageFilter (taskbarCreatedMessage, 1 /* MSGFLT_ADD */); | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
SetWindowLongPtr (iconData.hWnd, GWLP_WNDPROC, (LONG_PTR) originalWndProc); | |||||
iconData.uFlags = 0; | |||||
notify (NIM_DELETE); | |||||
DestroyIcon (iconData.hIcon); | |||||
} | |||||
void updateIcon (HICON hicon) | |||||
{ | |||||
HICON oldIcon = iconData.hIcon; | |||||
iconData.hIcon = hicon; | |||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; | |||||
notify (NIM_MODIFY); | |||||
DestroyIcon (oldIcon); | |||||
} | |||||
void setToolTip (const String& toolTip) | |||||
{ | |||||
iconData.uFlags = NIF_TIP; | |||||
toolTip.copyToUTF16 (iconData.szTip, sizeof (iconData.szTip) - 1); | |||||
notify (NIM_MODIFY); | |||||
} | |||||
void handleTaskBarEvent (const LPARAM lParam) | |||||
{ | |||||
if (owner.isCurrentlyBlockedByAnotherModalComponent()) | |||||
{ | |||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN | |||||
|| lParam == WM_LBUTTONDBLCLK || lParam == WM_LBUTTONDBLCLK) | |||||
{ | |||||
if (Component* const current = Component::getCurrentlyModalComponent()) | |||||
current->inputAttemptWhenModal(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
ModifierKeys eventMods (ModifierKeys::getCurrentModifiersRealtime()); | |||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_LBUTTONDBLCLK) | |||||
eventMods = eventMods.withFlags (ModifierKeys::leftButtonModifier); | |||||
else if (lParam == WM_RBUTTONDOWN || lParam == WM_RBUTTONDBLCLK) | |||||
eventMods = eventMods.withFlags (ModifierKeys::rightButtonModifier); | |||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP) | |||||
eventMods = eventMods.withoutMouseButtons(); | |||||
const Time eventTime (getMouseEventTime()); | |||||
const MouseEvent e (Desktop::getInstance().getMainMouseSource(), | |||||
Point<int>(), eventMods, &owner, &owner, eventTime, | |||||
Point<int>(), eventTime, 1, false); | |||||
if (lParam == WM_LBUTTONDOWN || lParam == WM_RBUTTONDOWN) | |||||
{ | |||||
SetFocus (iconData.hWnd); | |||||
SetForegroundWindow (iconData.hWnd); | |||||
owner.mouseDown (e); | |||||
} | |||||
else if (lParam == WM_LBUTTONUP || lParam == WM_RBUTTONUP) | |||||
{ | |||||
owner.mouseUp (e); | |||||
} | |||||
else if (lParam == WM_LBUTTONDBLCLK || lParam == WM_RBUTTONDBLCLK) | |||||
{ | |||||
owner.mouseDoubleClick (e); | |||||
} | |||||
else if (lParam == WM_MOUSEMOVE) | |||||
{ | |||||
owner.mouseMove (e); | |||||
} | |||||
} | |||||
} | |||||
static Pimpl* getPimpl (HWND hwnd) | |||||
{ | |||||
if (JuceWindowIdentifier::isJUCEWindow (hwnd)) | |||||
if (ComponentPeer* peer = (ComponentPeer*) GetWindowLongPtr (hwnd, 8)) | |||||
if (SystemTrayIconComponent* const iconComp = dynamic_cast<SystemTrayIconComponent*> (&(peer->getComponent()))) | |||||
return iconComp->pimpl; | |||||
return nullptr; | |||||
} | |||||
static LRESULT CALLBACK hookedWndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |||||
{ | |||||
if (Pimpl* const p = getPimpl (hwnd)) | |||||
return p->windowProc (hwnd, message, wParam, lParam); | |||||
return DefWindowProcW (hwnd, message, wParam, lParam); | |||||
} | |||||
LRESULT windowProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) | |||||
{ | |||||
if (message == WM_TRAYNOTIFY) | |||||
{ | |||||
handleTaskBarEvent (lParam); | |||||
} | |||||
else if (message == taskbarCreatedMessage) | |||||
{ | |||||
iconData.uFlags = NIF_ICON | NIF_MESSAGE | NIF_TIP; | |||||
notify (NIM_ADD); | |||||
} | |||||
return CallWindowProc (originalWndProc, hwnd, message, wParam, lParam); | |||||
} | |||||
void showBubble (const String& title, const String& content) | |||||
{ | |||||
iconData.uFlags = 0x10 /*NIF_INFO*/; | |||||
title.copyToUTF16 (iconData.szInfoTitle, sizeof (iconData.szInfoTitle) - 1); | |||||
content.copyToUTF16 (iconData.szInfo, sizeof (iconData.szInfo) - 1); | |||||
notify (NIM_MODIFY); | |||||
} | |||||
SystemTrayIconComponent& owner; | |||||
NOTIFYICONDATA iconData; | |||||
private: | |||||
WNDPROC originalWndProc; | |||||
const DWORD taskbarCreatedMessage; | |||||
enum { WM_TRAYNOTIFY = WM_USER + 100 }; | |||||
void notify (DWORD message) noexcept { Shell_NotifyIcon (message, &iconData); } | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
}; | |||||
//============================================================================== | |||||
void SystemTrayIconComponent::setIconImage (const Image& newImage) | |||||
{ | |||||
if (newImage.isValid()) | |||||
{ | |||||
HICON hicon = IconConverters::createHICONFromImage (newImage, TRUE, 0, 0); | |||||
if (pimpl == nullptr) | |||||
pimpl = new Pimpl (*this, hicon, (HWND) getWindowHandle()); | |||||
else | |||||
pimpl->updateIcon (hicon); | |||||
} | |||||
else | |||||
{ | |||||
pimpl = nullptr; | |||||
} | |||||
} | |||||
void SystemTrayIconComponent::setIconTooltip (const String& tooltip) | |||||
{ | |||||
if (pimpl != nullptr) | |||||
pimpl->setToolTip (tooltip); | |||||
} | |||||
void SystemTrayIconComponent::setHighlighted (bool) | |||||
{ | |||||
// N/A on Windows. | |||||
} | |||||
void SystemTrayIconComponent::showInfoBubble (const String& title, const String& content) | |||||
{ | |||||
if (pimpl != nullptr) | |||||
pimpl->showBubble (title, content); | |||||
} | |||||
void SystemTrayIconComponent::hideInfoBubble() | |||||
{ | |||||
showInfoBubble (String::empty, String::empty); | |||||
} | |||||
void* SystemTrayIconComponent::getNativeHandle() const | |||||
{ | |||||
return pimpl != nullptr ? &(pimpl->iconData) : nullptr; | |||||
} |
@@ -0,0 +1,317 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2013 - Raw Material Software Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class WebBrowserComponent::Pimpl : public ActiveXControlComponent | |||||
{ | |||||
public: | |||||
Pimpl() | |||||
: browser (nullptr), | |||||
connectionPoint (nullptr), | |||||
adviseCookie (0) | |||||
{ | |||||
} | |||||
~Pimpl() | |||||
{ | |||||
if (connectionPoint != nullptr) | |||||
connectionPoint->Unadvise (adviseCookie); | |||||
if (browser != nullptr) | |||||
browser->Release(); | |||||
} | |||||
void createBrowser() | |||||
{ | |||||
createControl (&CLSID_WebBrowser); | |||||
browser = (IWebBrowser2*) queryInterface (&IID_IWebBrowser2); | |||||
if (IConnectionPointContainer* connectionPointContainer | |||||
= (IConnectionPointContainer*) queryInterface (&IID_IConnectionPointContainer)) | |||||
{ | |||||
connectionPointContainer->FindConnectionPoint (DIID_DWebBrowserEvents2, &connectionPoint); | |||||
if (connectionPoint != nullptr) | |||||
{ | |||||
WebBrowserComponent* const owner = dynamic_cast <WebBrowserComponent*> (getParentComponent()); | |||||
jassert (owner != nullptr); | |||||
EventHandler* handler = new EventHandler (*owner); | |||||
connectionPoint->Advise (handler, &adviseCookie); | |||||
handler->Release(); | |||||
} | |||||
} | |||||
} | |||||
void goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
if (browser != nullptr) | |||||
{ | |||||
LPSAFEARRAY sa = nullptr; | |||||
VARIANT flags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers) | |||||
VariantInit (&flags); | |||||
VariantInit (&frame); | |||||
VariantInit (&postDataVar); | |||||
VariantInit (&headersVar); | |||||
if (headers != nullptr) | |||||
{ | |||||
V_VT (&headersVar) = VT_BSTR; | |||||
V_BSTR (&headersVar) = SysAllocString ((const OLECHAR*) headers->joinIntoString ("\r\n").toWideCharPointer()); | |||||
} | |||||
if (postData != nullptr && postData->getSize() > 0) | |||||
{ | |||||
sa = SafeArrayCreateVector (VT_UI1, 0, (ULONG) postData->getSize()); | |||||
if (sa != nullptr) | |||||
{ | |||||
void* data = nullptr; | |||||
SafeArrayAccessData (sa, &data); | |||||
jassert (data != nullptr); | |||||
if (data != nullptr) | |||||
{ | |||||
postData->copyTo (data, 0, postData->getSize()); | |||||
SafeArrayUnaccessData (sa); | |||||
VARIANT postDataVar2; | |||||
VariantInit (&postDataVar2); | |||||
V_VT (&postDataVar2) = VT_ARRAY | VT_UI1; | |||||
V_ARRAY (&postDataVar2) = sa; | |||||
postDataVar = postDataVar2; | |||||
} | |||||
} | |||||
} | |||||
browser->Navigate ((BSTR) (const OLECHAR*) url.toWideCharPointer(), | |||||
&flags, &frame, &postDataVar, &headersVar); | |||||
if (sa != nullptr) | |||||
SafeArrayDestroy (sa); | |||||
VariantClear (&flags); | |||||
VariantClear (&frame); | |||||
VariantClear (&postDataVar); | |||||
VariantClear (&headersVar); | |||||
} | |||||
} | |||||
//============================================================================== | |||||
IWebBrowser2* browser; | |||||
private: | |||||
IConnectionPoint* connectionPoint; | |||||
DWORD adviseCookie; | |||||
//============================================================================== | |||||
class EventHandler : public ComBaseClassHelper <IDispatch>, | |||||
public ComponentMovementWatcher | |||||
{ | |||||
public: | |||||
EventHandler (WebBrowserComponent& owner_) | |||||
: ComponentMovementWatcher (&owner_), | |||||
owner (owner_) | |||||
{ | |||||
} | |||||
JUCE_COMRESULT GetTypeInfoCount (UINT*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT GetTypeInfo (UINT, LCID, ITypeInfo**) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT GetIDsOfNames (REFIID, LPOLESTR*, UINT, LCID, DISPID*) { return E_NOTIMPL; } | |||||
JUCE_COMRESULT Invoke (DISPID dispIdMember, REFIID /*riid*/, LCID /*lcid*/, WORD /*wFlags*/, DISPPARAMS* pDispParams, | |||||
VARIANT* /*pVarResult*/, EXCEPINFO* /*pExcepInfo*/, UINT* /*puArgErr*/) | |||||
{ | |||||
if (dispIdMember == DISPID_BEFORENAVIGATE2) | |||||
{ | |||||
*pDispParams->rgvarg->pboolVal | |||||
= owner.pageAboutToLoad (getStringFromVariant (pDispParams->rgvarg[5].pvarVal)) ? VARIANT_FALSE | |||||
: VARIANT_TRUE; | |||||
return S_OK; | |||||
} | |||||
else if (dispIdMember == DISPID_DOCUMENTCOMPLETE) | |||||
{ | |||||
owner.pageFinishedLoading (getStringFromVariant (pDispParams->rgvarg[0].pvarVal)); | |||||
return S_OK; | |||||
} | |||||
else if (dispIdMember == 263 /*DISPID_WINDOWCLOSING*/) | |||||
{ | |||||
owner.windowCloseRequest(); | |||||
// setting this bool tells the browser to ignore the event - we'll handle it. | |||||
if (pDispParams->cArgs > 0 && pDispParams->rgvarg[0].vt == (VT_BYREF | VT_BOOL)) | |||||
*pDispParams->rgvarg[0].pboolVal = VARIANT_TRUE; | |||||
return S_OK; | |||||
} | |||||
return E_NOTIMPL; | |||||
} | |||||
void componentMovedOrResized (bool, bool) override {} | |||||
void componentPeerChanged() override {} | |||||
void componentVisibilityChanged() override { owner.visibilityChanged(); } | |||||
private: | |||||
WebBrowserComponent& owner; | |||||
static String getStringFromVariant (VARIANT* v) | |||||
{ | |||||
return (v->vt & VT_BYREF) != 0 ? *v->pbstrVal | |||||
: v->bstrVal; | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (EventHandler) | |||||
}; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Pimpl) | |||||
}; | |||||
//============================================================================== | |||||
WebBrowserComponent::WebBrowserComponent (const bool unloadPageWhenBrowserIsHidden_) | |||||
: browser (nullptr), | |||||
blankPageShown (false), | |||||
unloadPageWhenBrowserIsHidden (unloadPageWhenBrowserIsHidden_) | |||||
{ | |||||
setOpaque (true); | |||||
addAndMakeVisible (browser = new Pimpl()); | |||||
} | |||||
WebBrowserComponent::~WebBrowserComponent() | |||||
{ | |||||
delete browser; | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::goToURL (const String& url, | |||||
const StringArray* headers, | |||||
const MemoryBlock* postData) | |||||
{ | |||||
lastURL = url; | |||||
lastHeaders.clear(); | |||||
if (headers != nullptr) | |||||
lastHeaders = *headers; | |||||
lastPostData.setSize (0); | |||||
if (postData != nullptr) | |||||
lastPostData = *postData; | |||||
blankPageShown = false; | |||||
browser->goToURL (url, headers, postData); | |||||
} | |||||
void WebBrowserComponent::stop() | |||||
{ | |||||
if (browser->browser != nullptr) | |||||
browser->browser->Stop(); | |||||
} | |||||
void WebBrowserComponent::goBack() | |||||
{ | |||||
lastURL.clear(); | |||||
blankPageShown = false; | |||||
if (browser->browser != nullptr) | |||||
browser->browser->GoBack(); | |||||
} | |||||
void WebBrowserComponent::goForward() | |||||
{ | |||||
lastURL.clear(); | |||||
if (browser->browser != nullptr) | |||||
browser->browser->GoForward(); | |||||
} | |||||
void WebBrowserComponent::refresh() | |||||
{ | |||||
if (browser->browser != nullptr) | |||||
browser->browser->Refresh(); | |||||
} | |||||
//============================================================================== | |||||
void WebBrowserComponent::paint (Graphics& g) | |||||
{ | |||||
if (browser->browser == nullptr) | |||||
g.fillAll (Colours::white); | |||||
} | |||||
void WebBrowserComponent::checkWindowAssociation() | |||||
{ | |||||
if (isShowing()) | |||||
{ | |||||
if (browser->browser == nullptr && getPeer() != nullptr) | |||||
{ | |||||
browser->createBrowser(); | |||||
reloadLastURL(); | |||||
} | |||||
else | |||||
{ | |||||
if (blankPageShown) | |||||
goBack(); | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
if (browser != nullptr && unloadPageWhenBrowserIsHidden && ! blankPageShown) | |||||
{ | |||||
// when the component becomes invisible, some stuff like flash | |||||
// carries on playing audio, so we need to force it onto a blank | |||||
// page to avoid this.. | |||||
blankPageShown = true; | |||||
browser->goToURL ("about:blank", 0, 0); | |||||
} | |||||
} | |||||
} | |||||
void WebBrowserComponent::reloadLastURL() | |||||
{ | |||||
if (lastURL.isNotEmpty()) | |||||
{ | |||||
goToURL (lastURL, &lastHeaders, &lastPostData); | |||||
lastURL.clear(); | |||||
} | |||||
} | |||||
void WebBrowserComponent::parentHierarchyChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} | |||||
void WebBrowserComponent::resized() | |||||
{ | |||||
browser->setSize (getWidth(), getHeight()); | |||||
} | |||||
void WebBrowserComponent::visibilityChanged() | |||||
{ | |||||
checkWindowAssociation(); | |||||
} |