Page MenuHomePhabricator (Chris)

No OneTemporary

Authored By
Unknown
Size
195 KB
Referenced Files
None
Subscribers
None
diff --git a/src/FakeLuaLexer.cpp b/src/FakeLuaLexer.cpp
index 9d81781..cf91360 100644
--- a/src/FakeLuaLexer.cpp
+++ b/src/FakeLuaLexer.cpp
@@ -1,524 +1,508 @@
/*
* Copyright (C) 2019 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "FakeLuaLexer.h"
+#include "UTF8Functions.h"
#include <string.h>
#include <stdio.h>
FakeLuaLexer::FakeLuaLexer()
: buf(NULL)
, posStart(ITreeStorageBuilder::FilePosition{ 1, 1 })
, pos(ITreeStorageBuilder::FilePosition{ 1, 1 })
, tokenType(EndOfFile)
, storedByPOASerializer(false)
{
}
bool FakeLuaLexer::getNextToken() {
char c;
// skip whitespaces
for (;;) {
c = *buf;
if (c == ' ' || c == '\r' || c == '\n' || c == '\t') {
buf++; advanceByCharacter(c);
} else break;
}
if (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
// it's identifier
posStart = pos;
for (int i = 1;; i++) {
c = buf[i];
if (c == '_' || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
// do nothing
} else {
tokenType = Identifier;
token.assign(buf, buf + i);
buf += i; pos.column += i;
return true;
}
}
} else if (c >= '0' && c <= '9') {
// it's a number
return parseNumber();
}
int length = 0;
switch (c) {
case '\0':
// EOF
tokenType = EndOfFile;
token.clear();
return false;
break;
case '.':
if (buf[1] >= '0' && buf[1] <= '9') {
// it's a number
return parseNumber();
} else {
length = (buf[1] == '.') ? (buf[2] == '.' ? 3 : 2) : 1;
}
break;
case '\'': case '\"':
// short string
buf++; advanceByCharacter(c);
return parseShortString(c);
break;
case '-':
// check if it's the beginning of a comment
if (buf[1] == '-') {
buf += 2; pos.column += 2;
return parseComment();
} else {
length = 1;
}
break;
case '+': case '*': case '%': case '^': case '#': case '&': case '|':
case '(': case ')': case '{': case '}': case ']': case ';': case ',':
length = 1;
break;
case '/': case ':':
length = (buf[1] == c) ? 2 : 1;
break;
case '<': case '>':
length = (buf[1] == c || buf[1] == '=') ? 2 : 1;
break;
case '~': case '=':
length = (buf[1] == '=') ? 2 : 1;
break;
case '[':
// check if it's the beginning of a long string
length = checkOpeningLongBracket();
if (length >= 0) {
buf += length + 2; pos.column += length + 2;
return parseLongString(length);
} else {
length = 1;
}
break;
default:
// invalid character
break;
}
if (length > 0) {
tokenType = Operator;
token.assign(buf, buf + length);
posStart = pos;
buf += length; pos.column += length;
return true;
} else {
tokenType = EndOfFile;
token.clear();
error = "Invalid character: '";
error.push_back(c);
error.push_back('\'');
return false;
}
}
// Parse number which is of form
// ([0-9]+([.][0-9]*)?|[.][0-9]+)([Ee][+-]?[0-9]+)?
// or
// 0[Xx]([0-9A-Fa-f]+([.][0-9A-Fa-f]*)?|[.][0-9A-Fa-f]+)([Pp][+-]?[0-9]+)?
// returns true if it succeded,
bool FakeLuaLexer::parseNumber() {
char c;
int i = 0;
bool isHex = false;
c = buf[0];
if (c == '0' && (buf[1] == 'X' || buf[1] == 'x')) {
isHex = true;
i = 2;
}
int len1 = 0, len2 = 0;
// read the numbers before '.'
for (;;) {
c = buf[i];
if ((c >= '0' && c <= '9') ||
(isHex && ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
)
{
i++;
len1++;
} else {
break;
}
}
if (c == '.') {
// read the numbers after '.'
i++;
for (;;) {
c = buf[i];
if ((c >= '0' && c <= '9') ||
(isHex && ((c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')))
)
{
i++;
len2++;
} else {
break;
}
}
}
if (len1 == 0 && len2 == 0) {
// invalid
tokenType = EndOfFile;
token.clear();
error = "At least one digits are expected";
buf += i; pos.column += i;
return false;
}
if (isHex ? (c == 'P' || c == 'p') : (c == 'E' || c == 'e')) {
// read the exponents
i++;
c = buf[i];
if (c == '+' || c == '-') {
i++;
c = buf[i];
}
int len3 = 0;
for (;;) {
c = buf[i];
if (c >= '0' && c <= '9') {
i++;
len3++;
} else {
break;
}
}
if (len3 == 0) {
// invalid
tokenType = EndOfFile;
token.clear();
error = "At least one digits are expected";
buf += i; pos.column += i;
return false;
}
}
// done
tokenType = NumberLiteral;
token.assign(buf, buf + i);
posStart = pos;
buf += i; pos.column += i;
return true;
}
-#define U8_ENCODE(CH,OPERATION) \
- if(CH<0x80){ \
- OPERATION(CH); \
- }else if(CH<0x800){ \
- OPERATION(0xC0 | (CH>>6)); \
- OPERATION(0x80 | (CH & 0x3F)); \
- }else if(CH<0x10000){ \
- OPERATION(0xE0 | (CH>>12)); \
- OPERATION(0x80 | ((CH>>6) & 0x3F)); \
- OPERATION(0x80 | (CH & 0x3F)); \
- }else{ \
- OPERATION(0xF0 | (CH>>18)); \
- OPERATION(0x80 | ((CH>>12) & 0x3F)); \
- OPERATION(0x80 | ((CH>>6) & 0x3F)); \
- OPERATION(0x80 | (CH & 0x3F)); \
- }
-
// parse a short string which should ends with delim.
// before calling this function the buf should point to the first character of this string.
bool FakeLuaLexer::parseShortString(char delim) {
tokenType = StringLiteral;
token.clear();
posStart = pos;
char c;
for (;;) {
c = *buf;
if (c == delim) {
// over
buf++; advanceByCharacter(c);
return true;
} else if (c == '\\') {
buf++; pos.column++;
c = *buf;
switch (c) {
case 'a': buf++; pos.column++; token.push_back('\a'); break;
case 'b': buf++; pos.column++; token.push_back('\b'); break;
case 'f': buf++; pos.column++; token.push_back('\f'); break;
case 'n': buf++; pos.column++; token.push_back('\n'); break;
case 'r': buf++; pos.column++; token.push_back('\r'); break;
case 't': buf++; pos.column++; token.push_back('\t'); break;
case 'v': buf++; pos.column++; token.push_back('\v'); break;
case '\\': buf++; pos.column++; token.push_back('\\'); break;
case '\"': buf++; advanceByCharacter(c); token.push_back('\"'); break;
case '\'': buf++; pos.column++; token.push_back('\''); break;
case '\r': case '\n':
buf++; advanceByCharacter(c);
if ((buf[0] == '\r' || buf[0] == '\n') && buf[0] != c) {
c = buf[0];
buf++; advanceByCharacter(c);
}
token.push_back('\n');
break;
case 'z':
buf++; pos.column++;
for (;;) {
c = *buf;
if (c == '\r' || c == '\n' || c == ' ' || c == '\t') {
buf++; advanceByCharacter(c);
} else {
break;
}
}
break;
case 'u':
{
buf++; pos.column++;
if (buf[0] != '{') {
tokenType = EndOfFile;
token.clear();
error = "'{' expected";
return false;
}
buf++; pos.column++;
int ch = 0;
for (;;) {
int tmp = checkDigit(true);
if (tmp < 0) {
if (buf[0] == '}') {
buf++; pos.column++;
break;
}
tokenType = EndOfFile;
token.clear();
error = "Hexadecimal digit or '}' expected";
return false;
}
tmp |= (ch << 4);
if (tmp >= 0x110000) {
tokenType = EndOfFile;
token.clear();
error = "Out of Unicode range";
return false;
}
buf++; pos.column++;
ch = tmp;
}
U8_ENCODE(ch, token.push_back);
break;
}
case 'x':
{
buf++; pos.column++;
int ch = 0;
for (int i = 0; i < 2; i++) {
int tmp = checkDigit(true);
if (tmp < 0) {
tokenType = EndOfFile;
token.clear();
error = "Hexadecimal digit expected";
return false;
}
buf++; pos.column++;
ch = (ch << 4) | tmp;
}
token.push_back(ch);
break;
}
default:
if (c >= '0' && c <= '9') {
int ch = 0;
for (int i = 0; i < 3; i++) {
int tmp = checkDigit(false);
if (tmp < 0) break;
tmp += ch * 10;
if (tmp > 255) break;
buf++; pos.column++;
ch = tmp;
}
token.push_back(ch);
} else {
// invalid character
tokenType = EndOfFile;
token.clear();
error = "Invalid character: '";
error.push_back(c);
error.push_back('\'');
return false;
}
break;
}
} else if (c == '\r' || c == '\n' || c == '\0') {
tokenType = EndOfFile;
token.clear();
error = "Unexpected end of string literal";
return false;
} else {
buf++; advanceByCharacter(c);
token.push_back(c);
}
}
}
// parse a long string which should ends with closing long bracket of given level.
// before calling this function the buf should point to the first character of this string.
bool FakeLuaLexer::parseLongString(int level) {
tokenType = StringLiteral;
token.clear();
char c;
// skip initial newline
c = *buf;
if (c == '\r' || c == '\n') {
buf++; advanceByCharacter(c);
if ((buf[0] == '\r' || buf[0] == '\n') && buf[0] != c) {
c = buf[0];
buf++; advanceByCharacter(c);
}
}
posStart = pos;
for (;;) {
c = *buf;
if (c == ']' && checkClosingLongBracket() == level) {
// over
buf += level + 2; pos.column += level + 2;
return true;
} else if (c == '\r' || c == '\n') {
buf++; advanceByCharacter(c);
if ((buf[0] == '\r' || buf[0] == '\n') && buf[0] != c) {
c = buf[0];
buf++; advanceByCharacter(c);
}
token.push_back('\n');
} else if (c == '\0') {
tokenType = EndOfFile;
token.clear();
error = "Unexpected end of string literal";
return false;
} else {
buf++; advanceByCharacter(c);
token.push_back(c);
}
}
}
// parse a comment.
// before calling this function the buf should point to the first character of this comment.
bool FakeLuaLexer::parseComment() {
int level = checkOpeningLongBracket();
if (level >= 0) {
buf += level + 2; pos.column += level + 2;
if (parseLongString(level)) {
tokenType = Comment;
return true;
} else {
return false;
}
}
tokenType = Comment;
token.clear();
posStart = pos;
char c;
for (;;) {
c = *buf;
if (c == '\r' || c == '\n' || c == '\0') {
// over
return true;
} else {
buf++; advanceByCharacter(c);
token.push_back(c);
}
}
}
// check if the beginning of buf+offset is an opening long bracket.
// if yes it returns the level, otherwise it returns -1.
int FakeLuaLexer::checkOpeningLongBracket(int offset) {
char c;
c = buf[offset];
if (c == '[') {
for (int i = 1;; i++) {
c = buf[offset + i];
if (c == '[') return i - 1;
else if (c != '=') return -1;
}
}
return -1;
}
// check if the beginning of buf+offset is a closing long bracket.
// if yes it returns the level, otherwise it returns -1.
int FakeLuaLexer::checkClosingLongBracket(int offset) {
char c;
c = buf[offset];
if (c == ']') {
for (int i = 1;; i++) {
c = buf[offset + i];
if (c == ']') return i - 1;
else if (c != '=') return -1;
}
}
return -1;
}
// check if buf+offset is a digit.
// if yes it returns the number representation of the digit, otherwise it returns -1.
int FakeLuaLexer::checkDigit(bool isHex, int offset) {
char c = buf[offset];
if (c >= '0' && c <= '9') {
return c - '0';
} else {
if (isHex) {
if (c >= 'A' && c <= 'F') {
return c - ('A' - 10);
} else if (c >= 'a' && c <= 'f') {
return c - ('a' - 10);
}
}
return -1;
}
}
void FakeLuaLexer::advanceByCharacter(char c) {
// we need to advance by 2 since the \" stored by POASerializer is escaped
if (c == '\"') pos.column += storedByPOASerializer ? 2 : 1;
else pos.advanceByCharacter((int)(unsigned char)c);
}
diff --git a/src/Functions.cpp b/src/Functions.cpp
index bd600c2..6c7e734 100644
--- a/src/Functions.cpp
+++ b/src/Functions.cpp
@@ -1,1740 +1,1678 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <math.h>
#include <string.h>
#include <algorithm>
#include <SDL.h>
#include <SDL_mixer.h>
#include <SDL_syswm.h>
#include <SDL_ttf_fontfallback.h>
#include <string>
#include "Globals.h"
#include "Functions.h"
#include "Settings.h"
#include "FontManager.h"
#include "FileManager.h"
#include "GameObjects.h"
#include "LevelPack.h"
#include "TitleMenu.h"
#include "OptionsMenu.h"
#include "CreditsMenu.h"
#include "LevelEditSelect.h"
#include "LevelEditor.h"
#include "Game.h"
#include "LevelPlaySelect.h"
#include "Addons.h"
#include "InputManager.h"
#include "ImageManager.h"
#include "MusicManager.h"
#include "SoundManager.h"
#include "ScriptExecutor.h"
#include "LevelPackManager.h"
#include "ThemeManager.h"
#include "GUIListBox.h"
#include "GUIOverlay.h"
#include "StatisticsManager.h"
#include "StatisticsScreen.h"
#include "Cursors.h"
#include "ScriptAPI.h"
#include "LevelPackPOTExporter.h"
#include "libs/tinyformat/tinyformat.h"
#include "libs/tinygettext/tinygettext.hpp"
#include "libs/tinygettext/log.hpp"
#include "libs/findlocale/findlocale.h"
using namespace std;
#ifdef WIN32
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#define TO_UTF8(SRC, DEST) WideCharToMultiByte(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST), NULL, NULL)
#define TO_UTF16(SRC, DEST) MultiByteToWideChar(CP_UTF8, 0, SRC, -1, DEST, sizeof(DEST)/sizeof(DEST[0]))
#else
#include <strings.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <dirent.h>
#endif
//Initialise the musicManager.
//The MusicManager is used to prevent loading music files multiple times and for playing/fading music.
MusicManager musicManager;
//Initialise the soundManager.
//The SoundManager is used to keep track of the sfx in the game.
SoundManager soundManager;
//Initialise the levelPackManager.
//The LevelPackManager is used to prevent loading levelpacks multiple times and for the game to know which levelpacks there are.
LevelPackManager levelPackManager;
//Map containing changed settings using command line arguments.
map<string,string> tmpSettings;
//Pointer to the settings object.
//It is used to load and save the settings file and change the settings.
Settings* settings=nullptr;
SDL_Renderer* sdlRenderer=nullptr;
std::string pgettext(const std::string& context, const std::string& message) {
if (dictionaryManager) {
return dictionaryManager->get_dictionary().translate_ctxt(context, message);
} else {
return message;
}
}
std::string ngettext(const std::string& message,const std::string& messageplural,int num) {
if (dictionaryManager) {
return dictionaryManager->get_dictionary().translate_plural(message, messageplural, num);
} else {
//Assume it's of English plural rule
return (num != 1) ? messageplural : message;
}
}
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip){
//The offset is needed to draw at the right location.
SDL_Rect offset;
offset.x=x;
offset.y=y;
//Let SDL do the drawing of the surface.
SDL_BlitSurface(source,clip,dest,&offset);
}
void drawRect(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
//NOTE: We let SDL_gfx render it.
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//rectangleRGBA(&renderer,x,y,x+w,y+h,color >> 24,color >> 16,color >> 8,255);
const SDL_Rect r{x,y,w,h};
SDL_RenderDrawRect(&renderer,&r);
}
//Draw a box with anti-aliased borders using SDL_gfx.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color){
SDL_Renderer* rd = &renderer;
//FIXME, this may get the wrong color on system with different endianness.
//Fill content's background color from function parameter
SDL_SetRenderDrawColor(rd,color >> 24,color >> 16,color >> 8,color >> 0);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderFillRect(rd, &r);
}
SDL_SetRenderDrawColor(rd,0,0,0,255);
//Draw first black borders around content and leave 1 pixel in every corner
SDL_RenderDrawLine(rd,x+1,y,x+w-2,y);
SDL_RenderDrawLine(rd,x+1,y+h-1,x+w-2,y+h-1);
SDL_RenderDrawLine(rd,x,y+1,x,y+h-2);
SDL_RenderDrawLine(rd,x+w-1,y+1,x+w-1,y+h-2);
//Fill the corners with transperent color to create anti-aliased borders
SDL_SetRenderDrawColor(rd,0,0,0,160);
SDL_RenderDrawPoint(rd,x,y);
SDL_RenderDrawPoint(rd,x,y+h-1);
SDL_RenderDrawPoint(rd,x+w-1,y);
SDL_RenderDrawPoint(rd,x+w-1,y+h-1);
//Draw second lighter border around content
SDL_SetRenderDrawColor(rd,0,0,0,64);
{
const SDL_Rect r{x+1,y+1,w-2,h-2};
SDL_RenderDrawRect(rd,&r);
}
SDL_SetRenderDrawColor(rd,0,0,0,50);
//Create anti-aliasing in corners of second border
SDL_RenderDrawPoint(rd,x+1,y+1);
SDL_RenderDrawPoint(rd,x+1,y+h-2);
SDL_RenderDrawPoint(rd,x+w-2,y+1);
SDL_RenderDrawPoint(rd,x+w-2,y+h-2);
}
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color){
SDL_SetRenderDrawColor(&renderer,color >> 24,color >> 16,color >> 8,255);
//NOTE: We let SDL_gfx render it.
//lineRGBA(&renderer,x1,y1,x2,y2,color >> 24,color >> 16,color >> 8,255);
SDL_RenderDrawLine(&renderer,x1,y1,x2,y2);
}
void drawLineWithArrow(int x1,int y1,int x2,int y2,SDL_Renderer& renderer,Uint32 color,int spacing,int offset,int xsize,int ysize){
//Draw line first
drawLine(x1,y1,x2,y2,renderer,color);
//calc delta and length
double dx=x2-x1;
double dy=y2-y1;
double length=sqrt(dx*dx+dy*dy);
if(length<0.001) return;
//calc the unit vector
dx/=length; dy/=length;
//Now draw arrows on it
for(double p=offset;p<length;p+=spacing){
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx-ysize*dy+0.5),int(y1+(p-xsize)*dy+ysize*dx+0.5),renderer,color);
drawLine(int(x1+p*dx+0.5),int(y1+p*dy+0.5),
int(x1+(p-xsize)*dx+ysize*dy+0.5),int(y1+(p-xsize)*dy-ysize*dx+0.5),renderer,color);
}
}
ScreenData creationFailed() {
return ScreenData{ nullptr };
}
ScreenData createScreen(){
//Check if we are going fullscreen.
if(settings->getBoolValue("fullscreen"))
pickFullscreenResolution();
//Set the screen_width and height.
SCREEN_WIDTH=atoi(settings->getValue("width").c_str());
SCREEN_HEIGHT=atoi(settings->getValue("height").c_str());
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Set the flags.
Uint32 flags = 0;
Uint32 currentFlags = SDL_GetWindowFlags(sdlWindow);
//#if !defined(ANDROID)
// flags |= SDL_DOUBLEBUF;
//#endif
if(settings->getBoolValue("fullscreen")) {
flags|=SDL_WINDOW_FULLSCREEN; //TODO with SDL2 we can also do SDL_WINDOW_FULLSCREEN_DESKTOP
}
else if(settings->getBoolValue("resizable"))
flags|=SDL_WINDOW_RESIZABLE;
//Create the window and renderer if they don't exist and check if there weren't any errors.
if (!sdlWindow && !sdlRenderer) {
SDL_CreateWindowAndRenderer(SCREEN_WIDTH, SCREEN_HEIGHT, flags, &sdlWindow, &sdlRenderer);
if(!sdlWindow || !sdlRenderer){
std::cerr << "FATAL ERROR: SDL_CreateWindowAndRenderer failed.\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
SDL_SetRenderDrawBlendMode(sdlRenderer, SDL_BlendMode::SDL_BLENDMODE_BLEND);
// White background so we see the menu on failure.
SDL_SetRenderDrawColor(sdlRenderer, 255, 255, 255, 255);
} else if (sdlWindow) {
// Try changing to/from fullscreen
if(SDL_SetWindowFullscreen(sdlWindow, flags & SDL_WINDOW_FULLSCREEN) != 0) {
std::cerr << "WARNING: Failed to switch to fullscreen: " << SDL_GetError() << std::endl;
};
currentFlags = SDL_GetWindowFlags(sdlWindow);
// Change fullscreen resolution
if((currentFlags & SDL_WINDOW_FULLSCREEN ) || (currentFlags & SDL_WINDOW_FULLSCREEN_DESKTOP)) {
SDL_DisplayMode m{0,0,0,0,nullptr};
SDL_GetWindowDisplayMode(sdlWindow,&m);
m.w = SCREEN_WIDTH;
m.h = SCREEN_HEIGHT;
if(SDL_SetWindowDisplayMode(sdlWindow, &m) != 0) {
std::cerr << "WARNING: Failed to set display mode: " << SDL_GetError() << std::endl;
}
} else {
SDL_SetWindowSize(sdlWindow, SCREEN_WIDTH, SCREEN_HEIGHT);
}
}
//Now configure the newly created window (if windowed).
if(settings->getBoolValue("fullscreen")==false)
configureWindow();
//Set the the window caption.
SDL_SetWindowTitle(sdlWindow, ("Me and My Shadow "+version).c_str());
//FIXME Seems to be obsolete
// SDL_EnableUNICODE(1);
//Nothing went wrong so return true.
return ScreenData{sdlRenderer};
}
vector<SDL_Point> getResolutionList(){
//Vector that will hold the resolutions to choose from.
vector<SDL_Point> resolutionList;
//Enumerate available resolutions using SDL_ListModes()
//NOTE: we enumerate fullscreen resolutions because
// windowed resolutions always can be arbitrary
if(resolutionList.empty()){
// SDL_Rect **modes=SDL_ListModes(NULL,SDL_FULLSCREEN|SCREEN_FLAGS|SDL_ANYFORMAT);
//NOTe - currently only using the first display (0)
int numDisplayModes = SDL_GetNumDisplayModes(0);
if(numDisplayModes < 1){
cerr<<"ERROR: Can't enumerate available screen resolutions."
" Use predefined screen resolutions list instead."<<endl;
static const SDL_Point predefinedResolutionList[] = {
{800,600},
{1024,600},
{1024,768},
{1152,864},
{1280,720},
{1280,768},
{1280,800},
{1280,960},
{1280,1024},
{1360,768},
{1366,768},
{1440,900},
{1600,900},
{1600,1200},
{1680,1080},
{1920,1080},
{1920,1200},
{2560,1440},
{3840,2160}
};
//Fill the resolutionList.
for (unsigned int i = 0; i<sizeof(predefinedResolutionList) / sizeof(SDL_Point); i++){
resolutionList.push_back(predefinedResolutionList[i]);
}
}else{
//Fill the resolutionList.
for(int i=0;i < numDisplayModes; ++i){
SDL_DisplayMode mode;
int error = SDL_GetDisplayMode(0, i, &mode);
if(error < 0) {
//We failed to get a display mode. Should we crash here?
std::cerr << "ERROR: Failed to get display mode " << i << " " << std::endl;
}
//Check if the resolution is higher than the minimum (800x600).
if(mode.w >= 800 && mode.h >= 600){
SDL_Point res = { mode.w, mode.h };
resolutionList.push_back(res);
}
}
//Reverse it so that we begin with the lowest resolution.
reverse(resolutionList.begin(),resolutionList.end());
}
}
//Return the resolution list.
return resolutionList;
}
void pickFullscreenResolution(){
//Get the resolution list.
vector<SDL_Point> resolutionList=getResolutionList();
//The resolution that will hold the final result, we start with the minimum (800x600).
SDL_Point closestMatch = { 800, 600 };
int width=atoi(getSettings()->getValue("width").c_str());
int height=atoi(getSettings()->getValue("height").c_str());
int delta = 0x40000000;
//Now loop through the resolutionList.
for (int i = 0; i < (int)resolutionList.size(); i++){
int dx = width - resolutionList[i].x;
if (dx < 0) dx = -dx;
int dy = height - resolutionList[i].y;
if (dy < 0) dy = -dy;
if (dx + dy < delta){
delta = dx + dy;
closestMatch.x = resolutionList[i].x;
closestMatch.y = resolutionList[i].y;
}
}
//Now set the resolution to the closest match.
char s[64];
sprintf(s,"%d",closestMatch.x);
getSettings()->setValue("width",s);
sprintf(s,"%d",closestMatch.y);
getSettings()->setValue("height",s);
}
void configureWindow(){
//We only need to configure the window if it's resizable.
if(!getSettings()->getBoolValue("resizable"))
return;
//We use a new function in SDL2 to restrict minimum window size
SDL_SetWindowMinimumSize(sdlWindow, 800, 600);
}
void onVideoResize(ImageManager& imageManager, SDL_Renderer &renderer){
//Check if the resize event isn't malformed.
if(event.window.data1<=0 || event.window.data2<=0)
return;
//Check the size limit.
//TODO: SDL2 porting note: This may break on systems non-X11 or Windows systems as the window size won't be limited
//there.
if(event.window.data1<800)
event.window.data1=800;
if(event.window.data2<600)
event.window.data2=600;
//Check if it really resizes.
if(SCREEN_WIDTH==event.window.data1 && SCREEN_HEIGHT==event.window.data2)
return;
char s[32];
//Set the new width and height.
SDL_snprintf(s,32,"%d",event.window.data1);
getSettings()->setValue("width",s);
SDL_snprintf(s,32,"%d",event.window.data2);
getSettings()->setValue("height",s);
//FIXME: THIS doesn't work properly.
//Do resizing.
SCREEN_WIDTH = event.window.data1;
SCREEN_HEIGHT = event.window.data2;
//Update the camera.
camera.w=SCREEN_WIDTH;
camera.h=SCREEN_HEIGHT;
//Tell the theme to resize.
if(!loadTheme(imageManager,renderer,""))
return;
//And let the currentState update it's GUI to the new resolution.
currentState->resize(imageManager, renderer);
}
ScreenData init(){
//Initialze SDL.
if(SDL_Init(SDL_INIT_TIMER|SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK)==-1) {
std::cerr << "FATAL ERROR: SDL_Init failed\nError: " << SDL_GetError() << std::endl;
return creationFailed();
}
//Initialze SDL_mixer (audio).
//Note for SDL2 port: Changed frequency from 22050 to 44100.
//22050 caused some sound artifacts on my system, and I'm not sure
//why one would use it in this day and age anyhow.
//unless it's for compatability with some legacy system.
if(Mix_OpenAudio(44100,MIX_DEFAULT_FORMAT,2,1024)==-1){
std::cerr << "FATAL ERROR: Mix_OpenAudio failed\nError: " << Mix_GetError() << std::endl;
return creationFailed();
}
//Set the volume.
Mix_Volume(-1,atoi(settings->getValue("sound").c_str()));
//Increase the number of channels.
soundManager.setNumberOfChannels(48);
//Initialze SDL_ttf (fonts).
if(TTF_Init()==-1){
std::cerr << "FATAL ERROR: TTF_Init failed\nError: " << TTF_GetError() << std::endl;
return creationFailed();
}
//Create the screen.
ScreenData screenData(createScreen());
if(!screenData) {
return creationFailed();
}
//Load key config. Then initialize joystick support.
inputMgr.loadConfig();
inputMgr.openAllJoysitcks();
//Init tinygettext for translations for the right language
dictionaryManager = new tinygettext::DictionaryManager();
dictionaryManager->set_use_fuzzy(false);
dictionaryManager->add_directory(getDataPath()+"locale");
dictionaryManager->set_charset("UTF-8");
//Disable annoying 'Couldn't translate: blah blah blah'
tinygettext::Log::set_log_info_callback(NULL);
//Check if user have defined own language. If not, find it out for the player using findlocale
string lang=getSettings()->getValue("lang");
if(lang.length()>0){
printf("Locale set by user to %s\n",lang.c_str());
language=lang;
}else{
FL_Locale *locale;
FL_FindLocale(&locale,FL_MESSAGES);
printf("Locale isn't set by user: %s\n",locale->lang);
language=locale->lang;
if(locale->country!=NULL){
language+=string("_")+string(locale->country);
}
if(locale->variant!=NULL){
language+=string("@")+string(locale->variant);
}
FL_FreeLocale(&locale);
}
//Now set the language in the dictionaryManager.
dictionaryManager->set_language(tinygettext::Language::from_name(language));
#ifdef WIN32
//Some ad-hoc fix for Windows since it accepts "zh-CN" but not "zh_CN"
std::string language2;
for (auto c : language) {
if (isalnum(c)) language2.push_back(c);
else if (c == '_') language2.push_back('-');
else break;
}
const char* languagePtr = language2.c_str();
#else
const char* languagePtr = language.c_str();
#endif
//Set time format.
setlocale(LC_TIME, languagePtr);
//Also set the numeric format for tinyformat.
tfm::setNumericFormat(
/// TRANSLATORS: This is the decimal point character in your language.
pgettext("numeric", "."),
/// TRANSLATORS: This is the thousands separator character in your language.
pgettext("numeric", ","),
/// TRANSLATORS: This is the grouping of digits in your language,
/// see <http://www.cplusplus.com/reference/locale/numpunct/grouping/> for more information.
/// However, we use string containing "123..." instead of "\x01\x02\x03...", also, "0" is the same as "".
pgettext("numeric", "3")
);
//Create the types of blocks.
for(int i=0;i<TYPE_MAX;i++){
Game::blockNameMap[Game::blockName[i]]=i;
}
//Structure that holds the event type/name pair.
struct EventTypeName{
int type;
const char* name;
};
//Create the types of game object event types.
{
const EventTypeName types[]={
{GameObjectEvent_PlayerWalkOn,"playerWalkOn"},
{GameObjectEvent_PlayerIsOn,"playerIsOn"},
{GameObjectEvent_PlayerLeave,"playerLeave"},
{GameObjectEvent_OnCreate,"onCreate"},
{GameObjectEvent_OnEnterFrame,"onEnterFrame"},
{ GameObjectEvent_OnPlayerInteraction, "onPlayerInteraction" },
{GameObjectEvent_OnToggle,"onToggle"},
{GameObjectEvent_OnSwitchOn,"onSwitchOn"},
{GameObjectEvent_OnSwitchOff,"onSwitchOff"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::gameObjectEventNameMap[types[i].name]=types[i].type;
Game::gameObjectEventTypeMap[types[i].type]=types[i].name;
}
}
//Create the types of level event types.
{
const EventTypeName types[]={
{LevelEvent_OnCreate,"onCreate"},
{LevelEvent_OnSave,"onSave"},
{LevelEvent_OnLoad,"onLoad"},
{0,NULL}
};
for(int i=0;types[i].name;i++){
Game::levelEventNameMap[types[i].name]=types[i].type;
Game::levelEventTypeMap[types[i].type]=types[i].name;
}
}
//Nothing went wrong so we return true.
return screenData;
}
bool loadFonts(){
//Load the fonts.
//NOTE: This is a separate method because it will be called separately when re-initing in case of language change.
//NOTE2: Since the font fallback is implemented, the font will not be loaded again if call loadFonts() twice.
if (fontMgr) {
return true;
}
fontMgr = new FontManager;
fontMgr->loadFonts();
fontTitle = fontMgr->getFont("fontTitle");
fontGUI = fontMgr->getFont("fontGUI");
fontGUISmall = fontMgr->getFont("fontGUISmall");
fontText = fontMgr->getFont("fontText");
fontMono = fontMgr->getFont("fontMono");
if (fontTitle == NULL || fontGUI == NULL || fontGUISmall == NULL || fontText == NULL || fontMono == NULL){
printf("FATAL ERROR: Unable to load fonts!\n");
return false;
}
//Nothing went wrong so return true.
return true;
}
//Generate small arrows used for some GUI widgets.
static void generateArrows(SDL_Renderer& renderer){
TTF_Font* fontArrow = fontMgr->getFont("fontArrow");
arrowLeft1=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(false));
arrowRight1=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(false));
arrowLeft2=textureFromText(renderer,*fontArrow,"<",objThemes.getTextColor(true));
arrowRight2=textureFromText(renderer,*fontArrow,">",objThemes.getTextColor(true));
}
bool loadTheme(ImageManager& imageManager,SDL_Renderer& renderer,std::string name){
//Load default fallback theme if it isn't loaded yet
if(objThemes.themeCount()==0){
if(objThemes.appendThemeFromFile(getDataPath()+"themes/Cloudscape/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load default theme file\n");
return false;
}
}
//Resize background or load specific theme
bool success=true;
if(name==""||name.empty()){
objThemes.scaleToScreen();
}else{
string theme=processFileName(name);
if(objThemes.appendThemeFromFile(theme+"/theme.mnmstheme", imageManager, renderer)==NULL){
printf("ERROR: Can't load theme %s\n",theme.c_str());
success=false;
}
}
generateArrows(renderer);
//Everything went fine so return true.
return success;
}
static SDL_Cursor* loadCursor(const char* image[]){
int i,row,col;
//The array that holds the data (0=white 1=black)
Uint8 data[4*32];
//The array that holds the alpha mask (0=transparent 1=visible)
Uint8 mask[4*32];
//The coordinates of the hotspot of the cursor.
int hotspotX, hotspotY;
i=-1;
//Loop through the rows and columns.
//NOTE: We assume a cursor size of 32x32.
for(row=0;row<32;++row){
for(col=0; col<32;++col){
if(col % 8) {
data[i]<<=1;
mask[i]<<=1;
}else{
++i;
data[i]=mask[i]=0;
}
switch(image[4+row][col]){
case '+':
data[i] |= 0x01;
mask[i] |= 0x01;
break;
case '.':
mask[i] |= 0x01;
break;
default:
break;
}
}
}
//Get the hotspot x and y locations from the last line of the cursor.
sscanf(image[4+row],"%d,%d",&hotspotX,&hotspotY);
return SDL_CreateCursor(data,mask,32,32,hotspotX,hotspotY);
}
bool loadFiles(ImageManager& imageManager, SDL_Renderer& renderer){
//Load the fonts.
if(!loadFonts())
return false;
//Show a loading screen
{
int w = 0,h = 0;
SDL_GetRendererOutputSize(&renderer, &w, &h);
SDL_Color fg={255,255,255,0};
TexturePtr loadingTexture = titleTextureFromText(renderer, _("Loading..."), fg, w);
SDL_Rect loadingRect = rectFromTexture(*loadingTexture);
loadingRect.x = (w-loadingRect.w)/2;
loadingRect.y = (h-loadingRect.h)/2;
SDL_RenderCopy(sdlRenderer, loadingTexture.get(), NULL, &loadingRect);
SDL_RenderPresent(sdlRenderer);
SDL_RenderClear(sdlRenderer);
}
musicManager.destroy();
//Load the music and play it.
if(musicManager.loadMusic((getDataPath()+"music/menu.music")).empty()){
printf("WARNING: Unable to load background music! \n");
}
musicManager.playMusic("menu",false);
//Load all the music lists from the data and user data path.
{
vector<string> musicLists=enumAllFiles((getDataPath()+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
musicLists=enumAllFiles((getUserPath(USER_DATA)+"music/"),"list",true);
for(unsigned int i=0;i<musicLists.size();i++)
getMusicManager()->loadMusicList(musicLists[i]);
}
//Set the list to the configured one.
getMusicManager()->setMusicList(getSettings()->getValue("musiclist"));
//Check if music is enabled.
if(getSettings()->getBoolValue("music"))
getMusicManager()->setEnabled();
//Load the sound effects
soundManager.loadSound((getDataPath()+"sfx/jump.wav").c_str(),"jump");
soundManager.loadSound((getDataPath()+"sfx/hit.wav").c_str(),"hit");
soundManager.loadSound((getDataPath()+"sfx/checkpoint.wav").c_str(),"checkpoint");
soundManager.loadSound((getDataPath()+"sfx/swap.wav").c_str(),"swap");
soundManager.loadSound((getDataPath()+"sfx/toggle.ogg").c_str(),"toggle");
soundManager.loadSound((getDataPath()+"sfx/error.wav").c_str(),"error");
soundManager.loadSound((getDataPath()+"sfx/collect.wav").c_str(),"collect");
soundManager.loadSound((getDataPath()+"sfx/achievement.ogg").c_str(),"achievement");
//Load the cursor images from the Cursor.h file.
cursors[CURSOR_POINTER]=loadCursor(pointer);
cursors[CURSOR_CARROT]=loadCursor(ibeam);
cursors[CURSOR_DRAG]=loadCursor(closedhand);
cursors[CURSOR_SIZE_HOR]=loadCursor(size_hor);
cursors[CURSOR_SIZE_VER]=loadCursor(size_ver);
cursors[CURSOR_SIZE_FDIAG]=loadCursor(size_fdiag);
cursors[CURSOR_SIZE_BDIAG]=loadCursor(size_bdiag);
cursors[CURSOR_REMOVE]=loadCursor(remove_cursor);
cursors[CURSOR_POINTING_HAND] = loadCursor(pointing_hand);
//Set the default cursor right now.
SDL_SetCursor(cursors[CURSOR_POINTER]);
levelPackManager.destroy();
//Now sum up all the levelpacks.
vector<string> v=enumAllDirs(getDataPath()+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getDataPath()+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"levelpacks/"+*i);
}
v=enumAllDirs(getUserPath(USER_DATA)+"custom/levelpacks/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelPackManager.loadLevelPack(getUserPath(USER_DATA)+"custom/levelpacks/"+*i);
}
//Now we add a special levelpack that will contain the levels not in a levelpack.
LevelPack* levelsPack=new LevelPack;
levelsPack->levelpackName="Levels";
levelsPack->levelpackPath=LEVELS_PATH;
levelsPack->type=COLLECTION;
LevelPack* customLevelsPack=new LevelPack;
customLevelsPack->levelpackName="Custom Levels";
customLevelsPack->levelpackPath=CUSTOM_LEVELS_PATH;
customLevelsPack->type=COLLECTION;
//List the main levels and add them one for one.
v = enumAllFiles(getDataPath() + "levels/");
for (vector<string>::iterator i = v.begin(); i != v.end(); ++i){
levelsPack->addLevel(getDataPath() + "levels/" + *i);
levelsPack->setLocked(levelsPack->getLevelCount() - 1);
}
//List the addon levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
}
//List the custom levels and add them one for one.
v=enumAllFiles(getUserPath(USER_DATA)+"custom/levels/");
for(vector<string>::iterator i=v.begin(); i!=v.end(); ++i){
levelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
levelsPack->setLocked(levelsPack->getLevelCount()-1);
customLevelsPack->addLevel(getUserPath(USER_DATA)+"custom/levels/"+*i);
customLevelsPack->setLocked(customLevelsPack->getLevelCount()-1);
}
//Add them to the manager.
levelPackManager.addLevelPack(levelsPack);
levelPackManager.addLevelPack(customLevelsPack);
//Load statistics
statsMgr.loadPicture(renderer, imageManager);
statsMgr.registerAchievements(imageManager);
statsMgr.loadFile(getUserPath(USER_CONFIG)+"statistics");
//Do something ugly and slow
statsMgr.reloadCompletedLevelsAndAchievements();
statsMgr.reloadOtherAchievements();
//Load the theme, both menu and default.
//NOTE: Loading theme may fail and returning false would stop everything, default theme will be used instead.
if (!loadTheme(imageManager,renderer,getSettings()->getValue("theme"))){
getSettings()->setValue("theme","%DATA%/themes/Cloudscape");
saveSettings();
}
//Nothing failed so return true.
return true;
}
bool loadSettings(){
//Check the version of config file.
int version = 0;
std::string cfgV05 = getUserPath(USER_CONFIG) + "meandmyshadow_V0.5.cfg";
std::string cfgV04 = getUserPath(USER_CONFIG) + "meandmyshadow.cfg";
if (fileExists(cfgV05.c_str())) {
//We find a config file of current version.
version = 0x000500;
} else if (fileExists(cfgV04.c_str())) {
//We find a config file of V0.4 version or earlier.
copyFile(cfgV04.c_str(), cfgV05.c_str());
version = 0x000400;
} else {
//No config file found, just create a new one.
version = 0x000500;
}
settings=new Settings(cfgV05);
settings->parseFile(version);
//Now apply settings changed through command line arguments, if any.
map<string,string>::iterator it;
for(it=tmpSettings.begin();it!=tmpSettings.end();++it){
settings->setValue(it->first,it->second);
}
tmpSettings.clear();
//Always return true?
return true;
}
bool saveSettings(){
return settings->save();
}
Settings* getSettings(){
return settings;
}
MusicManager* getMusicManager(){
return &musicManager;
}
SoundManager* getSoundManager(){
return &soundManager;
}
LevelPackManager* getLevelPackManager(){
return &levelPackManager;
}
void flipScreen(SDL_Renderer& renderer){
// Render the data from the back buffer.
SDL_RenderPresent(&renderer);
}
void clean(){
//Save statistics
statsMgr.saveFile(getUserPath(USER_CONFIG)+"statistics");
//We delete the settings.
if(settings){
delete settings;
settings=NULL;
}
//Delete dictionaryManager.
delete dictionaryManager;
//Get rid of the currentstate.
//NOTE: The state is probably already deleted by the changeState function.
if(currentState)
delete currentState;
//Destroy the GUI if present.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
//These calls to destroy makes sure stuff is
//deleted before SDL is uninitialised (as these managers are stack allocated
//globals.)
//Destroy the musicManager.
musicManager.destroy();
//Destroy all sounds
soundManager.destroy();
//Destroy the cursors.
for(int i=0;i<CURSOR_MAX;i++){
SDL_FreeCursor(cursors[i]);
cursors[i]=NULL;
}
//Destroy the levelPackManager.
levelPackManager.destroy();
levels=NULL;
//Close all joysticks.
inputMgr.closeAllJoysticks();
//Close the fonts and quit SDL_ttf.
delete fontMgr;
fontMgr = NULL;
TTF_Quit();
//Remove the temp surface.
SDL_DestroyRenderer(sdlRenderer);
SDL_DestroyWindow(sdlWindow);
arrowLeft1.reset(nullptr);
arrowLeft2.reset(nullptr);
arrowRight1.reset(nullptr);
arrowRight2.reset(nullptr);
//Stop audio.and quit
Mix_CloseAudio();
//SDL2 porting note. Not sure why this was only done on apple.
//#ifndef __APPLE__
Mix_Quit();
//#endif
//And finally quit SDL.
SDL_Quit();
}
void setNextState(int newstate){
//Only change the state when we aren't already exiting.
if(nextState!=STATE_EXIT){
nextState=newstate;
}
}
void changeState(ImageManager& imageManager, SDL_Renderer& renderer, int fade){
//Check if there's a nextState.
if(nextState!=STATE_NULL){
//Fade out, if fading is enabled.
if (currentState && settings->getBoolValue("fading")) {
for (; fade >= 0; fade -= 17) {
currentState->render(imageManager, renderer);
//TODO: Shouldn't the gamestate take care of rendering the GUI?
if (GUIObjectRoot) GUIObjectRoot->render(renderer);
dimScreen(renderer, static_cast<Uint8>(255 - fade));
//draw new achievements (if any) as overlay
statsMgr.render(imageManager, renderer);
flipScreen(renderer);
SDL_Delay(1000/FPS);
}
}
//Delete the currentState.
delete currentState;
currentState=NULL;
//Set the currentState to the nextState.
stateID=nextState;
nextState=STATE_NULL;
//Init the state.
switch(stateID){
case STATE_GAME:
{
currentState=NULL;
Game* game=new Game(renderer, imageManager);
currentState=game;
//Check if we should load record file or a level.
if(!Game::recordFile.empty()){
if (Game::recordFile[0] == '?') {
//This means load record file with current version of level.
game->loadRecord(imageManager, renderer, Game::recordFile.c_str() + 1, levels->getLevelFile().c_str());
} else {
game->loadRecord(imageManager, renderer, Game::recordFile.c_str());
}
Game::recordFile.clear();
}else{
game->loadLevel(imageManager,renderer,levels->getLevelFile());
levels->saveLevelProgress();
}
}
break;
case STATE_MENU:
currentState=new Menu(imageManager, renderer);
break;
case STATE_LEVEL_SELECT:
currentState=new LevelPlaySelect(imageManager, renderer);
break;
case STATE_LEVEL_EDIT_SELECT:
currentState=new LevelEditSelect(imageManager, renderer);
break;
case STATE_LEVEL_EDITOR:
{
currentState=NULL;
LevelEditor* levelEditor=new LevelEditor(renderer, imageManager);
currentState=levelEditor;
//Load the selected level.
levelEditor->loadLevel(imageManager,renderer,levels->getLevelFile());
}
break;
case STATE_OPTIONS:
currentState=new Options(imageManager, renderer);
break;
case STATE_ADDONS:
currentState=new Addons(renderer, imageManager);
break;
case STATE_CREDITS:
currentState=new Credits(imageManager,renderer);
break;
case STATE_STATISTICS:
currentState=new StatisticsScreen(imageManager,renderer);
break;
}
//NOTE: STATE_EXIT isn't mentioned, meaning that currentState is null.
//This way the game loop will break and the program will exit.
}
}
void musicStoppedHook(){
//We just call the musicStopped method of the MusicManager.
musicManager.musicStopped();
}
void channelFinishedHook(int channel){
soundManager.channelFinished(channel);
}
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b){
//Check if the left side of box a isn't past the right side of b.
if(a.x>=b.x+b.w){
return false;
}
//Check if the right side of box a isn't left of the left side of b.
if(a.x+a.w<=b.x){
return false;
}
//Check if the top side of box a isn't under the bottom side of b.
if(a.y>=b.y+b.h){
return false;
}
//Check if the bottom side of box a isn't above the top side of b.
if(a.y+a.h<=b.y){
return false;
}
//We have collision.
return true;
}
bool pointOnRect(const SDL_Rect& point, const SDL_Rect& rect) {
if(point.x >= rect.x && point.x < rect.x + rect.w
&& point.y >= rect.y && point.y < rect.y + rect.h) {
return true;
}
return false;
}
int parseArguments(int argc, char** argv){
//Loop through all arguments.
//We start at one since 0 is the command itself.
for(int i=1;i<argc;i++){
string argument=argv[i];
//Check if the argument is the data-dir.
if(argument=="--data-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the dataPath with the given path.
dataPath=argv[i];
if(!getDataPath().empty()){
char c=dataPath[dataPath.size()-1];
if(c!='/'&&c!='\\') dataPath+="/";
}
}else if(argument=="--user-dir"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Configure the userPath with the given path.
userPath=argv[i];
if(!userPath.empty()){
char c=userPath[userPath.size()-1];
if(c!='/'&&c!='\\') userPath+="/";
}
}else if(argument=="-f" || argument=="-fullscreen" || argument=="--fullscreen"){
tmpSettings["fullscreen"]="1";
}else if(argument=="-w" || argument=="-windowed" || argument=="--windowed"){
tmpSettings["fullscreen"]="0";
}else if(argument=="-mv" || argument=="-music" || argument=="--music"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set the music volume.
tmpSettings["music"]=argv[i];
}else if(argument=="-sv" || argument=="-sound" || argument=="--sound"){
//We need a second argument so we increase i.
i++;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//Now set sound volume.
tmpSettings["sound"]=argv[i];
}else if(argument=="-set" || argument=="--set"){
//We need a second and a third argument so we increase i.
i+=2;
if(i>=argc){
printf("ERROR: Missing argument for command '%s'\n\n",argument.c_str());
return -1;
}
//And set the setting.
tmpSettings[argv[i-1]]=argv[i];
}else if(argument=="-v" || argument=="-version" || argument=="--version"){
//Print the version.
printf("%s\n",version.c_str());
return 0;
} else if (argument == "-h" || argument == "-help" || argument == "--help"){
//If the help is requested we'll return false without printing an error.
//This way the usage/help text will be printed.
return -1;
} else if (argument == "-export-pot" || argument == "--export-pot") {
i++;
if (i >= argc){
printf("ERROR: Missing argument for command '%s'\n\n", argument.c_str());
return -1;
}
for (; i < argc; i++) {
argument = argv[i];
if (argument.empty()) continue;
char c = argument.back();
if (c != '/' && c != '\\') argument += "/";
if (LevelPackPOTExporter::exportPOT(argument)) {
printf("Successful exporting translation template '%slocale/messages.pot'\n", argument.c_str());
} else {
printf("ERROR: Failed to export translation template for levelpack at '%s'\n", argument.c_str());
}
}
return 0;
} else if (argument == "-lua-debug" || argument == "--lua-debug") {
ScriptExecutor::enableDebugSupport = true;
} else {
//Any other argument is unknow so we return false.
printf("ERROR: Unknown argument %s\n\n",argument.c_str());
return -1;
}
}
//If everything went well we can return true.
return 1;
}
//Special structure that will recieve the GUIEventCallbacks of the messagebox.
struct msgBoxHandler:public GUIEventCallback{
public:
//Integer containing the ret(urn) value of the messageBox.
int ret;
public:
//Constructor.
msgBoxHandler():ret(0){}
void GUIEventCallback_OnEvent(ImageManager& imageManager, SDL_Renderer& renderer, std::string name,GUIObject* obj,int eventType){
//Make sure it's a click event.
if(eventType==GUIEventClick){
//Set the return value.
ret=obj->value;
//After a click event we can delete the GUI.
if(GUIObjectRoot){
delete GUIObjectRoot;
GUIObjectRoot=NULL;
}
}
}
};
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const string& prompt,msgBoxButtons buttons,const string& title){
//Create the event handler.
msgBoxHandler objHandler;
//Create the GUIObjectRoot, the height and y location is temp.
//It depends on the content what it will be.
GUIObject* root=new GUIFrame(imageManager,renderer,(SCREEN_WIDTH-600)/2,200,600,200,title.c_str());
//Integer containing the current y location used to grow dynamic depending on the content.
int y=50;
//Now process the prompt.
{
//NOTE: We shouldn't modify the cotents in the c_str() of a string,
//since it's said that at least in g++ the std::string is copy-on-write
//hence if we modify the content it may break
//The copy of the prompt.
std::vector<char> copyOfPrompt(prompt.begin(), prompt.end());
//Append another '\0' to it.
copyOfPrompt.push_back(0);
//Pointer to the string.
char* lps = &(copyOfPrompt[0]);
//Pointer to a character.
char* lp=NULL;
//The list of labels.
std::vector<GUIObject*> labels;
//We keep looping forever.
//The only way out is with the break statement.
for(;;){
//As long as it's still the same sentence we continue.
//It will stop when there's a newline or end of line.
for(lp=lps;*lp!='\n'&&*lp!='\r'&&*lp!=0;lp++);
//Store the character we stopped on. (End or newline)
char c=*lp;
//Set the character in the string to 0, making lps a string containing one sentence.
*lp=0;
//Add a GUIObjectLabel with the sentence.
GUIObject *label = new GUILabel(imageManager, renderer, 0, y, root->width, 25, lps, 0, true, true, GUIGravityCenter);
labels.push_back(label);
root->addChild(label);
//Calculate the width of the text.
int w = 0;
TTF_SizeUTF8(fontText, lps, &w, NULL);
w += 20;
if (w > root->width) root->width = w;
//Increase y with 25, about the height of the text.
y+=25;
//Check the stored character if it was a stop.
if(c==0){
//It was so break out of the for loop.
lps=lp;
break;
}
//It wasn't meaning more will follow.
//We set lps to point after the "newline" forming a new string.
lps=lp+1;
}
//Shrink the dialog if it's too big.
if (root->width > SCREEN_WIDTH - 20) root->width = SCREEN_WIDTH - 20;
root->left = (SCREEN_WIDTH - root->width) / 2;
//Move labels to their correct locations.
for (auto label : labels) {
label->width = root->width;
}
}
//Add 70 to y to leave some space between the content and the buttons.
y+=70;
//Recalc the size of the message box.
root->top=(SCREEN_HEIGHT-y)/2;
root->height=y;
//Now we need to add the buttons.
//Integer containing the number of buttons to add.
int count=0;
//Array with the return codes for the buttons.
int value[3]={0};
//Array containing the captation for the buttons.
string button[3]={"","",""};
switch(buttons){
case MsgBoxOKCancel:
count=2;
button[0]=_("OK");value[0]=MsgBoxOK;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
case MsgBoxAbortRetryIgnore:
count=3;
button[0]=_("Abort");value[0]=MsgBoxAbort;
button[1]=_("Retry");value[1]=MsgBoxRetry;
button[2]=_("Ignore");value[2]=MsgBoxIgnore;
break;
case MsgBoxYesNoCancel:
count=3;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
button[2]=_("Cancel");value[2]=MsgBoxCancel;
break;
case MsgBoxYesNo:
count=2;
button[0]=_("Yes");value[0]=MsgBoxYes;
button[1]=_("No");value[1]=MsgBoxNo;
break;
case MsgBoxRetryCancel:
count=2;
button[0]=_("Retry");value[0]=MsgBoxRetry;
button[1]=_("Cancel");value[1]=MsgBoxCancel;
break;
default:
count=1;
button[0]=_("OK");value[0]=MsgBoxOK;
break;
}
//Now we start making the buttons.
{
//Reduce y so that the buttons fit inside the frame.
y-=40;
double places[3]={0.0};
if(count==1){
places[0]=0.5;
}else if(count==2){
places[0]=0.35;
places[1]=0.65;
}else if(count==3){
places[0]=0.25;
places[1]=0.5;
places[2]=0.75;
}
std::vector<GUIButton*> buttons;
//Loop to add the buttons.
for(int i=0;i<count;i++){
GUIButton* obj = new GUIButton(imageManager, renderer, root->width*places[i], y, -1, 36, button[i].c_str(), value[i], true, true, GUIGravityCenter);
obj->eventCallback=&objHandler;
buttons.push_back(obj);
root->addChild(obj);
}
//Update widgets
for (int i = 0; i < count; i++) {
buttons[i]->render(renderer, 0, 0, false);
}
bool overlap = false;
//Check if they overlap
if (buttons[0]->left - buttons[0]->gravityX < 5 ||
buttons[count - 1]->left - buttons[count - 1]->gravityX + buttons[count - 1]->width > root->width - 5)
{
overlap = true;
} else {
for (int i = 0; i < count - 1; i++) {
if (buttons[i]->left - buttons[i]->gravityX + buttons[i]->width >= buttons[i + 1]->left - buttons[i + 1]->gravityX) {
overlap = true;
break;
}
}
}
//Shrink the font size if any buttons are overlap
if (overlap) {
for (int i = 0; i < count; i++) {
buttons[i]->smallFont = true;
buttons[i]->width = -1;
}
}
}
//Now we dim the screen and keep the GUI rendering/updating.
GUIOverlay* overlay=new GUIOverlay(renderer,root);
overlay->keyboardNavigationMode = LeftRightFocus | UpDownFocus | TabFocus | ((count == 1) ? 0 : ReturnControls);
overlay->enterLoop(imageManager, renderer, true, count == 1);
//And return the result.
return (msgBoxResult)objHandler.ret;
}
-// A helper function to read a character from utf8 string
-// s: the string
-// p [in,out]: the position
-// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
-int utf8ReadForward(const char* s, int& p) {
- int ch = (unsigned char)s[p];
- if (ch < 0x80){
- if (ch) p++;
- return ch;
- } else if (ch < 0xC0){
- // skip invalid characters
- while (((unsigned char)s[p] & 0xC0) == 0x80) p++;
- return -1;
- } else if (ch < 0xE0){
- int c2 = (unsigned char)s[++p];
- if ((c2 & 0xC0) != 0x80) return -1;
-
- ch = ((ch & 0x1F) << 6) | (c2 & 0x3F);
- p++;
- return ch;
- } else if (ch < 0xF0){
- int c2 = (unsigned char)s[++p];
- if ((c2 & 0xC0) != 0x80) return -1;
- int c3 = (unsigned char)s[++p];
- if ((c3 & 0xC0) != 0x80) return -1;
-
- ch = ((ch & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
- p++;
- return ch;
- } else if (ch < 0xF8){
- int c2 = (unsigned char)s[++p];
- if ((c2 & 0xC0) != 0x80) return -1;
- int c3 = (unsigned char)s[++p];
- if ((c3 & 0xC0) != 0x80) return -1;
- int c4 = (unsigned char)s[++p];
- if ((c4 & 0xC0) != 0x80) return -1;
-
- ch = ((ch & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
- if (ch >= 0x110000) ch = -1;
- p++;
- return ch;
- } else {
- p++;
- return -1;
- }
-}
-
-// A helper function to read a character backward from utf8 string (experimental)
-// s: the string
-// p [in,out]: the position
-// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
-int utf8ReadBackward(const char* s, int& p) {
- if (p <= 0) return 0;
-
- do {
- p--;
- } while (p > 0 && ((unsigned char)s[p] & 0xC0) == 0x80);
-
- int tmp = p;
- return utf8ReadForward(s, tmp);
-}
-
#ifndef WIN32
// ad-hoc function to check if a program is installed
static bool programExists(const std::string& program) {
std::string p = tfm::format("which \"%s\" 2>&1", program);
const int BUFSIZE = 128;
char buf[BUFSIZE];
FILE *fp;
if ((fp = popen(p.c_str(), "r")) == NULL) {
return false;
}
while (fgets(buf, BUFSIZE, fp) != NULL) {
// Drop all outputs since 'which' returns -1 when the program is not found
}
if (pclose(fp)) {
return false;
}
return true;
}
#endif
void openWebsite(const std::string& url) {
#ifdef WIN32
wchar_t ws[4096];
TO_UTF16(url.c_str(), ws);
SDL_SysWMinfo info = {};
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(sdlWindow, &info);
ShellExecuteW(info.info.win.window, L"open", ws, NULL, NULL, SW_SHOW);
#else
static int method = -1;
// Some of these methods are copied from https://stackoverflow.com/questions/5116473/
const char* methods[] = {
"xdg-open", "xdg-open \"%s\"",
"gnome-open", "gnome-open \"%s\"",
"kde-open", "kde-open \"%s\"",
"open", "open \"%s\"",
"python", "python -m webbrowser \"%s\"",
"sensible-browser", "sensible-browser \"%s\"",
"x-www-browser", "x-www-browser \"%s\"",
NULL,
};
if (method < 0) {
for (method = 0; methods[method]; method += 2) {
if (programExists(methods[method])) break;
}
}
if (methods[method]) {
std::string p = tfm::format(methods[method + 1], url);
system(p.c_str());
} else {
fprintf(stderr, "TODO: openWebsite is not implemented on your system\n");
}
#endif
}
std::string appendURLToLicense(const std::string& license) {
// if the license doesn't include url, try to detect it from a predefined list
if (license.find("://") == std::string::npos) {
std::string normalized;
for (char c : license) {
if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z')) {
normalized.push_back(c);
} else if (c >= 'a' && c <= 'z') {
normalized.push_back(c + ('A' - 'a'));
}
}
const char* licenses[] = {
// AGPL
"AGPL1", "AGPLV1", NULL, "http://www.affero.org/oagpl.html",
"AGPL2", "AGPLV2", NULL, "http://www.affero.org/agpl2.html",
"AGPL", NULL, "https://gnu.org/licenses/agpl.html",
// LGPL
"LGPL21", "LGPLV21", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html",
"LGPL2", "LGPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/lgpl-2.0.html",
"LGPL", NULL, "https://www.gnu.org/copyleft/lesser.html",
// GPL
"GPL1", "GPLV1", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-1.0.html",
"GPL2", "GPLV2", NULL, "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html",
"GPL", NULL, "https://gnu.org/licenses/gpl.html",
// CC BY-NC-ND
"CCBYNCND1", "CCBYNDNC1", NULL, "https://creativecommons.org/licenses/by-nd-nc/1.0",
"CCBYNCND25", "CCBYNDNC25", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.5",
"CCBYNCND2", "CCBYNDNC2", NULL, "https://creativecommons.org/licenses/by-nc-nd/2.0",
"CCBYNCND3", "CCBYNDNC3", NULL, "https://creativecommons.org/licenses/by-nc-nd/3.0",
"CCBYNCND", "CCBYNDNC", NULL, "https://creativecommons.org/licenses/by-nc-nd/4.0",
// CC BY-NC-SA
"CCBYNCSA1", NULL, "https://creativecommons.org/licenses/by-nc-sa/1.0",
"CCBYNCSA25", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.5",
"CCBYNCSA2", NULL, "https://creativecommons.org/licenses/by-nc-sa/2.0",
"CCBYNCSA3", NULL, "https://creativecommons.org/licenses/by-nc-sa/3.0",
"CCBYNCSA", NULL, "https://creativecommons.org/licenses/by-nc-sa/4.0",
// CC BY-ND
"CCBYND1", NULL, "https://creativecommons.org/licenses/by-nd/1.0",
"CCBYND25", NULL, "https://creativecommons.org/licenses/by-nd/2.5",
"CCBYND2", NULL, "https://creativecommons.org/licenses/by-nd/2.0",
"CCBYND3", NULL, "https://creativecommons.org/licenses/by-nd/3.0",
"CCBYND", NULL, "https://creativecommons.org/licenses/by-nd/4.0",
// CC BY-NC
"CCBYNC1", NULL, "https://creativecommons.org/licenses/by-nc/1.0",
"CCBYNC25", NULL, "https://creativecommons.org/licenses/by-nc/2.5",
"CCBYNC2", NULL, "https://creativecommons.org/licenses/by-nc/2.0",
"CCBYNC3", NULL, "https://creativecommons.org/licenses/by-nc/3.0",
"CCBYNC", NULL, "https://creativecommons.org/licenses/by-nc/4.0",
// CC BY-SA
"CCBYSA1", NULL, "https://creativecommons.org/licenses/by-sa/1.0",
"CCBYSA25", NULL, "https://creativecommons.org/licenses/by-sa/2.5",
"CCBYSA2", NULL, "https://creativecommons.org/licenses/by-sa/2.0",
"CCBYSA3", NULL, "https://creativecommons.org/licenses/by-sa/3.0",
"CCBYSA", NULL, "https://creativecommons.org/licenses/by-sa/4.0",
// CC BY
"CCBY1", NULL, "https://creativecommons.org/licenses/by/1.0",
"CCBY25", NULL, "https://creativecommons.org/licenses/by/2.5",
"CCBY2", NULL, "https://creativecommons.org/licenses/by/2.0",
"CCBY3", NULL, "https://creativecommons.org/licenses/by/3.0",
"CCBY", NULL, "https://creativecommons.org/licenses/by/4.0",
// CC0
"CC0", NULL, "https://creativecommons.org/publicdomain/zero/1.0",
// WTFPL
"WTFPL", NULL, "http://www.wtfpl.net/",
// end
NULL,
};
for (int i = 0; licenses[i]; i++) {
bool found = false;
for (; licenses[i]; i++) {
if (normalized.find(licenses[i]) != std::string::npos) found = true;
}
i++;
if (found) {
return license + tfm::format(" <%s>", licenses[i]);
}
}
}
return license;
}
int getKeyboardRepeatDelay() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDDELAY, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 3);
ret = (i + 1) * 10;
#else
// TODO: platform-dependent code
// Assume it's 250ms, i.e. 10 frames
ret = 10;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatDelay() = %d\n", ret);
#endif
}
return ret;
}
int getKeyboardRepeatInterval() {
static int ret = -1;
if (ret < 0) {
#ifdef WIN32
int i = 0;
SystemParametersInfoW(SPI_GETKEYBOARDSPEED, 0, &i, 0);
// NOTE: these weird numbers are derived from Microsoft's documentation explaining the return value of SystemParametersInfo.
i = clamp(i, 0, 31);
ret = (int)floor(40.0f / (2.5f + 0.887097f * (float)i) + 0.5f);
#else
// TODO: platform-dependent code
// Assume it's 25ms, i.e. 1 frame
ret = 1;
#endif
// Debug
#ifdef _DEBUG
printf("getKeyboardRepeatInterval() = %d\n", ret);
#endif
}
return ret;
}
std::string escapeNewline(const std::string& src) {
std::string ret;
for (auto c : src) {
switch (c) {
case '\r':
break;
case '\n':
ret += "\\n";
break;
case '\\':
ret += "\\\\";
break;
default:
ret.push_back(c);
break;
}
}
return ret;
}
std::string unescapeNewline(const std::string& src) {
std::string ret;
for (int i = 0, m = src.size(); i < m; i++) {
char c = src[i];
if (c == '\\' && i + 1 < m) {
i++;
char c2 = src[i];
switch (c2) {
case 'r':
break;
case 'n':
ret.push_back('\n');
break;
case '\\':
ret.push_back('\\');
break;
default:
ret.push_back(c);
ret.push_back(c2);
break;
}
} else {
ret.push_back(c);
}
}
return ret;
}
std::string escapeCString(const std::string& src) {
std::string ret;
for (auto c : src) {
switch (c) {
case '\r':
break;
case '\n':
ret += "\\n";
break;
case '\t':
ret += "\\t";
break;
case '\0':
ret += "\\0";
break;
case '\\':
ret += "\\\\";
break;
case '\"':
ret += "\\\"";
break;
default:
if (c >= 0 && c < 32) {
char s[8];
sprintf(s, "\\x%02X", (int)c);
ret += s;
} else {
ret.push_back(c);
}
break;
}
}
return ret;
}
diff --git a/src/Functions.h b/src/Functions.h
index 720335b..362cf26 100644
--- a/src/Functions.h
+++ b/src/Functions.h
@@ -1,309 +1,297 @@
/*
* Copyright (C) 2011-2013 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef FUNCTIONS_H
#define FUNCTIONS_H
#include "Globals.h"
#include <SDL.h>
#include <string>
#include <vector>
class MusicManager;
class SoundManager;
//gettext function
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _(message) (dictionaryManager!=NULL?dictionaryManager->get_dictionary().translate(message).c_str():std::string(message).c_str())
//gettext function
//NOTE: "_C" is conflict to some Android macros so we change its name.
//dictionaryManager: Pointer to the dictionaryManager to use for the translation.
//message: The message to translate.
//Returns: The translated string or the original string if there is not translation available.
#define _CC(dictionaryManager, message) ((dictionaryManager)!=NULL?(dictionaryManager)->get_dictionary().translate(message).c_str():std::string(message).c_str())
//dummy function for xgettext
//message: The message to translate.
//Returns: message parameter
#define __(message) (message)
class ImageManager;
struct SDL_Texture;
class LevelPackManager;
//gettext function for contexts
//context: The context of the message.
//message: The message to translate.
//Returns: The translated string or the original string if there are no translations available.
std::string pgettext(const std::string& context, const std::string& message);
//gettext function for plural forms
//message: The singular version of the message to translate.
//messageplural: The plural version of the message to translate.
//num: The number to fetch the plural form for
//Returns: The translated string or the original string if there are no translations available.
std::string ngettext(const std::string& message, const std::string& messageplural, int num);
//Method for drawing an SDL_Surface onto another.
//x: The x location to draw the source on the desination.
//y: The y location to draw the source on the desination.
//source: The SDL_Surface to draw.
//dest: The SDL_Surface to draw on.
//clip: Rectangle which part of the source should be drawn.
void applySurface(int x,int y,SDL_Surface* source,SDL_Surface* dest,SDL_Rect* clip);
//Method used to draw an rectangle.
//x: The top left x location of the rectangle.
//y: The top left y location of the rectangle.
//w: The width of the rectangle,
//h: The height of the rectangle.
//dest: The SDL_Surface to draw on.
//color: The color of the rectangle border to draw.
void drawRect(int x, int y, int w, int h, SDL_Renderer &renderer, Uint32 color=0);
//Method used to draw filled boxes with an anti-alliased border.
//Mostly used for GUI components.
//x: The top left x location of the box.
//y: The top left y location of the box.
//w: The width of the box,
//h: The height of the box.
//renderer: The SDL_Renderer to render on..
//color: The color of the rectangle background to draw.
void drawGUIBox(int x,int y,int w,int h,SDL_Renderer& renderer,Uint32 color);
//Method used to draw a line.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
void drawLine(int x1,int y1,int x2,int y2,SDL_Renderer &renderer,Uint32 color=0);
//Method used to draw a line with some arrows on it.
//x1: The x location of the start point.
//y1: The y location of the start point.
//x2: The x location of the end point.
//y2: The y location of the end point.
//dest: The SDL_Surface to draw on.
//color: The color of the line to draw.
//spacing: The spacing between arrows.
//offset: Offset of first arrow relative to the start point.
//xize, ysize: The size of arrow.
void drawLineWithArrow(int x1, int y1, int x2, int y2, SDL_Renderer &renderer, Uint32 color=0, int spacing=16, int offset=0, int xsize=5, int ysize=5);
//Method that will load the fonts needed for the game.
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing the language.
bool loadFonts();
//Method that will load the default theme again.
//name: name of the theme to load or empty for scaling background
//NOTE: It's separate from loadFiles(), since it might get called separatly from the code when changing resolution.
bool loadTheme(ImageManager& imageManager, SDL_Renderer& renderer, std::string name);
struct ScreenData{
SDL_Renderer* renderer;
explicit operator bool() const {
return renderer!=nullptr;
}
};
//This method will attempt to create the screen/window.
//NOTE: It's separate from init(), since it might get called separatly from the code when changing resolution.
ScreenData createScreen();
//Method for retrieving a list of resolutions.
std::vector<SDL_Point> getResolutionList();
//Method that is called when a fullscreen window is created.
//It will choose the resolution that is closest to the configured one.
void pickFullscreenResolution();
//This method is used to configure the window that is created by createScreen.
//NOTE: It will do it in a WM specific way, so if the wm is unkown it will do nothing.
void configureWindow();
//Call this method when receive SDL_VIDEORESIZE event.
void onVideoResize(ImageManager &imageManager, SDL_Renderer& renderer);
//Initialises the game. This is done almost at the beginning of the program.
//It initialises: SDL, SDL_Mixer, SDL_ttf, the screen and the block types.
//Returns: True if everything goes well.
ScreenData init();
//Loads some important files, like the background music and the default theme.
//Returns: True if everything goes well.
bool loadFiles(ImageManager &imageManager, SDL_Renderer &renderer);
class Settings;
//This method will load the settings from the settings file.
//Returns: False if there's an error while loading.
bool loadSettings();
//This method will save the settings to the settings file.
//Returns: False if there's an error while saving.
bool saveSettings();
//Method used to get a pointer to the settings object.
//Returns: A pointer to the settings object.
Settings* getSettings();
//Method used to get a pointer to the MusicManager object.
//Returns: A pointer to the MusicManager object.
MusicManager* getMusicManager();
//Method used to get a pointer to the SoundManager object.
//Returns: A pointer to the SoundManager object.
SoundManager* getSoundManager();
//Method used to get a pointer to the LevelPackManager object.
//Returns: A pointer to the LevelPackManager object.
LevelPackManager* getLevelPackManager();
//Method that will, depending on the rendering backend, draw the screen surface to the screen.
void flipScreen(SDL_Renderer& renderer);
//Method used to clean up before quiting meandmyshadow.
void clean();
//Sets what the nextState will be.
//newstate: Integer containing the id of the newstate.
void setNextState(int newstate);
//Method that will perform the state change.
//It will fade out (but not fade in).
void changeState(ImageManager &imageManager, SDL_Renderer &renderer, int fade = 255);
//This method is called when music is stopped.
//NOTE: This method is outside the MusicManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
void musicStoppedHook();
//This method is called when a sfx finished playing.
//NOTE: This method is outside the SoundManager because it can't be called otherwise by SDL_Mixer.
//Do not call this method anywhere in the code!
//channel: The number of the channel that is finished.
void channelFinishedHook(int channel);
//Checks collision between two SDL_Rects.
//a: The first rectangle.
//b: The second rectangle.
//Returns: True if the two rectangles collide.
bool checkCollision(const SDL_Rect& a,const SDL_Rect& b);
//Checks if a given point lays on an SDL_Rect
//point: The point to check.
//rect: The rectangle to check.
//Returns: True if the point is on the rectangle.
bool pointOnRect(const SDL_Rect& a,const SDL_Rect& b);
//Parse the commandline arguments.
//argc: Integer containing the number of aruguments there are.
//argv: The arguments.
//Returns: -1 if something goes wrong while parsing,
// 0 if version is shown,
// 1 if everything is alright
int parseArguments(int argc, char** argv);
//From http://en.wikipedia.org/wiki/Clamping_(graphics)
//x: The value to clamp.
//min: The minimum x can be.
//max: The maximum x can be.
//Returns: Integer containing the clamped value.
int inline clamp(int x,int min,int max){
return (x>max)?max:(x<min)?min:x;
}
//Enumeration containing the different messagebox button combinations.
enum msgBoxButtons{
//Only one button with the text OK.
MsgBoxOKOnly=0,
//Two buttons, one saying OK, the other Cancel.
MsgBoxOKCancel=1,
//Three buttons, Abort, Retry, Ignore.
MsgBoxAbortRetryIgnore=2,
//Three buttons, Yes, No or Cancel.
MsgBoxYesNoCancel=3,
//Two buttons, one saying Yes, the other No.
MsgBoxYesNo=4,
//Two buttons, one saying Retry, the other Cancel.
MsgBoxRetryCancel=5,
};
//Enumeration containing the different result that can be retrieved from a messagebox.
//It represents the button that has been pressed.
enum msgBoxResult{
//The OK button.
MsgBoxOK=1,
//The cancel button.
MsgBoxCancel=2,
//The abort button.
MsgBoxAbort=3,
//The retry button.
MsgBoxRetry=4,
//The ignore button.
MsgBoxIgnore=5,
//The yes button.
MsgBoxYes=6,
//The no button.
MsgBoxNo=7,
};
//Method that prompts the user with a notification and/or question.
//prompt: The message the user is prompted with.
//buttons: Which buttons the messagebox should have.
//title: The title of the message box.
//Returns: A msgBoxResult which button has been pressed.
msgBoxResult msgBox(ImageManager& imageManager,SDL_Renderer& renderer, const std::string& prompt,msgBoxButtons buttons,const std::string& title);
-// A helper function to read a character from utf8 string
-// s: the string
-// p [in,out]: the position
-// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
-int utf8ReadForward(const char* s, int& p);
-
-// A helper function to read a character backward from utf8 string (experimental)
-// s: the string
-// p [in,out]: the position
-// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
-int utf8ReadBackward(const char* s, int& p);
-
// Open the website using default web browser.
// url: The url of the website. Currently only http and https are supported.
// Also we assume that the url only contains ASCII characters.
// WARNING: Passing other url may result in arbitrary behavior (esp. passing '*.exe' on Windows).
void openWebsite(const std::string& url);
// Append a URL to a license if the license doesn't include URL (try to detect it from a predefined list).
// license: The license.
// Return value: The license appended with a URL if we detect the license successfully.
std::string appendURLToLicense(const std::string& license);
// Retrieves the (approximate) keyboard repeat delay time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatDelay();
// Retrieves the (approximate) keyboard repeat interval time, in frames (NOTE: frame rate is hardcoded as 40).
int getKeyboardRepeatInterval();
// Escape the newline '\n' and '\\', and removes '\r'.
std::string escapeNewline(const std::string& src);
// Unescape the newline '\n' and '\\'.
std::string unescapeNewline(const std::string& src);
// Removes '\r' and escape the characters which are invalid in C string, e.g. '\n', '\\', '\"', etc.
std::string escapeCString(const std::string& src);
#endif
diff --git a/src/GUIObject.cpp b/src/GUIObject.cpp
index 7516dfc..17bf5dd 100644
--- a/src/GUIObject.cpp
+++ b/src/GUIObject.cpp
@@ -1,1234 +1,1235 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
+#include "UTF8Functions.h"
#include "GUIObject.h"
#include "ThemeManager.h"
#include "InputManager.h"
#include "GUIListBox.h"
#include "GUISlider.h"
#include <algorithm>
#include <iostream>
#include <list>
#include <SDL_ttf_fontfallback.h>
#include "Render.h"
using namespace std;
//Set the GUIObjectRoot to NULL.
GUIObject* GUIObjectRoot=NULL;
//Initialise the event queue.
list<GUIEvent> GUIEventQueue;
//A boolean variable used to skip next mouse up event for GUI (temporary workaround).
bool GUISkipNextMouseUpEvent = false;
void GUIObjectHandleEvents(ImageManager& imageManager, SDL_Renderer& renderer, bool kill){
//Check if we need to reset the skip variable.
if (event.type == SDL_MOUSEBUTTONDOWN) {
GUISkipNextMouseUpEvent = false;
}
//Check if we need to skip event.
if (event.type == SDL_MOUSEBUTTONUP && GUISkipNextMouseUpEvent) {
GUISkipNextMouseUpEvent = false;
} else {
//Make sure that GUIObjectRoot isn't null.
if (GUIObjectRoot)
GUIObjectRoot->handleEvents(renderer);
}
//Check for SDL_QUIT.
if(event.type==SDL_QUIT && kill){
//We get a quit event so enter the exit state.
setNextState(STATE_EXIT);
delete GUIObjectRoot;
GUIObjectRoot=NULL;
return;
}
//Keep calling events until there are none left.
while(!GUIEventQueue.empty()){
//Get one event and remove it from the queue.
GUIEvent e=GUIEventQueue.front();
GUIEventQueue.pop_front();
//If an eventCallback exist call it.
if(e.eventCallback){
e.eventCallback->GUIEventCallback_OnEvent(imageManager,renderer,e.name,e.obj,e.eventType);
}
}
//We empty the event queue just to be sure.
GUIEventQueue.clear();
}
GUIObject::GUIObject(ImageManager& imageManager, SDL_Renderer& renderer, int left, int top, int width, int height,
const char* caption, int value,
bool enabled, bool visible, int gravity) :
left(left), top(top), width(width), height(height),
gravity(gravity), value(value),
enabled(enabled), visible(visible),
eventCallback(NULL), state(0),
cachedEnabled(enabled), gravityX(0),
gravityLeft(0), gravityTop(0), gravityRight(0), gravityBottom(0)
{
//Make sure that caption isn't NULL before setting it.
if (caption){
GUIObject::caption = caption;
//And set the cached caption.
cachedCaption = caption;
}
if (width <= 0)
autoWidth = true;
else
autoWidth = false;
inDialog = false;
//Load the gui images.
bmGuiTex = imageManager.loadTexture(getDataPath() + "gfx/gui.png", renderer);
}
GUIObject::~GUIObject(){
//We need to delete every child we have.
for(unsigned int i=0;i<childControls.size();i++){
delete childControls[i];
}
//Deleted the childs now empty the childControls vector.
childControls.clear();
}
void GUIObject::addChild(GUIObject* obj){
//Add widget add a child
childControls.push_back(obj);
//Copy inDialog boolean from parent.
obj->inDialog = inDialog;
}
GUIObject* GUIObject::getChild(const std::string& name){
//Look for a child with the name.
for (unsigned int i = 0; i<childControls.size(); i++)
if (childControls[i]->name == name)
return childControls[i];
//Not found so return NULL.
return NULL;
}
bool GUIObject::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIObject::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the GUIObject when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUIObject::onResize() {
}
void GUIObject::refreshCache(bool enabled) {
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//TODO: Only change alpha if only enabled changes.
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget
if(autoWidth)
width=-1;
}
}
int GUIObject::getSelectedControl() {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled && obj->state) {
if (dynamic_cast<GUITextBox*>(obj) && obj->state == 2) {
return i;
} else if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
return i;
}
}
}
return -1;
}
void GUIObject::setSelectedControl(int index) {
for (int i = 0; i < (int)childControls.size(); i++) {
GUIObject *obj = childControls[i];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)) {
//It's a button.
obj->state = (i == index) ? 1 : 0;
} else if (dynamic_cast<GUITextBox*>(obj)) {
//It's a text box (or a spin box).
if(i == index) {
obj->state = 2;
} else {
dynamic_cast<GUITextBox*>(obj)->blur();
}
} else if (dynamic_cast<GUISingleLineListBox*>(obj)) {
//It's a single line list box.
obj->state = (i == index) ? 0x100 : 0;
} else if (dynamic_cast<GUISlider*>(obj)) {
//It's a slider.
obj->state = (i == index) ? 0x10000 : 0;
}
}
}
}
int GUIObject::selectNextControl(int direction, int selected) {
//Get the index of currently selected control.
if (selected == 0x80000000) {
selected = getSelectedControl();
}
//Find the next control.
for (int i = 0; i < (int)childControls.size(); i++) {
if (selected < 0) {
selected = 0;
} else {
selected += direction;
if (selected >= (int)childControls.size()) {
selected -= childControls.size();
} else if (selected < 0) {
selected += childControls.size();
}
}
GUIObject *obj = childControls[selected];
if (obj && obj->visible && obj->enabled) {
if (dynamic_cast<GUIButton*>(obj) || dynamic_cast<GUICheckBox*>(obj)
|| dynamic_cast<GUITextBox*>(obj)
|| dynamic_cast<GUISingleLineListBox*>(obj)
|| dynamic_cast<GUISlider*>(obj)
)
{
setSelectedControl(selected);
return selected;
}
}
}
return -1;
}
bool GUIObject::handleKeyboardNavigationEvents(ImageManager& imageManager, SDL_Renderer& renderer, int keyboardNavigationMode) {
if (keyboardNavigationMode == 0) return false;
//Check operation on focused control. These have higher priority.
if (isKeyboardOnly) {
//Check enter key.
if ((keyboardNavigationMode & ReturnControls) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_SELECT)) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
if (dynamic_cast<GUIButton*>(obj)) {
//It's a button.
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
if (dynamic_cast<GUICheckBox*>(obj)) {
//It's a check box.
obj->value = obj->value ? 0 : 1;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
return true;
}
}
}
//Check left/right key.
if ((keyboardNavigationMode & LeftRightControls) != 0 && (inputMgr.isKeyDownEvent(INPUTMGR_LEFT) || inputMgr.isKeyDownEvent(INPUTMGR_RIGHT))) {
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
auto sllb = dynamic_cast<GUISingleLineListBox*>(obj);
if (sllb) {
//It's a single line list box.
int newValue = sllb->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? 1 : -1);
if (newValue >= (int)sllb->item.size()) {
newValue -= sllb->item.size();
} else if (newValue < 0) {
newValue += sllb->item.size();
}
if (sllb->value != newValue) {
sllb->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventClick);
}
}
return true;
}
auto slider = dynamic_cast<GUISlider*>(obj);
if (slider) {
//It's a slider.
int newValue = slider->value + (inputMgr.isKeyDownEvent(INPUTMGR_RIGHT) ? slider->largeChange : -slider->largeChange);
newValue = clamp(newValue, slider->minValue, slider->maxValue);
if (slider->value != newValue) {
slider->value = newValue;
if (obj->eventCallback) {
obj->eventCallback->GUIEventCallback_OnEvent(imageManager, renderer, obj->name, obj, GUIEventChange);
}
}
return true;
}
}
}
}
//Check if we need to exclude printable characters
bool excludePrintable = false;
{
int index = getSelectedControl();
if (index >= 0) {
GUIObject *obj = childControls[index];
if (dynamic_cast<GUITextBox*>(obj)) {
excludePrintable = true;
}
}
}
//Check focus movement
int m = SDL_GetModState();
if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_RIGHT, excludePrintable))
|| ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_DOWN, excludePrintable))
|| ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) == 0)
)
{
isKeyboardOnly = true;
selectNextControl(1);
return true;
} else if (((keyboardNavigationMode & LeftRightFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_LEFT, excludePrintable))
|| ((keyboardNavigationMode & UpDownFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_UP, excludePrintable))
|| ((keyboardNavigationMode & TabFocus) != 0 && inputMgr.isKeyDownEvent(INPUTMGR_TAB, excludePrintable) && (m & KMOD_SHIFT) != 0)
)
{
isKeyboardOnly = true;
selectNextControl(-1);
return true;
}
return false;
}
//////////////GUIButton///////////////////////////////////////////////////////////////////
bool GUIButton::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update button state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible.
if (enabled && visible) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height) {
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press and the event hasn't been already processed.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b) {
//If event callback is configured then add an event to the queue.
if (eventCallback) {
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
//Event has been processed.
b = true;
}
}
}
}
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIButton::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
if(!smallFont) {
cacheTex = textureFromText(renderer, *fontGUI, lp, color);
} else {
cacheTex = textureFromText(renderer, *fontGUISmall, lp, color);
}
//Make the widget transparent if it's disabled.
if(!enabled) {
SDL_SetTextureAlphaMod(cacheTex.get(), 128);
}
//Calculate proper size for the widget.
if(width<=0){
width=textureWidth(*cacheTex)+50;
if(gravity==GUIGravityCenter){
gravityX=int(width/2);
}else if(gravity==GUIGravityRight){
gravityX=width;
}else{
gravityX=0;
}
}
}
if(draw){
//Center the text both vertically as horizontally.
const SDL_Rect size = rectFromTexture(*cacheTex);
const int drawX=x-gravityX+(width-size.w)/2;
const int drawY=y+(height-size.h)/2-GUI_FONT_RAISE;
//Check if the arrows don't fall of.
if(size.w+32<=width){
if(state==1){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w+5,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-25,y+2,*arrowRight1,renderer);
}
}else if(state==2){
if(inDialog){
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft2,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,*arrowRight2,renderer);
}else{
applyTexture(x-gravityX+(width-size.w)/2+4+size.w,y+2,*arrowLeft1,renderer);
applyTexture(x-gravityX+(width-size.w)/2-20,y+2,arrowRight1,renderer);
}
}
}
//Draw the text.
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
}
//////////////GUICheckBox///////////////////////////////////////////////////////////////////
bool GUICheckBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//We don't update state under keyboard only mode.
if (!isKeyboardOnly) {
//Set state to 0.
state = 0;
//Only check for events when the object is both enabled and visible.
if (enabled&&visible){
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height){
//We have hover so set state to one.
state = 1;
//Check for a mouse button press.
if (k&SDL_BUTTON(1))
state = 2;
//Check if there's a mouse press and the event hasn't been already processed.
if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT && !b){
//It's a checkbox so toggle the value.
value = value ? 0 : 1;
//If event callback is configured then add an event to the queue.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventClick };
GUIEventQueue.push_back(e);
}
//Event has been processed.
b = true;
}
}
}
}
return b;
}
void GUICheckBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
refreshCache(enabled);
//Draw the highlight in keyboard only mode.
if (isKeyboardOnly && state && draw) {
drawGUIBox(x, y, width, height, renderer, 0xFFFFFF40);
}
//Get the text.
const char* lp=caption.c_str();
//Make sure it isn't empty.
if(lp!=NULL && lp[0]){
//Update the cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer,*fontText,lp,color);
}
if(draw){
//Calculate the location, center it vertically.
const int drawX=x;
const int drawY=y+(height - textureHeight(*cacheTex))/2;
//Draw the text
applyTexture(drawX, drawY, *cacheTex, renderer);
}
}
if(draw){
//Draw the check (or not).
//value*16 determines where in the gui textures we draw from.
//if(value==1||value==2)
// r1.x=value*16;
const SDL_Rect srcRect={value*16,0,16,16};
const SDL_Rect dstRect={x+width-20, y+(height-16)/2, 16, 16};
//Get the right image depending on the state of the object.
SDL_RenderCopy(&renderer, bmGuiTex.get(), &srcRect, &dstRect);
}
}
//////////////GUILabel///////////////////////////////////////////////////////////////////
bool GUILabel::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUILabel::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
//Get the absolute x and y location.
x+=left;
y+=top;
//Rectangle the size of the widget.
SDL_Rect r;
r.x=x;
r.y=y;
r.w=width;
r.h=height;
//Get the caption and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex){
SDL_Color color = objThemes.getTextColor(inDialog);
cacheTex=textureFromText(renderer, *fontText, lp, color);
if(width<=0)
width=textureWidth(*cacheTex);
}
//Align the text properly and draw it.
if(draw){
const SDL_Rect size = rectFromTexture(*cacheTex);
if(gravity==GUIGravityCenter)
gravityX=(width-size.w)/2;
else if(gravity==GUIGravityRight)
gravityX=width-size.w;
else
gravityX=0;
r.y=y+(height - size.h)/2;
r.x+=gravityX;
applyTexture(r.x, r.y, cacheTex, renderer);
}
}
}
//////////////GUITextBox///////////////////////////////////////////////////////////////////
void GUITextBox::backspaceChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd&&highlightStart>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightStart); // we obtain new highlightStart from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX = highlightStartX = highlightEndX - advance;
caption.erase(highlightStart, highlightEnd - highlightStart);
highlightEnd = highlightStart;
} else if (highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::deleteChar(){
//We need to remove a character so first make sure that there is text.
if(caption.length()>0){
if(highlightStart==highlightEnd){
// this is proper utf8 support
int i = highlightEnd;
utf8ReadForward(caption.c_str(), i);
if (i > highlightEnd) caption.erase(highlightEnd, i - highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}else if(highlightStart<highlightEnd){
caption.erase(highlightStart,highlightEnd-highlightStart);
highlightEnd=highlightStart;
highlightEndX=highlightStartX;
}else{
caption.erase(highlightEnd,highlightStart-highlightEnd);
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
//If there is an event callback then call it.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::moveCarrotLeft(){
if(highlightEnd>0){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadBackward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX-=advance;
}else{
highlightStart=highlightEnd;
highlightStartX=highlightEndX=highlightEndX-advance;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::moveCarrotRight(){
if(highlightEnd<caption.length()){
int advance = 0;
// this is proper UTF-8 support
int ch = utf8ReadForward(caption.c_str(), highlightEnd); // we obtain new highlightEnd from this
if (ch > 0) TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
if(SDL_GetModState() & KMOD_SHIFT){
highlightEndX+=advance;
}else{
highlightStartX=highlightEndX=highlightEndX+advance;
highlightStart=highlightEnd;
}
}else{
if((SDL_GetModState() & KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
}
tick=15;
}
void GUITextBox::updateText(const std::string& text) {
caption = text;
updateSelection(0, 0);
}
void GUITextBox::updateSelection(int start, int end) {
start = clamp(start, 0, caption.size());
end = clamp(end, 0, caption.size());
highlightStart = start;
highlightStartX = 0;
highlightEnd = end;
highlightEndX = 0;
if (start > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, start).c_str(), &highlightStartX, NULL);
}
if (end > 0) {
TTF_SizeUTF8(fontText, caption.substr(0, end).c_str(), &highlightEndX, NULL);
}
}
void GUITextBox::inputText(const char* s) {
int m = strlen(s);
if (m > 0){
if (highlightStart == highlightEnd) {
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
} else if (highlightStart < highlightEnd) {
caption.erase(highlightStart, highlightEnd - highlightStart);
caption.insert((size_t)highlightStart, s);
highlightStart += m;
highlightEnd = highlightStart;
highlightEndX = highlightStartX;
} else {
caption.erase(highlightEnd, highlightStart - highlightEnd);
caption.insert((size_t)highlightEnd, s);
highlightEnd += m;
highlightStart = highlightEnd;
highlightStartX = highlightEndX;
}
int advance = 0;
for (int i = 0;;) {
int a = 0;
int ch = utf8ReadForward(s, i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &a);
advance += a;
}
highlightStartX = highlightEndX = highlightStartX + advance;
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
void GUITextBox::blur(){
state = 0;
highlightStart=highlightStartX=0;
highlightEnd=highlightEndX=0;
}
bool GUITextBox::handleEvents(SDL_Renderer&,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left-gravityX;
y+=top;
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b){
//Get the keycode.
SDL_Keycode key=event.key.keysym.sym;
if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
//Check if the key is supported.
if (event.key.keysym.sym == SDLK_BACKSPACE){
backspaceChar();
} else if (event.key.keysym.sym == SDLK_DELETE){
deleteChar();
} else if (event.key.keysym.sym == SDLK_RIGHT){
moveCarrotRight();
} else if (event.key.keysym.sym == SDLK_LEFT){
moveCarrotLeft();
}
} else {
//Check hotkey.
if (event.key.keysym.sym == SDLK_a) {
//Select all.
highlightStart = 0;
highlightStartX = 0;
highlightEnd = caption.size();
highlightEndX = 0;
if (highlightEnd > 0) {
TTF_SizeUTF8(fontText, caption.c_str(), &highlightEndX, NULL);
}
} else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
//Cut or copy.
int start = highlightStart, end = highlightEnd;
if (start > end) std::swap(start, end);
if (start < end) {
SDL_SetClipboardText(caption.substr(start, end - start).c_str());
if (event.key.keysym.sym == SDLK_x) {
//Cut.
backspaceChar();
}
}
} else if (event.key.keysym.sym == SDLK_v) {
//Paste.
if (SDL_HasClipboardText()) {
char *s = SDL_GetClipboardText();
inputText(s);
SDL_free(s);
}
}
}
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTINPUT && !b){
inputText(event.text.text);
//The event has been processed.
b = true;
} else if (state == 2 && event.type == SDL_TEXTEDITING && !b){
// TODO: process SDL_TEXTEDITING event
}
//Only process mouse event when not in keyboard only mode
if (!isKeyboardOnly) {
//The mouse location (x=i, y=j) and the mouse button (k).
int i, j, k;
k = SDL_GetMouseState(&i, &j);
//Check if the mouse is inside the widget.
if (i >= x && i < x + width && j >= y && j < y + height){
//We can only increase our state. (nothing->hover->focus).
if (state != 2){
state = 1;
}
//Also update the cursor type.
currentCursor = CURSOR_CARROT;
//Move carrot and highlightning according to mouse input.
int clickX = i - x - 2;
int finalPos = 0;
int finalX = 0;
if (cacheTex&&!caption.empty()){
finalPos = caption.length();
for (int i = 0;;){
int advance = 0;
// this is proper UTF-8 support
int i0 = i;
int ch = utf8ReadForward(caption.c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(fontText, ch, NULL, NULL, NULL, NULL, &advance);
finalX += advance;
if (clickX < finalX - advance / 2){
finalPos = i0;
finalX -= advance;
break;
}
}
}
if (event.type == SDL_MOUSEBUTTONUP && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
} else if (event.type == SDL_MOUSEBUTTONDOWN){
state = 2;
highlightStart = highlightEnd = finalPos;
highlightStartX = highlightEndX = finalX;
} else if (event.type == SDL_MOUSEMOTION && (k&SDL_BUTTON(1)) && state == 2){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
}
} else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if (state == 1){
state = 0;
}
//If it's a click event outside the textbox then we blur.
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT){
blur();
}
}
}
}
return b;
}
void GUITextBox::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
refreshCache(enabled);
if(draw){
//Default background opacity
int clr=50;
//If hovering or focused make background more visible.
if(state==1)
clr=128;
else if (state==2)
clr=100;
//Draw the box.
Uint32 color=0xFFFFFF00|clr;
drawGUIBox(x,y,width,height,renderer,color);
}
//Rectangle used for drawing.
SDL_Rect r{0,0,0,0};
//Get the text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
if(!cacheTex) {
//Draw the text.
cacheTex=textureFromText(renderer,*fontText,lp,objThemes.getTextColor(true));
}
if(draw){
//Only draw the carrot and highlight when focus.
if(state==2){
//Place the highlighted area.
r.x=x+4;
r.y=y+3;
r.h=height-6;
if(highlightStart<highlightEnd){
r.x+=highlightStartX;
r.w=highlightEndX-highlightStartX;
}else{
r.x+=highlightEndX;
r.w=highlightStartX-highlightEndX;
}
//Draw the area.
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,128,128,128));
SDL_SetRenderDrawColor(&renderer, 128,128,128,255);
SDL_RenderFillRect(&renderer, &r);
//Ticking carrot.
if(tick<16){
//Show carrot: 15->0.
r.x=x+highlightEndX+2;
r.y=y+3;
r.h=height-6;
r.w=2;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer,0,0,0,255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if(tick<=0)
tick=32;
else
tick--;
}else{
//Hide carrot: 32->16.
tick--;
}
}
//Calculate the location, center it vertically.
SDL_Rect dstRect=rectFromTexture(*cacheTex);
dstRect.x=x+4;
dstRect.y=y+(height-dstRect.h)/2;
dstRect.w=std::min(width-2, dstRect.w);
//Draw the text.
const SDL_Rect srcRect={0,0,width-2,25};
SDL_RenderCopy(&renderer, cacheTex.get(), &srcRect, &dstRect);
}
}else{
//Only draw the carrot when focus.
if(state==2&&draw){
//Ticking carrot.
if (tick<16){
//Show carrot: 15->0.
r.x = x + 4;
r.y = y + 4;
r.w = 2;
r.h = height - 8;
//SDL_FillRect(screen,&r,SDL_MapRGB(screen->format,0,0,0));
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderFillRect(&renderer, &r);
//Reset: 32 or count down.
if (tick <= 0)
tick = 32;
else
tick--;
} else{
//Hide carrot: 32->16.
tick--;
}
}
}
}
//////////////GUIFrame///////////////////////////////////////////////////////////////////
bool GUIFrame::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The widget is only enabled when its parent are enabled.
enabled=enabled && this->enabled;
//The widget is only enabled when its parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Also let the children handle their events.
for(unsigned int i=0;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUIFrame::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing this widget when it's invisible.
if(!visible)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Check if the enabled state changed or the caption, if so we need to clear the (old) cache.
if(enabled!=cachedEnabled || caption.compare(cachedCaption)!=0 || width<=0){
//Free the cache.
cacheTex.reset(nullptr);
//And cache the new values.
cachedEnabled=enabled;
cachedCaption=caption;
//Finally resize the widget.
if(autoWidth)
width=-1;
}
//Draw fill and borders.
if(draw){
Uint32 color=0xDDDDDDFF;
drawGUIBox(x,y,width,height,renderer,color);
}
//Get the title text and make sure it isn't empty.
const char* lp=caption.c_str();
if(lp!=NULL && lp[0]){
//Update cache if needed.
if(!cacheTex) {
cacheTex = textureFromText(renderer, *fontGUI, lp, objThemes.getTextColor(true));
}
//Draw the text.
if(draw) {
applyTexture(x+(width-textureWidth(*cacheTex))/2, y+6-GUI_FONT_RAISE, *cacheTex, renderer);
}
}
//We now need to draw all the children.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
//////////////GUIImage///////////////////////////////////////////////////////////////////
GUIImage::~GUIImage(){
}
bool GUIImage::handleEvents(SDL_Renderer&,int ,int ,bool ,bool ,bool processed){
return processed;
}
void GUIImage::fitToImage(){
const SDL_Rect imageSize = rectFromTexture(*image);
//Increase or decrease the width and height to fully show the image.
if(clip.w!=0) {
width=clip.w;
} else {
width=imageSize.w;
}
if(clip.h!=0) {
height=clip.h;
} else {
height=imageSize.h;
}
}
void GUIImage::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the widget when it's invisible.
//Also make sure the image isn't null.
if(!visible || !image)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
//Create a clip rectangle.
SDL_Rect r=clip;
//The width and height are capped by the GUIImage itself.
if(r.w>width || r.w==0) {
r.w=width;
}
if(r.h>height || r.h==0) {
r.h=height;
}
const SDL_Rect dstRect={x,y,r.w,r.h};
SDL_RenderCopy(&renderer, image.get(), &r, &dstRect);
}
diff --git a/src/GUITextArea.cpp b/src/GUITextArea.cpp
index c0d0c52..eedba7a 100644
--- a/src/GUITextArea.cpp
+++ b/src/GUITextArea.cpp
@@ -1,1135 +1,1136 @@
/*
* Copyright (C) 2011-2012 Me and My Shadow
*
* This file is part of Me and My Shadow.
*
* Me and My Shadow 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 3 of the License, or
* (at your option) any later version.
*
* Me and My Shadow 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.
*
* You should have received a copy of the GNU General Public License
* along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Functions.h"
+#include "UTF8Functions.h"
#include "GUITextArea.h"
#include "ThemeManager.h"
#include <cmath>
#include <algorithm>
#include <assert.h>
#include <ctype.h>
#include <SDL_ttf_fontfallback.h>
using namespace std;
#define SPACE_PER_TAB 2
GUITextArea::GUITextArea(ImageManager& imageManager, SDL_Renderer& renderer,int left,int top,int width,int height,bool enabled,bool visible):
GUIObject(imageManager,renderer,left,top,width,height,NULL,-1,enabled,visible),editable(true){
//Set some default values.
state=0;
setFont(fontText);
highlightLineStart=highlightLineEnd=0;
highlightStartX=highlightEndX=0;
highlightStart=highlightEnd=0;
//Add empty text.
lines.push_back("");
linesCache.push_back(nullptr);
//Create scrollbar widget.
scrollBar=new GUIScrollBar(imageManager,renderer,width-16,0,16,height,1,0,0,0);
childControls.push_back(scrollBar);
scrollBarH=new GUIScrollBar(imageManager,renderer,0,height-16,width-16,16,0,0,0,0,100,500,true,false);
childControls.push_back(scrollBarH);
}
void GUITextArea::setFont(TTF_Font* font){
//NOTE: This fuction shouldn't be called after adding items, so no need to update the whole cache.
widgetFont=font;
fontHeight=TTF_FontHeight(font)+1;
}
void GUITextArea::inputText(SDL_Renderer &renderer, const char* s) {
if (s && s[0]) {
//Split into lines.
vector<string> newLines;
newLines.push_back(std::string());
for (int i = 0; s[i]; i++) {
if (s[i] == '\r') continue;
if (s[i] == '\n') {
newLines.push_back(std::string());
continue;
}
if (s[i] == '\t') {
// Replace tabs by spaces.
newLines.back() += std::string(SPACE_PER_TAB, ' ');
continue;
}
newLines.back().push_back(s[i]);
}
const int m = newLines.size();
if (m == 1 && newLines[0].empty()) return;
//Remove selected text.
removeHighlight(renderer);
//Calculate the width of the last line.
int advance = 0;
{
const char* lastLine = newLines[m - 1].c_str();
for (int i = 0;;) {
int a = 0;
int ch = utf8ReadForward(lastLine, i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &a);
advance += a;
}
}
if (m > 1) {
//Multiple lines.
highlightEnd = newLines[m - 1].size();
highlightStartX = highlightEndX = advance;
newLines[m - 1] += lines[highlightLineStart].substr(highlightStart);
lines[highlightLineStart] = lines[highlightLineStart].substr(0, highlightStart) + newLines[0];
lines.insert(lines.begin() + (highlightLineStart + 1), newLines.begin() + 1, newLines.end());
for (int i = 0; i < m - 1; i++) {
linesCache.insert(linesCache.begin() + (highlightLineStart + 1), nullptr);
}
highlightStart = highlightEnd;
} else {
//Single line.
highlightEnd = highlightStart + newLines[0].size();
lines[highlightLineStart].insert(highlightStart, newLines[0]);
highlightStart = highlightEnd;
highlightStartX = highlightEndX = highlightStartX + advance;
}
//Update cache.
highlightLineEnd = highlightLineStart + m - 1;
for (int i = highlightLineStart; i <= highlightLineEnd; i++) {
linesCache[i] = textureFromText(renderer, *widgetFont, lines[i].c_str(), objThemes.getTextColor(true));
}
highlightLineStart = highlightLineEnd;
//Update view if needed.
adjustView();
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
void GUITextArea::scrollScrollbar(int dx, int dy) {
if (dx && scrollBarH->visible){
scrollBarH->value = clamp(scrollBarH->value + dx, 0, scrollBarH->maxValue);
}
if (dy) {
scrollBar->value = clamp(scrollBar->value + dy, 0, scrollBar->maxValue);
}
}
bool GUITextArea::handleEvents(SDL_Renderer& renderer,int x,int y,bool enabled,bool visible,bool processed){
//Boolean if the event is processed.
bool b=processed;
//The GUIObject is only enabled when he and his parent are enabled.
enabled=enabled && this->enabled;
//The GUIObject is only enabled when he and his parent are enabled.
visible=visible && this->visible;
//Get the absolute position.
x+=left;
y+=top;
//Update the vertical scrollbar.
b=b||scrollBar->handleEvents(renderer,x,y,enabled,visible,b);
//NOTE: We don't reset the state to have a "focus" effect.
//Only check for events when the object is both enabled and visible.
if(enabled&&visible){
//Check if there's a key press and the event hasn't been already processed.
if(state==2 && event.type==SDL_KEYDOWN && !b && editable){
if ((event.key.keysym.mod & KMOD_CTRL) == 0) {
//Check if the key is supported.
if (event.key.keysym.sym == SDLK_BACKSPACE){
//Delete one character direct to prevent a lag.
backspaceChar(renderer);
} else if (event.key.keysym.sym == SDLK_DELETE){
//Delete one character direct to prevent a lag.
deleteChar(renderer);
} else if (event.key.keysym.sym == SDLK_RETURN){
removeHighlight(renderer);
//Split the current line and update.
string str2 = lines.at(highlightLineEnd).substr(highlightStart);
lines.at(highlightLineStart) = lines.at(highlightLineStart).substr(0, highlightStart);
linesCache.at(highlightLineStart) =
textureFromText(renderer, *widgetFont, lines.at(highlightLineStart).c_str(), objThemes.getTextColor(true));
//Calculate indentation.
int indent = 0;
for (int i = 0; i < (int)lines.at(highlightLineStart).length(); i++){
if (isspace(lines.at(highlightLineStart)[i]))
indent++;
else
break;
}
str2.insert(0, indent, ' ');
//Add the rest in a new line.
highlightLineStart++;
highlightStart = indent;
highlightEnd = highlightStart;
highlightLineEnd++;
highlightStartX = 0;
for (int i = 0; i < indent; i++){
int advance;
TTF_GlyphMetrics(widgetFont, str2.at(i), NULL, NULL, NULL, NULL, &advance);
highlightStartX += advance;
}
highlightEndX = highlightStartX;
lines.insert(lines.begin() + highlightLineStart, str2);
auto tex = textureFromText(renderer, *widgetFont, str2.c_str(), objThemes.getTextColor(true));
linesCache.insert(linesCache.begin() + highlightLineStart, std::move(tex));
adjustView();
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
} else if (event.key.keysym.sym == SDLK_TAB){
//Calculate the width of a space.
int advance;
TTF_GlyphMetrics(widgetFont, ' ', NULL, NULL, NULL, NULL, &advance);
int start = highlightLineStart, end = highlightLineEnd;
if (start > end) std::swap(start, end);
for (int line = start; line <= end; line++) {
int count = 0;
std::string &s = lines[line];
if (event.key.keysym.mod & KMOD_SHIFT) {
// remove spaces
for (; count < SPACE_PER_TAB; count++) {
if (s.c_str()[count] != ' ') break;
}
if (count > 0) {
s.erase(0, count);
count = -count;
}
} else {
// add spaces
count = SPACE_PER_TAB;
s.insert(0, count, ' ');
}
//Update cache.
if (count) {
linesCache.at(line) = textureFromText(renderer, *widgetFont, s.c_str(), objThemes.getTextColor(true));
}
//Update selection.
if (line == highlightLineStart) {
highlightStart += count;
highlightStartX += count*advance;
if (highlightStart <= 0) {
highlightStart = 0;
highlightStartX = 0;
}
}
if (line == highlightLineEnd) {
highlightEnd += count;
highlightEndX += count*advance;
if (highlightEnd <= 0) {
highlightEnd = 0;
highlightEndX = 0;
}
}
}
adjustView();
} else if (event.key.keysym.sym == SDLK_RIGHT){
//Move the carrot once to prevent a lag.
moveCarrotRight();
} else if (event.key.keysym.sym == SDLK_LEFT){
//Move the carrot once to prevent a lag.
moveCarrotLeft();
} else if (event.key.keysym.sym == SDLK_DOWN){
//Move the carrot once to prevent a lag.
moveCarrotDown();
} else if (event.key.keysym.sym == SDLK_UP){
//Move the carrot once to prevent a lag.
moveCarrotUp();
}
} else {
//Check hotkey.
if (event.key.keysym.sym == SDLK_a) {
//Select all.
highlightLineStart = 0;
highlightStart = 0;
highlightStartX = 0;
highlightLineEnd = lines.size() - 1;
highlightEnd = lines.back().size();
highlightEndX = 0;
if (highlightEnd > 0) {
TTF_SizeUTF8(widgetFont, lines.back().c_str(), &highlightEndX, NULL);
}
} else if (event.key.keysym.sym == SDLK_x || event.key.keysym.sym == SDLK_c) {
//Cut or copy.
int startLine = highlightLineStart, endLine = highlightLineEnd;
int start = highlightStart, end = highlightEnd;
if (startLine > endLine || (startLine == endLine && start > end)) {
std::swap(startLine, endLine);
std::swap(start, end);
}
std::string s;
if (startLine < endLine) {
//Multiple lines.
s = lines[startLine].substr(start);
s.push_back('\n');
for (int i = startLine + 1; i < endLine; i++) {
s += lines[i];
s.push_back('\n');
}
s += lines[endLine].substr(0, end);
} else {
//Single line.
s = lines[startLine].substr(start, end - start);
}
if (!s.empty()) {
SDL_SetClipboardText(s.c_str());
if (event.key.keysym.sym == SDLK_x) {
//Cut.
removeHighlight(renderer);
//If there is an event callback then call it.
if (eventCallback){
GUIEvent e = { eventCallback, name, this, GUIEventChange };
GUIEventQueue.push_back(e);
}
}
}
} else if (event.key.keysym.sym == SDLK_v) {
//Paste.
if (SDL_HasClipboardText()) {
char *s = SDL_GetClipboardText();
inputText(renderer, s);
SDL_free(s);
}
}
}
//The event has been processed.
b=true;
} else if (state == 2 && event.type == SDL_TEXTINPUT && !b && editable){
inputText(renderer, event.text.text);
} else if (state == 2 && event.type == SDL_TEXTEDITING && !b && editable){
// TODO: process SDL_TEXTEDITING event
}
//The mouse location (x=i, y=j) and the mouse button (k).
int i,j,k;
k=SDL_GetMouseState(&i,&j);
//Check if the mouse is inside the GUIObject.
if(i>=x&&i<x+width&&j>=y&&j<y+height){
//We can only increase our state. (nothing->hover->focus).
if(state!=2){
state=1;
}
//Check for mouse wheel scrolling.
//Scroll horizontally if mouse is over the horizontal scrollbar.
//Otherwise scroll vertically.
if(event.type==SDL_MOUSEWHEEL && event.wheel.y) {
if(j>=y+height-16&&scrollBarH->visible){
scrollScrollbar(event.wheel.y < 0 ? 20 : -20, 0);
}else{
scrollScrollbar(0, event.wheel.y < 0 ? 1 : -1);
}
}
//When mouse is not over the scrollbar.
if(i<x+width-16&&j<(scrollBarH->visible?y+height-16:y+height)){
if (editable) {
//Update the cursor type.
currentCursor = CURSOR_CARROT;
if (((event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEBUTTONDOWN) && event.button.button == 1)
|| (event.type == SDL_MOUSEMOTION && (k & SDL_BUTTON(1))))
{
//Move carrot to the place clicked.
const int mouseLine = clamp((int)floor(float(j - y) / float(fontHeight)) + scrollBar->value, 0, lines.size() - 1);
string* str = &lines.at(mouseLine);
value = str->length();
const int clickX = i - x + scrollBarH->value;
int finalX = 0;
int finalPos = str->length();
for (int i = 0;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str->c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
finalX += advance;
if (clickX < finalX - advance / 2){
finalPos = i0;
finalX -= advance;
break;
}
}
if (event.type == SDL_MOUSEBUTTONUP){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
highlightLineEnd = mouseLine;
} else if (event.type == SDL_MOUSEBUTTONDOWN){
state = 2;
highlightStart = highlightEnd = finalPos;
highlightStartX = highlightEndX = finalX;
highlightLineStart = highlightLineEnd = mouseLine;
} else if (event.type == SDL_MOUSEMOTION){
state = 2;
highlightEnd = finalPos;
highlightEndX = finalX;
highlightLineEnd = mouseLine;
}
}
} else {
const int mouseLine = (int)floor(float(j - y) / float(fontHeight)) + scrollBar->value;
if (mouseLine >= 0 && mouseLine < (int)hyperlinks.size()) {
const int clickX = i - x + scrollBarH->value;
for (const Hyperlink& lnk : hyperlinks[mouseLine]) {
if (clickX >= lnk.startX && clickX < lnk.endX) {
currentCursor = CURSOR_POINTING_HAND;
if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == 1) {
openWebsite(lnk.url);
}
break;
}
}
}
}
}
}else{
//The mouse is outside the TextBox.
//If we don't have focus but only hover we lose it.
if(state==1){
state=0;
}
//If it's a click event outside the textbox then we blur.
if(event.type==SDL_MOUSEBUTTONUP && event.button.button==SDL_BUTTON_LEFT){
//Set state to 0.
state=0;
}
}
}
if(!editable)
highlightLineStart=scrollBar->value;
//Process child controls event except for the scrollbar.
//That's why i starts at one.
for(unsigned int i=1;i<childControls.size();i++){
bool b1=childControls[i]->handleEvents(renderer,x,y,enabled,visible,b);
//The event is processed when either our or the childs is true (or both).
b=b||b1;
}
return b;
}
void GUITextArea::removeHighlight(SDL_Renderer& renderer){
if (highlightLineStart==highlightLineEnd) {
if (highlightStart == highlightEnd) return;
int start=highlightStart, end=highlightEnd, startx=highlightStartX;
if(highlightStart>highlightEnd){
start=highlightEnd;
end=highlightStart;
startx=highlightEndX;
}
std::string& str=lines.at(highlightLineStart);
str.erase(start,end-start);
highlightStart=highlightEnd=start;
highlightStartX=highlightEndX=startx;
// Update cache.
linesCache.at(highlightLineStart) = textureFromText(renderer,*widgetFont,str.c_str(),objThemes.getTextColor(true));
}else{
int startLine=highlightLineStart, endLine=highlightLineEnd,
start=highlightStart, end=highlightEnd, startx=highlightStartX;
if(startLine>endLine){
startLine=highlightLineEnd;
endLine=highlightLineStart;
start=highlightEnd;
end=highlightStart;
startx=highlightEndX;
}
lines[startLine] = lines[startLine].substr(0, start) + lines[endLine].substr(end);
lines.erase(lines.begin() + startLine + 1, lines.begin() + endLine + 1);
linesCache.erase(linesCache.begin() + startLine + 1, linesCache.begin() + endLine + 1);
highlightLineStart=highlightLineEnd=startLine;
highlightStart=highlightEnd=start;
highlightStartX=highlightEndX=startx;
// Update cache.
linesCache.at(startLine) = textureFromText(renderer, *widgetFont, lines[startLine].c_str(), objThemes.getTextColor(true));
}
adjustView();
}
void GUITextArea::deleteChar(SDL_Renderer& renderer){
if (highlightLineStart==highlightLineEnd && highlightStart==highlightEnd){
if(highlightEnd>=(int)lines.at(highlightLineEnd).length()){
if(highlightLineEnd<(int)lines.size()-1){
highlightLineEnd++;
highlightEnd=0;
}
} else {
utf8ReadForward(lines.at(highlightLineEnd).c_str(), highlightEnd);
}
}
removeHighlight(renderer);
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::backspaceChar(SDL_Renderer& renderer){
if(highlightLineStart==highlightLineEnd && highlightStart==highlightEnd){
if(highlightStart<=0){
if(highlightLineStart==0){
highlightStart=0;
}else{
highlightLineStart--;
highlightStart=lines.at(highlightLineStart).length();
highlightStartX=0;
if (highlightStart > 0) {
TexturePtr& t = linesCache.at(highlightLineStart);
if (t) highlightStartX = textureWidth(*t);
}
}
}else{
int advance = 0;
int ch = utf8ReadBackward(lines.at(highlightLineStart).c_str(), highlightStart);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightStartX -= advance;
}
}
removeHighlight(renderer);
//If there is an event callback.
if(eventCallback){
GUIEvent e={eventCallback,name,this,GUIEventChange};
GUIEventQueue.push_back(e);
}
}
void GUITextArea::moveCarrotRight(){
if (highlightEnd>=(int)lines.at(highlightLineEnd).length()){
if (highlightLineEnd<(int)lines.size()-1){
highlightEnd=0;
highlightEndX=0;
highlightLineEnd++;
}
}else{
int advance = 0;
int ch = utf8ReadForward(lines.at(highlightLineEnd).c_str(), highlightEnd);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX += advance;
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightLineStart=highlightLineEnd;
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
adjustView();
}
void GUITextArea::moveCarrotLeft(){
if (highlightEnd<=0){
if (highlightLineEnd==0){
highlightEnd=0;
}else{
highlightLineEnd--;
highlightEnd=lines.at(highlightLineEnd).length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}
}else{
int advance = 0;
int ch = utf8ReadBackward(lines.at(highlightLineEnd).c_str(), highlightEnd);
if (ch > 0) TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
highlightEndX -= advance;
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightLineStart=highlightLineEnd;
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
}
adjustView();
}
void GUITextArea::moveCarrotUp(){
if(highlightLineEnd==0){
highlightEnd=0;
highlightEndX=0;
}else{
highlightLineEnd--;
const std::string& str=lines.at(highlightLineEnd);
//Find out closest match.
int xPos=0;
int i=0;
for (;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str.c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
xPos += advance;
if(highlightEndX<xPos-advance/2){
highlightEnd=i=i0;
highlightEndX=xPos-advance;
break;
}
}
if (i == 0) {
highlightEnd = highlightEndX = 0;
} else if (i == str.length()){
highlightEnd=str.length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
highlightLineStart=highlightLineEnd;
}
adjustView();
}
void GUITextArea::moveCarrotDown(){
if(highlightLineEnd==lines.size()-1){
highlightEnd=lines.at(highlightLineEnd).length();
highlightEndX=0;
if (highlightEnd > 0) {
TexturePtr& t = linesCache.at(highlightLineEnd);
if (t) highlightEndX = textureWidth(*t);
}
}else{
highlightLineEnd++;
string* str=&lines.at(highlightLineEnd);
//Find out closest match.
int xPos=0;
int i = 0;
for (;;){
int advance = 0;
int i0 = i;
int ch = utf8ReadForward(str->c_str(), i);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
xPos += advance;
if (highlightEndX<xPos - advance / 2){
highlightEnd = i = i0;
highlightEndX = xPos - advance;
break;
}
}
if (i == 0) {
highlightEnd = highlightEndX = 0;
} else if (i == str->length()){
highlightEnd=str->length();
highlightEndX=0;
TexturePtr& t = linesCache.at(highlightLineEnd);
if(t) highlightEndX=textureWidth(*t);
}
}
if((SDL_GetModState()&KMOD_SHIFT)==0){
highlightStart=highlightEnd;
highlightStartX=highlightEndX;
highlightLineStart=highlightLineEnd;
}
adjustView();
}
void GUITextArea::adjustView(){
//Adjust view to current line.
if(fontHeight*(highlightLineEnd-scrollBar->value)+4>height-4)
scrollBar->value=highlightLineEnd-3;
else if(highlightLineEnd-scrollBar->value<0)
scrollBar->value=highlightLineEnd;
//Find out the lenght of the longest line.
int maxWidth=0;
for(const TexturePtr& tex: linesCache){
if(tex) {
const int texWidth = textureWidth(*tex.get());
if(texWidth>width-16&&texWidth>maxWidth)
maxWidth=texWidth;
}
}
//We need the horizontal scrollbar if any line is too long.
if(maxWidth>0){
scrollBar->height=height-16;
scrollBarH->visible=true;
scrollBarH->maxValue=maxWidth-width+24;
}else{
scrollBar->height=height;
scrollBarH->visible=false;
scrollBarH->value=0;
scrollBarH->maxValue=0;
}
//Adjust the horizontal view.
int carrotX=0;
for(int n=0;n<highlightEnd;){
int advance = 0;
int ch = utf8ReadForward(lines.at(highlightLineEnd).c_str(), n);
if (ch <= 0) break;
TTF_GlyphMetrics(widgetFont, ch, NULL, NULL, NULL, NULL, &advance);
carrotX += advance;
}
if(carrotX>width-24)
scrollBarH->value=scrollBarH->maxValue;
else
scrollBarH->value=0;
//Update vertical scrollbar.
int rh=height-(scrollBarH->visible?16:0);
int m=lines.size(),n=(int)floor((float)rh/(float)fontHeight);
if(m>n){
scrollBar->maxValue=m-n;
scrollBar->smallChange=1;
scrollBar->largeChange=n;
}else{
scrollBar->value=0;
scrollBar->maxValue=0;
}
}
void GUITextArea::drawHighlight(SDL_Renderer& renderer, int x,int y,SDL_Rect r,SDL_Color color){
if(r.x<x) {
int tmp_w = r.w - x + r.x;
if(tmp_w<=0) return;
r.w = tmp_w;
r.x = x;
}
if(r.x+r.w > x+width){
int tmp_w=width-r.x+x;
if(tmp_w<=0) return;
r.w=tmp_w;
}
if(r.y<y){
int tmp_h=r.h - y + r.y;
if(tmp_h<=0) return;
r.h=tmp_h;
r.y = y;
}
if(r.y+r.h > y+height){
int tmp_h=height-r.y+y;
if(tmp_h<=0) return;
r.h=tmp_h;
}
SDL_SetRenderDrawColor(&renderer, color.r, color.g, color.b, color.a);
SDL_RenderFillRect(&renderer, &r);
}
void GUITextArea::render(SDL_Renderer& renderer, int x,int y,bool draw){
//There's no need drawing the GUIObject when it's invisible.
if(!visible||!draw)
return;
//Get the absolute x and y location.
x+=left;
y+=top;
{
//Draw the box.
const Uint32 color=0xFFFFFFFF;
drawGUIBox(x,y,width,height,renderer,color);
}
//Place the highlighted area.
SDL_Rect r;
const SDL_Color color{128,128,128,255};
if (editable) {
if (highlightLineStart == highlightLineEnd){
r.x = x - scrollBarH->value;
r.y = y + ((highlightLineStart - scrollBar->value)*fontHeight);
r.h = fontHeight;
if (highlightStart < highlightEnd){
r.x += highlightStartX;
r.w = highlightEndX - highlightStartX;
} else{
r.x += highlightEndX;
r.w = highlightStartX - highlightEndX;
}
drawHighlight(renderer, x, y, r, color);
} else if (highlightLineStart < highlightLineEnd){
int lnc = highlightLineEnd - highlightLineStart;
for (int i = 0; i <= lnc; i++){
r.x = x - scrollBarH->value;
r.y = y + ((i + highlightLineStart - scrollBar->value)*fontHeight);
r.w = width + scrollBarH->maxValue;
r.h = fontHeight;
if (i == 0){
r.x += highlightStartX;
r.w -= highlightStartX;
} else if (i == lnc){
r.w = highlightEndX;
}
if (lines.at(i + highlightLineStart).empty()){
r.w = fontHeight / 4;
}
drawHighlight(renderer, x, y, r, color);
}
} else{
int lnc = highlightLineStart - highlightLineEnd;
for (int i = 0; i <= lnc; i++){
r.x = x - scrollBarH->value;
r.y = y + ((i + highlightLineEnd - scrollBar->value)*fontHeight);
r.w = width + scrollBarH->maxValue;
r.h = fontHeight;
if (i == 0){
r.x += highlightEndX;
r.w -= highlightEndX;
} else if (i == lnc){
r.w = highlightStartX;
}
if (lines.at(i + highlightLineEnd).empty()){
r.w = fontHeight / 4;
}
drawHighlight(renderer, x, y, r, color);
}
}
}
//Draw text.
int lineY=0;
for(int line=scrollBar->value;line<(int)linesCache.size();line++){
TexturePtr& it = linesCache[line];
if(it){
if(lineY<height){
SDL_Rect r = { scrollBarH->value, 0, std::min(width - 17, textureWidth(*it.get()) - scrollBarH->value), textureHeight(*it.get()) };
int over=-height+lineY+fontHeight;
if(over>0) r.h-=over;
const SDL_Rect dstRect={x+1,y+1+lineY,r.w,r.h};
if(r.w>0 && r.h>0) SDL_RenderCopy(&renderer,it.get(),&r,&dstRect);
// draw hyperlinks
if (!editable && line<(int)hyperlinks.size()) {
r.y = lineY + fontHeight - 1;
if (r.y < height){
r.y += y + 1;
r.h = 1;
for (const Hyperlink& lnk : hyperlinks[line]) {
r.x = clamp(lnk.startX - scrollBarH->value, 0, width - 17);
r.w = clamp(lnk.endX - scrollBarH->value, 0, width - 17);
if (r.w > r.x) {
r.w -= r.x;
r.x += x + 1;
SDL_SetRenderDrawColor(&renderer, 0, 0, 0, 255);
SDL_RenderFillRect(&renderer, &r);
}
}
}
}
}else{
break;
}
}
lineY+=fontHeight;
}
//Only draw the carrot when focus.
if(state==2&&editable){
r.x=x-scrollBarH->value+highlightEndX;
r.y=y+4+fontHeight*(highlightLineEnd-scrollBar->value);
r.w=2;
r.h=fontHeight-4;
//Make sure that the carrot is inside the textbox.
if((r.y<y+height-4)&&(r.y>y)&&(r.x>x-1)&&(r.x<x+width-16)){
drawHighlight(renderer,x,y,r,SDL_Color{0,0,0,127});
}
}
//We now need to draw all the children of the GUIObject.
for(unsigned int i=0;i<childControls.size();i++){
childControls[i]->render(renderer,x,y,draw);
}
}
void GUITextArea::addString(SDL_Renderer& renderer, const std::string& input, bool wordWrap) {
if (wordWrap && !input.empty()) {
const size_t m = input.size();
size_t lp = 0;
std::string line;
int lineWidth = 0;
for (;;) {
//A word consists of a sequence of white spaces and a sequence of non-white-spaces.
//Initialize.
size_t lps = lp, numberOfSpaces = 0;
//Read white spaces.
for (; lp < m; lp++) {
if (input[lp] != ' ' && input[lp] != '\t') break;
numberOfSpaces++;
}
//Read non-white-spaces.
//TODO: For CJK should only read one CJK character (possibly with a punctuation mark)
for (; lp < m; lp++) {
if (input[lp] == ' ' || input[lp] == '\t') break;
}
assert(lp > lps);
if (line.empty()) {
//A line consists of at least one word, so we append it forcefully.
line = input.substr(lps, lp - lps);
TTF_SizeUTF8(widgetFont, line.c_str(), &lineWidth, NULL);
} else {
//Calculate the width of the new word.
std::string newWord = input.substr(lps, lp - lps);
int newWidth;
TTF_SizeUTF8(widgetFont, newWord.c_str(), &newWidth, NULL);
//Check if it fits into current line.
if (lineWidth + newWidth > width - 16) {
//No, we output current line.
lines.push_back(line);
//And add a new line consisting of new word (but we remove spaces in it).
if (numberOfSpaces > 0) {
line = newWord.substr(numberOfSpaces);
TTF_SizeUTF8(widgetFont, line.c_str(), &lineWidth, NULL);
} else {
line = newWord;
lineWidth = newWidth;
}
} else {
//Yes, we append the new word to current line.
line.append(newWord);
lineWidth += newWidth;
}
}
//Check if we processed all the characters.
if (lp >= m) break;
}
//Output the remaining text.
if (!line.empty()) {
lines.push_back(line);
}
} else {
lines.push_back(input);
}
}
void GUITextArea::setString(SDL_Renderer& renderer, const std::string& input, bool wordWrap) {
//Clear previous content if any.
//Delete every line.
lines.clear();
linesCache.clear();
size_t linePos=0,lineLen=0;
//Loop through the input string.
for(size_t i=0;i<input.length();++i){
//Check when we come in end of a line.
if(input.at(i)=='\n'){
//Check if the line is empty.
if(lineLen==0){
addString(renderer, std::string(), wordWrap);
} else{
//Read the whole line.
addString(renderer, input.substr(linePos, lineLen), wordWrap);
}
//Skip '\n' in end of the line.
linePos=i+1;
lineLen=0;
}else{
lineLen++;
}
}
//The string might not end with a newline.
//That's why we're going to add end rest of the string as one line.
addString(renderer, input.substr(linePos), wordWrap);
//Render and cache text.
for (const std::string& s : lines) {
linesCache.push_back(textureFromText(renderer, *widgetFont, s.c_str(), objThemes.getTextColor(true)));
}
adjustView();
}
void GUITextArea::setStringArray(SDL_Renderer& renderer, const std::vector<std::string>& input, bool wordWrap) {
//Free cached images.
linesCache.clear();
//Copy values.
if (wordWrap) {
lines.clear();
for (const std::string& s : input) {
addString(renderer, s, wordWrap);
}
} else {
lines = input;
}
//Render and cache text.
for(const std::string& s: lines) {
linesCache.push_back(textureFromText(renderer,*widgetFont,s.c_str(),objThemes.getTextColor(true)));
}
adjustView();
}
void GUITextArea::extractHyperlinks() {
const int lm = lines.size();
hyperlinks.clear();
if (lm <= 0) return;
hyperlinks.resize(lm);
for (int l = 0; l < lm; l++) {
const char* s = lines[l].c_str();
for (int i = 0, m = lines[l].size(); i < m; i++) {
const int lps = i;
std::string url;
// we only support http or https
if ((s[i] == 'H' || s[i] == 'h')
&& (s[i + 1] == 'T' || s[i + 1] == 't')
&& (s[i + 2] == 'T' || s[i + 2] == 't')
&& (s[i + 3] == 'P' || s[i + 3] == 'p'))
{
if (s[i + 4] == ':' && s[i + 5] == '/' && s[i + 6] == '/') {
// http
i += 7;
url = "http://";
} else if ((s[i + 4] == 'S' || s[i + 4] == 's') && s[i + 5] == ':' && s[i + 6] == '/' && s[i + 7] == '/') {
// https
i += 8;
url = "https://";
} else {
continue;
}
for (; i < m; i++) {
char c = s[i];
// url ends with following character
if (c == '\0' || c == ' ' || c == ')' || c == ']' || c == '}' || c == '>' || c == '\r' || c == '\n' || c == '\t') {
break;
}
url.push_back(c);
}
} else {
continue;
}
const int lpe = i;
Hyperlink hyperlink = {};
TTF_SizeUTF8(widgetFont, lines[l].substr(0, lps).c_str(), &hyperlink.startX, NULL);
TTF_SizeUTF8(widgetFont, lines[l].substr(0, lpe).c_str(), &hyperlink.endX, NULL);
hyperlink.url = lines[l].substr(lps, lpe - lps);
hyperlinks[l].push_back(hyperlink);
}
}
}
string GUITextArea::getString(){
string tmp;
for(vector<string>::iterator it=lines.begin();it!=lines.end();++it){
//Append a newline only if not the first line.
if(it!=lines.begin())
tmp.append(1,'\n');
//Append the line.
tmp.append(*it);
}
return tmp;
}
void GUITextArea::onResize(){
scrollBar->left=width-16;
scrollBar->height=height;
if(scrollBarH->visible)
scrollBar->height-=16;
scrollBarH->top=height-16;
scrollBarH->width=width-16;
adjustView();
}
diff --git a/src/HyphenationRule.cpp b/src/HyphenationRule.cpp
new file mode 100644
index 0000000..559ab32
--- /dev/null
+++ b/src/HyphenationRule.cpp
@@ -0,0 +1,108 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+
+/* This source file provides code for the HyphenationRule class which
+ * is documented in HyphenationRule.h */
+#include "HyphenationRule.h"
+
+using namespace std;
+
+Hyphenate::HyphenationRule::HyphenationRule(std::string dpattern)
+ : del_pre(0), skip_post(0)
+{
+ int priority = 0;
+ unsigned int i;
+
+ for (i = 0; i < dpattern.size() && dpattern[i] != '/'; i++)
+ if (dpattern[i] >= '0' && dpattern[i] <= '9')
+ priority = 10 * priority + dpattern[i] - '0';
+ else {
+ key += dpattern[i];
+ priorities.push_back(priority);
+ priority = 0;
+ }
+
+ /* Complete and simplify the array. */
+ priorities.push_back(priority);
+ while (priorities.back() == 0) priorities.pop_back();
+
+ /* Now check for nonstandard hyphenation. First, parse it. */
+ if (i < dpattern.size() && dpattern[i] == '/') {
+ i += 1; /* Ignore the /. */
+
+ int field = 1;
+ unsigned int start = 0, cut = 0;
+ for (; i < dpattern.size(); i++) {
+ if (field == 1 && dpattern[i] == '=')
+ field++;
+ else if (field >= 2 && field <= 3 && dpattern[i] == ',')
+ field++;
+ else if (field == 4 && (dpattern[i] < '0' || dpattern[i] > '9'))
+ break;
+ else if (field == 1)
+ insert_pre += dpattern[i];
+ else if (field == 2)
+ insert_post += dpattern[i];
+ else if (field == 3)
+ start = start * 10 + dpattern[i] - '0';
+ else if (field == 4)
+ cut = cut * 10 + dpattern[i] - '0';
+ }
+ if (field < 4) /* There was no fourth field */
+ cut = key.size() - start;
+ if (field < 3)
+ start = 1;
+
+ skip_post = cut;
+ for (unsigned int j = start; j < start + cut && j < priorities.size(); j++) {
+ if (priorities[j - 1] % 2 == 1) break;
+ del_pre++; skip_post--;
+ }
+ }
+}
+
+int Hyphenate::HyphenationRule::apply(string& word, const string &hyph) const
+{
+ apply_first(word, hyph);
+ return apply_second(word);
+}
+
+void Hyphenate::HyphenationRule::apply_first(string& word, const string &hyph)
+const
+{
+ if (del_pre > 0) word.erase(word.size() - del_pre);
+ word += insert_pre;
+ word += hyph;
+}
+
+int Hyphenate::HyphenationRule::apply_second(string& word) const
+{
+ if (del_pre > 0) word.erase(word.size() - del_pre);
+ word += insert_post;
+ return skip_post;
+}
+
+auto_ptr<char> Hyphenate::HyphenationRule::replacement_string() const {
+ string s = (insert_pre + "=" + insert_post);
+ char *r = (char *)malloc((s.size() + 1) * sizeof(char));
+ strcpy(r, s.c_str());
+ return auto_ptr<char>(r);
+}
diff --git a/src/HyphenationRule.h b/src/HyphenationRule.h
new file mode 100644
index 0000000..b4cee86
--- /dev/null
+++ b/src/HyphenationRule.h
@@ -0,0 +1,119 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+#ifndef HYPHENATION_RULE_H
+#define HYPHENATION_RULE_H
+
+#include <string>
+#include <vector>
+
+namespace Hyphenate {
+ /** The HyphenationRule class represents a single Hyphenation Rule, that
+ * is, a pattern that has a number assigned to each letter and will,
+ * if applied, hyphenate a word at the given point. The number assigned
+ * to each letter and accessed by priority() is odd when hyphenation
+ * should occur before the letter, and only the rule with the highest
+ * number will be applied to any letter. */
+ class HyphenationRule {
+ private:
+ int del_pre, skip_post;
+ std::string key, insert_pre, insert_post;
+ std::vector<char> priorities;
+
+ std::string replacement;
+
+ public:
+ /* HyphenationRule is constructed from a string consisting of
+ * letters with numbers strewn in. The numbers are the priorities.
+ * In addition, a / will start a non-standard hyphenization. */
+ HyphenationRule(std::string source_string);
+
+ /** Call this method once an hyphen would, according to its base rule,
+ * be placed. Returns the number of bytes that should not be
+ * printed afterwards.
+ *
+ * For example, when applying the rules to "example", you should
+ * call the rules returned by HyphenationTree or Hyphenator as
+ * follows:
+ * string word = "ex";
+ * rule1.apply(word, "-");
+ * word += "am" ;
+ * rule2.apply(word, "-");
+ * word += "ple";
+ *
+ * Watch out for non-standard rules, though. Example: "Schiffahrt"
+ * string word = "Schif";
+ * int skip = rule1.apply(word, "-");
+ * char *rest = "fahrt";
+ * word += rest+skip;
+ */
+ int apply(std::string& word, const std::string &hyphen) const;
+ /** Only apply the first part, that is, up to and including the
+ * hyphen. */
+ void apply_first(std::string& word, const std::string &hyphen) const;
+ /** Only apply the second part, after the hyphen. */
+ int apply_second(std::string& word) const;
+
+ /** Returns true iff there is a priority value != 0 for this offset
+ * or a larger one. */
+ inline bool hasPriority(unsigned int offset) const
+ {
+ return priorities.size() > offset;
+ }
+ /** Returns the hyphenation priority for a hyphen preceding the byte
+ * at the given offset. */
+ inline char priority(unsigned int offset) const { return priorities[offset]; }
+
+ /** Returns the pattern to match for this rule to apply. */
+ inline std::string &getKey() { return key; }
+
+ /** Returns the amount of bytes that will additionally be needed
+ * in front of the hyphen if this rule is applied. 0 for standard
+ * hyphenation, 1 for Schiff-fahrt. */
+ int spaceNeededPreHyphen() const
+ {
+ return insert_pre.size() - del_pre;
+ }
+
+ /** Returns true iff this rule is not a standard hyphenation rule. */
+ bool isNonStandard() const
+ {
+ return del_pre != 0 || skip_post != 0 ||
+ (!insert_pre.empty()) || (!insert_post.empty());
+ }
+
+ /** Only needed for libhnj implementation:
+ * Returns an malloc()-allocated char array consisting of the full
+ * replacement of this rule, with a = between the parts. For example,
+ * for Schiffahrt -> Schiff-fahrt this yields 'ff=' or 'ff=f',
+ * depending on implementation. */
+ std::auto_ptr<char> replacement_string() const;
+ /** Only needed for libhnj implementation:
+ * Get the offset at which the hyphen will end up compared to a
+ * standard rule. 0 for standard rules, Schiff-fahrt would yield 1. */
+ int getHyphenOffset() const { return insert_pre.size() - del_pre; }
+ /** Only needed for libhnj implementation:
+ * Returns the total number of bytes that need to be cut out
+ * before the replacement_string() should be inserted. */
+ int getTotalCutout() const { return skip_post + del_pre; }
+ };
+}
+
+#endif
diff --git a/src/HyphenationTree.cpp b/src/HyphenationTree.cpp
new file mode 100644
index 0000000..35c534c
--- /dev/null
+++ b/src/HyphenationTree.cpp
@@ -0,0 +1,239 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+
+/* ------------- Implementation for HyphenationTree.h ---------------- */
+
+#include "HyphenationTree.h"
+#include "UTF8Functions.h"
+#include <iostream>
+
+using namespace std;
+using namespace Hyphenate;
+
+/* The HyphenationNode is a tree node for the hyphenation search tree. It
+* represents the matching state after a single character; if there is a
+* pattern that ends with that particular character, the hyphenation_pattern
+* is set to non-NULL. The jump_table links to the children of that node,
+* indexed by letters. */
+class Hyphenate::HyphenationNode {
+public:
+ typedef std::map<char, HyphenationNode*> JumpTable;
+ /* Table of children */
+ JumpTable jump_table;
+ /* Hyphenation pattern associated with the full path to this node. */
+ std::auto_ptr<HyphenationRule> hyphenation_pattern;
+
+ HyphenationNode() {}
+ ~HyphenationNode() {
+ /* The destructor has to destroy all childrens. */
+ for (JumpTable::iterator i = jump_table.begin();
+ i != jump_table.end(); i++)
+ delete i->second;
+ }
+
+ /** Find a particular jump table entry, or NULL if there is
+ * none for that letter. */
+ inline const HyphenationNode *find(char arg) const {
+ JumpTable::const_iterator i = jump_table.find(arg);
+ if (i != jump_table.end()) return i->second; else return NULL;
+ }
+ /** Find a particular jump table entry, or NULL if there is none
+ * for that letter. */
+ inline HyphenationNode *find(char arg) {
+ JumpTable::iterator i = jump_table.find(arg);
+ if (i != jump_table.end()) return i->second; else return NULL;
+ }
+
+ /** Insert a particular hyphenation pattern into this
+ * hyphenation subtree.
+ * \param pattern The character pattern to match in the input word.
+ * \param hp The digit-pattern for the hyphenation algorithm.
+ */
+ void insert(const char *id,
+ std::auto_ptr<HyphenationRule> pattern);
+
+ /** Apply all patterns for that subtree. */
+ void apply_patterns(
+ char *priority_buffer,
+ const HyphenationRule ** rule_buffer,
+ const char *to_match) const;
+};
+
+Hyphenate::HyphenationTree::HyphenationTree() :
+root(new HyphenationNode()), start_safe(1), end_safe(1) {}
+
+Hyphenate::HyphenationTree::~HyphenationTree() {
+ delete root;
+}
+
+void Hyphenate::HyphenationTree::insert(auto_ptr<HyphenationRule> pattern) {
+ /* Convert our key to lower case to ease matching. */
+ const std::string& upperCaseKey = pattern->getKey();
+ const size_t m = upperCaseKey.size();
+ std::string lowerCaseKey;
+
+ U8STRING_FOR_EACH_CHARACTER_DO_BEGIN(upperCaseKey, i, m, ch, 0x00FFFD);
+ ch = utf32ToLower(ch);
+ U8_ENCODE(ch, lowerCaseKey.push_back);
+ U8STRING_FOR_EACH_CHARACTER_DO_END();
+
+ root->insert(lowerCaseKey.c_str(), pattern);
+}
+
+void HyphenationNode::insert(const char* key_string,
+ auto_ptr<HyphenationRule> pattern)
+{
+ /* Is this the terminal node for that pattern? */
+ if (key_string[0] == 0) {
+ /* If we descended the tree all the way to the last letter, we can now
+ * write the pattern into this node. */
+
+ hyphenation_pattern.reset(pattern.release());
+ } else {
+ /* If not, however, we make sure that the branch for our letter exists
+ * and descend. */
+ char key = key_string[0];
+ /* Ensure presence of a branch for that letter. */
+ HyphenationNode *p = find(key);
+ if (!p) {
+ p = new HyphenationNode();
+ jump_table.insert(pair<char, HyphenationNode*>(key, p));
+ }
+ /* Go to the next letter and descend. */
+ p->insert(key_string + 1, pattern);
+ }
+}
+
+void Hyphenate::HyphenationNode::apply_patterns(
+ char *priority_buffer,
+ const HyphenationRule ** rule_buffer,
+ const char *to_match) const
+{
+ /* First of all, if we can descend further into the tree (that is,
+ * there is an input char left and there is a branch in the tree),
+ * do so. */
+ char key = to_match[0];
+
+ if (key != 0) {
+ const HyphenationNode *next = find(key);
+ if (next != NULL)
+ next->apply_patterns(priority_buffer, rule_buffer, to_match + 1);
+ }
+
+ /* Now, if we have a pattern at this point in the tree, it must be a good
+ * match. Apply the pattern. */
+ const HyphenationRule* hyp_pat = hyphenation_pattern.get();
+ if (hyp_pat != NULL)
+ for (int i = 0; hyp_pat->hasPriority(i); i++)
+ if (priority_buffer[i] < hyp_pat->priority(i)) {
+ rule_buffer[i] = (hyp_pat->priority(i) % 2 == 1) ? hyp_pat : NULL;
+ priority_buffer[i] = hyp_pat->priority(i);
+ }
+}
+
+auto_ptr<vector<const HyphenationRule*> > HyphenationTree::applyPatterns
+(const string &word) const
+{
+ return applyPatterns(word, string::npos);
+}
+
+auto_ptr<vector<const HyphenationRule*> > HyphenationTree::applyPatterns
+(const string &word, size_t stop_at) const
+{
+ /* Prepend and append a . to the string (word start and end), and convert
+ * all characters to lower case to ease matching. */
+ std::string w = ".";
+ {
+ const size_t m = word.size();
+
+ U8STRING_FOR_EACH_CHARACTER_DO_BEGIN(word, i, m, ch, 0x00FFFD);
+ ch = utf32ToLower(ch);
+ U8_ENCODE(ch, w.push_back);
+ U8STRING_FOR_EACH_CHARACTER_DO_END();
+ }
+ w += ".";
+
+ /* Vectors for priorities and rules. */
+ vector<char> pri(w.size() + 2, 0);
+ vector<const HyphenationRule*> rules(w.size() + 1, NULL);
+
+ /* For each suffix of the expanded word, search all matching prefixes.
+ * That way, each possible match is found. Note the pointer arithmetics
+ * in the first and second argument. */
+ for (unsigned int i = 0; i < w.size() - 1 && i <= stop_at; i++)
+ root->apply_patterns((&pri[i]), (&rules[i]), w.c_str() + i);
+
+ /* Copy the results to a shorter vector. */
+ auto_ptr<vector<const HyphenationRule*> > output_rules(
+ new vector<const HyphenationRule*>(word.size(), NULL));
+
+ /* We honor the safe areas at the start and end of each word here. */
+ /* Please note that the incongruence between start and end is due
+ * to the fact that hyphenation happens _before_ each character. */
+ unsigned int ind_start = 1, ind_end = w.size() - 1;
+ for (unsigned int skip = 0; skip < start_safe && ind_start < w.size(); ind_start++)
+ if ((w[ind_start] & 0xC0) != 0x80)
+ skip++;
+ for (unsigned int skip = 0; skip < end_safe && ind_end > 0; ind_end--)
+ if ((w[ind_end] & 0xC0) != 0x80)
+ skip++;
+
+ for (unsigned int i = ind_start; i <= ind_end; i++)
+ (*output_rules)[i - 1] = rules[i];
+ return output_rules;
+}
+
+void HyphenationTree::loadPatterns(istream &i) {
+ string pattern;
+ /* The input is a file with whitespace-separated words.
+ * The first numerical-only word we encountered denotes the safe start,
+ * the second the safe end area. */
+
+ char ch;
+ bool numeric = true;
+ int num_field = 0;
+ while (i.get(ch)) {
+ if (ch == '\n' || ch == '\r' || ch == '\t' || ch == ' ') {
+ /* The output operation. */
+ if (pattern.size() && numeric && num_field <= 1) {
+ ((num_field == 0) ? start_safe : end_safe) = atoi(pattern.c_str());
+ num_field++;
+ } else if (pattern.size()) {
+ insert(
+ auto_ptr<HyphenationRule>(new HyphenationRule(pattern)));
+ }
+
+ /* Reinitialize state. */
+ pattern.clear();
+ numeric = true;
+ } else {
+ /* This rule catches all other (mostly alpha, but probably UTF-8)
+ * characters. It normalizes the previous letter and then appends
+ * it to the pattern. */
+ pattern += ch;
+ if (ch < '0' || ch > '9') numeric = false;
+ }
+ }
+
+ if (pattern.size())
+ insert(auto_ptr<HyphenationRule>(new HyphenationRule(pattern)));
+}
+
diff --git a/src/HyphenationTree.h b/src/HyphenationTree.h
new file mode 100644
index 0000000..f1657b5
--- /dev/null
+++ b/src/HyphenationTree.h
@@ -0,0 +1,72 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+#ifndef HYPHENATION_TREE_H
+#define HYPHENATION_TREE_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include "HyphenationRule.h"
+
+namespace Hyphenate {
+ class HyphenationNode;
+ /**
+ * \class HyphenationTree
+ * \brief The root for a tree of HyphenationNodes.
+ */
+ class HyphenationTree {
+ private:
+ HyphenationNode* root;
+ unsigned int start_safe, end_safe;
+
+ public:
+ /** The constructor constructs an empty tree, which can be filled
+ * either by reading a whole file of patterns with
+ * <code>loadPatterns</code> or by <code>insert</code>. */
+ HyphenationTree();
+
+ ~HyphenationTree();
+
+ /** Read the istream while it is not empty, cutting it into words
+ * and constructing patterns from it. The first lone number
+ * encountered will be the safe start, the second the safe end. */
+ void loadPatterns(std::istream &source);
+
+ /** Insert a particular hyphenation pattern into the hyphenation tree.
+ * \param pattern The character pattern to match in the input word.
+ */
+ void insert(std::auto_ptr<HyphenationRule> pattern);
+
+ /** Apply all patterns for that hyphenation tree to the supplied
+ * string. Return an array with Hyphenation rules that should be
+ * applied before the addition of the next letter of the string.
+ * The pointers in that vector point into this tree. */
+ std::auto_ptr<std::vector<const HyphenationRule*> > applyPatterns
+ (const std::string &word) const;
+
+ /** Like applyPattern, but will only hyphenate up to the letter
+ * end_at. */
+ std::auto_ptr<std::vector<const HyphenationRule*> > applyPatterns
+ (const std::string &word, size_t end_at) const;
+ };
+}
+
+#endif
diff --git a/src/Hyphenator.cpp b/src/Hyphenator.cpp
new file mode 100644
index 0000000..4fe5e2c
--- /dev/null
+++ b/src/Hyphenator.cpp
@@ -0,0 +1,262 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+#include "Hyphenator.h"
+#include <iostream>
+#include <fstream>
+#include <vector>
+#include <map>
+#include <memory>
+#include <ctype.h>
+#include <stdlib.h>
+
+#include "HyphenationRule.h"
+#include "HyphenationTree.h"
+
+#include "UTF8Functions.h"
+
+#define UTF8_MAX 6
+
+using namespace std;
+using namespace Hyphenate;
+
+/** The hyphenation table parser. */
+static auto_ptr<HyphenationTree> read_hyphenation_table(const char *filename) {
+ ifstream i(filename, fstream::in);
+ auto_ptr<HyphenationTree> output(new HyphenationTree());
+ output->loadPatterns(i);
+
+ return output;
+}
+
+/** Build a hyphenator from the patterns in the file provided. */
+Hyphenate::Hyphenator::Hyphenator(const char *filename) {
+ dictionary = read_hyphenation_table(filename);
+}
+
+Hyphenator::~Hyphenator() {}
+
+std::string Hyphenator::hyphenate
+(const std::string &word, const std::string &hyphen)
+{
+ string result;
+ unsigned int word_start = -1;
+
+ /* Go through the input. All non-alpha characters are added to the
+ * output immediately, and words are hyphenated and then added. */
+ for (unsigned int i = 0; i < word.size(); i++) {
+ /* Skip UTF-8 tail bytes. */
+ if ((word[i] & 0xC0) == 0x80)
+ ;
+ else {
+ bool isalpha = utf32IsAlpha(utf8GetCharacter(word.c_str() + i));
+
+ if (word_start == string::npos && isalpha)
+ word_start = i;
+ else if (word_start != string::npos && !isalpha) {
+ result +=
+ hyphenate_word(word.substr(word_start, i - word_start), hyphen);
+ word_start = string::npos;
+ }
+ }
+
+ if (word_start == string::npos)
+ result += word[i];
+ }
+ if (word_start != string::npos)
+ result += hyphenate_word(word.substr(word_start), hyphen);
+
+ return result;
+}
+
+std::string Hyphenator::hyphenate_word
+(const std::string &word, const std::string &hyphen)
+{
+ auto_ptr<vector<const HyphenationRule*> > rules =
+ dictionary->applyPatterns(word);
+
+ /* Build our result string. Of course, we _could_ insert characters in
+ * w, but that would be highly inefficient. */
+ string result;
+
+ int acc_skip = 0;
+ for (unsigned int i = 0; i < word.size(); i++) {
+ if ((*rules)[i] != NULL)
+ acc_skip += (*rules)[i]->apply(result, hyphen);
+
+ if (acc_skip > 0)
+ acc_skip--;
+ else
+ result += word[i];
+ }
+
+ return result;
+}
+
+pair<std::string, std::string> Hyphenator::hyphenate_at
+(const std::string &src, const std::string &hyphen, size_t len)
+{
+ /* First of all, find the word which needs to be hyphenated. */
+ const char *cur = src.c_str();
+ for (unsigned int i = 0; i < len; i++)
+ cur = utf8GoToNextCharacter(cur);
+
+ const char *next = cur;
+ if (!utf32IsSpace(utf8GetCharacter(next)))
+ next = utf8GoToNextCharacter(next);
+ pair<string, string> result;
+
+ if (utf32IsSpace(utf8GetCharacter(next))) {
+ /* We are lucky: There is a space we can hyphenate at. */
+
+ /* We leave no spaces at the end of a line: */
+ while (utf32IsSpace(utf8GetCharacter(cur)))
+ cur = utf8GoToPrevCharacter(cur);
+ int len = cur - src.c_str() + 1;
+ result.first = src.substr(0, len);
+
+ /* Neither do we leave spaces at the beginning of the next. */
+ while (utf32IsSpace(utf8GetCharacter(next)))
+ next = utf8GoToNextCharacter(next);
+ result.second = src.substr(next - src.c_str());
+
+ } else {
+ /* We can hyphenate at hyphenation points in words or at spaces, whatever
+ * comes earlier. We will check all words here in the loop. */
+ const char *border = cur;
+ while (true) {
+ /* Find the start of a word first. */
+ bool in_word = utf32IsAlpha(utf8GetCharacter(cur));
+ const char *word_start = NULL;
+ while (cur > src.c_str()) {
+ cur = utf8GoToPrevCharacter(cur);
+ int ch = utf8GetCharacter(cur);
+
+ if (in_word && (!utf32IsAlpha(ch))) {
+ /* If we have a word, try hyphenating it.*/
+ word_start = utf8GoToNextCharacter(cur);
+ break;
+ } else if (utf32IsSpace(ch)) {
+ break;
+ } else if (!in_word && utf32IsAlpha(ch))
+ in_word = true;
+
+ if (cur == src.c_str() && in_word)
+ word_start = cur;
+ }
+
+ /* There are two reasons why we may have left the previous loop with-
+ * out result:
+ * Either because our word goes all the way to the first character,
+ * or because we found whitespace. */
+ /* In the first case, there is nothing really hyphenateable. */
+ if (word_start != NULL) {
+ /* We have the start of a word, now look for the character after
+ * the end. */
+ const char *word_end = word_start;
+ while (utf32IsAlpha(utf8GetCharacter(word_end)))
+ word_end = utf8GoToNextCharacter(word_end);
+
+ /* Build the substring consisting of the word. */
+ string word;
+ for (const char *i = word_start; i < word_end; i++)
+ word += *i;
+
+ /* Hyphenate the word. */
+ auto_ptr<vector<const HyphenationRule*> > rules =
+ dictionary->applyPatterns(word);
+
+ /* Determine the index of the latest hyphenation that will still
+ * fit. */
+ int latest_possible_hyphenation = -1;
+ int earliest_hyphenation = -1;
+ for (int i = 0; i < (int)rules->size(); i++)
+ if ((*rules)[i] != NULL) {
+ if (earliest_hyphenation == -1)
+ earliest_hyphenation = i;
+ if (word_start + i +
+ (*rules)[i]->spaceNeededPreHyphen() + hyphen.length()
+ <= border)
+ {
+ if (i > latest_possible_hyphenation) {
+ latest_possible_hyphenation = i;
+ }
+ } else
+ break;
+ }
+
+ bool have_space = false;
+ for (const char *i = src.c_str(); i <= word_start;
+ i = utf8GoToNextCharacter(i))
+ if (utf32IsSpace(utf8GetCharacter(i))) {
+ have_space = true;
+ break;
+ }
+ if (latest_possible_hyphenation == -1 && !have_space)
+ latest_possible_hyphenation = earliest_hyphenation;
+
+ /* Apply the best hyphenation, if any. */
+ if (latest_possible_hyphenation >= 0) {
+ int i = latest_possible_hyphenation;
+ result.first = src.substr(0, word_start - src.c_str() + i);
+ (*rules)[i]->apply_first(result.first, hyphen);
+ int skip = (*rules)[i]->apply_second(result.second);
+ const char *after_hyphen = word_start + i + skip;
+ result.second += string(after_hyphen);
+ break;
+ }
+ }
+
+ if (cur == src.c_str()) {
+ /* We cannot hyphenate at all, so leave the first block standing
+ * and move to its end. */
+ const char *eol = cur;
+ while (*eol != 0 && !utf32IsSpace(utf8GetCharacter(eol)))
+ eol = utf8GoToNextCharacter(eol);
+
+ result.first = src.substr(0, eol - src.c_str() + 1);
+ while (*eol != 0 && utf32IsSpace(utf8GetCharacter(eol)))
+ eol = utf8GoToNextCharacter(eol);
+ result.second = string(eol);
+ break;
+ } else if (utf32IsSpace(utf8GetCharacter(cur))) {
+ /* eol is the end of the previous line, bol the start of the
+ * next. */
+ const char *eol = cur, *bol = cur;
+ while (utf32IsSpace(utf8GetCharacter(eol)))
+ eol = utf8GoToPrevCharacter(eol);
+ while (utf32IsSpace(utf8GetCharacter(bol)))
+ bol = utf8GoToNextCharacter(bol);
+
+ result.first = src.substr(0, eol - src.c_str() + 1);
+ result.second = string(bol);
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+std::auto_ptr<std::vector<const HyphenationRule*> >
+Hyphenate::Hyphenator::applyHyphenationRules(const std::string& word)
+{
+ return dictionary->applyPatterns(word);
+}
diff --git a/src/Hyphenator.h b/src/Hyphenator.h
new file mode 100644
index 0000000..d1ef042
--- /dev/null
+++ b/src/Hyphenator.h
@@ -0,0 +1,103 @@
+/* libhyphenate: A TeX-like hyphenation algorithm.
+ * Copyright (C) 2007 Steve Wolter
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ * If you have any questions, feel free to contact me:
+ * http://swolter.sdf1.org
+ **/
+#ifndef HYPHENATE_HYPHENATOR_H
+#define HYPHENATE_HYPHENATOR_H
+
+#include <map>
+#include <string>
+#include <memory>
+#include <vector>
+
+namespace Hyphenate {
+ class HyphenationTree;
+ class HyphenationRule;
+
+ class Hyphenator {
+ private:
+ std::auto_ptr<HyphenationTree> dictionary;
+
+ std::string hyphenate_word
+ (const std::string &word, const std::string &hyphen);
+
+ public:
+ /** Build a hyphenator from the patterns in the file provided. */
+ Hyphenator(const char *filename);
+
+ /** Destructor. */
+ ~Hyphenator();
+
+ /** The actual workhorse. You'll want to call this function once
+ * for each word (NEW: or complete string, not only word. The library
+ * will do the word-splitting for you) you want hyphenated.
+ *
+ * Usage example:
+ * Hyphenate::Hyphenator hyphenator(Language("de-DE"));
+ * hyphenator.hyphenate("Schifffahrt");
+ *
+ * yields "Schiff-fahrt", while
+ *
+ * Hyphenate::Hyphenator hyphenator(Language("en"));
+ * hyphenator.hyphenate("example", "&shy;");
+ *
+ * yields "ex&shy;am&shy;ple".
+ *
+ * \param word A single UTF-8 encoded word to be hyphenated.
+ * \param hyphen The string to put at each possible
+ * hyphenation point. The default is an ASCII dash.
+ */
+ std::string hyphenate
+ (const std::string &word,
+ const std::string &hyphen = "-");
+
+ /** Find a single hyphenation point in the string so that the first
+ * part (including a hyphen) will be shorter or equal in length
+ * to the parameter len. If this is not possible, choose the shortest
+ * possible string.
+ *
+ * The first element is the result, the second element the rest of
+ * the string.
+ *
+ * Example: To format a piece of text to width 60, use the following
+ * loop:
+ * string rest = text;
+ * string result = "";
+ * while ( ! rest.empty() ) {
+ * pair<string,string> p = your_hyphenator.hyphenate_at(rest);
+ * result += p.first + "\n"
+ * rest = p.second;
+ * }
+ **/
+ std::pair<std::string, std::string> hyphenate_at
+ (const std::string &word,
+ const std::string &hyphen = "-",
+ size_t len = std::string::npos);
+
+ /** Just apply the hyphenation patterns to the word, but don't
+ * hyphenate anything.
+ *
+ * \returns A vector with the same size as the word with a non-NULL
+ * entry for every hyphenation point. */
+ std::auto_ptr<std::vector<const HyphenationRule*> >
+ applyHyphenationRules(const std::string& word);
+ };
+}
+
+#endif
diff --git a/src/UTF8Functions.cpp b/src/UTF8Functions.cpp
new file mode 100644
index 0000000..80e5717
--- /dev/null
+++ b/src/UTF8Functions.cpp
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <algorithm>
+#include <string>
+#include "UTF8Functions.h"
+
+// A helper function to read a character from utf8 string
+// s: the string
+// p [in,out]: the position
+// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
+int utf8ReadForward(const char* s, int& p) {
+ int ch = (unsigned char)s[p];
+ if (ch < 0x80){
+ if (ch) p++;
+ return ch;
+ } else if (ch < 0xC0){
+ // skip invalid characters
+ while (((unsigned char)s[p] & 0xC0) == 0x80) p++;
+ return -1;
+ } else if (ch < 0xE0){
+ int c2 = (unsigned char)s[++p];
+ if ((c2 & 0xC0) != 0x80) return -1;
+
+ ch = ((ch & 0x1F) << 6) | (c2 & 0x3F);
+ p++;
+ return ch;
+ } else if (ch < 0xF0){
+ int c2 = (unsigned char)s[++p];
+ if ((c2 & 0xC0) != 0x80) return -1;
+ int c3 = (unsigned char)s[++p];
+ if ((c3 & 0xC0) != 0x80) return -1;
+
+ ch = ((ch & 0xF) << 12) | ((c2 & 0x3F) << 6) | (c3 & 0x3F);
+ p++;
+ return ch;
+ } else if (ch < 0xF8){
+ int c2 = (unsigned char)s[++p];
+ if ((c2 & 0xC0) != 0x80) return -1;
+ int c3 = (unsigned char)s[++p];
+ if ((c3 & 0xC0) != 0x80) return -1;
+ int c4 = (unsigned char)s[++p];
+ if ((c4 & 0xC0) != 0x80) return -1;
+
+ ch = ((ch & 0x7) << 18) | ((c2 & 0x3F) << 12) | ((c3 & 0x3F) << 6) | (c4 & 0x3F);
+ if (ch >= 0x110000) ch = -1;
+ p++;
+ return ch;
+ } else {
+ p++;
+ return -1;
+ }
+}
+
+// A helper function to read a character backward from utf8 string (experimental)
+// s: the string
+// p [in,out]: the position
+// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
+int utf8ReadBackward(const char* s, int& p) {
+ if (p <= 0) return 0;
+
+ do {
+ p--;
+ } while (p > 0 && ((unsigned char)s[p] & 0xC0) == 0x80);
+
+ int tmp = p;
+ return utf8ReadForward(s, tmp);
+}
+
+const char* utf8GoToNextCharacter(const char* s) {
+ if (*s == 0) return s;
+ do {
+ s++;
+ } while (((unsigned char)(*s) & 0xC0) == 0x80);
+ return s;
+}
+
+const char* utf8GoToPrevCharacter(const char* s) {
+ do {
+ s--;
+ } while (((unsigned char)(*s) & 0xC0) == 0x80);
+ return s;
+}
+
+bool utf32IsSpace(int ch) {
+ //ripped from the output of glib-2.60.0
+ switch (ch) {
+ case 0x9: case 0xA: case 0xC: case 0xD: case 0x20: case 0xA0: case 0x1680:
+ case 0x2028: case 0x2029: case 0x202F: case 0x205F: case 0x3000:
+ return true;
+ default:
+ return (ch >= 0x2000 && ch <= 0x200A);
+ }
+}
+
+bool utf32IsAlpha(int ch) {
+ //ripped from the output of glib-2.60.0 (only a subset)
+
+ static const int ranges_65_247[] = {
+ 65, 26,
+ 97, 26,
+ 170, 1,
+ 181, 1,
+ 186, 1,
+ 192, 23,
+ 216, 31,
+ };
+ static const int ranges_248_751[] = {
+ 248, 458,
+ 710, 12,
+ 736, 5,
+ 748, 1,
+ 750, 1,
+ };
+ static const int ranges_880_1328[] = {
+ 880, 5,
+ 886, 2,
+ 890, 4,
+ 895, 1,
+ 902, 1,
+ 904, 3,
+ 908, 1,
+ 910, 20,
+ 931, 83,
+ 1015, 139,
+ 1162, 166,
+ };
+
+ // skip 0x0530 - 0x1CFF
+
+ static const int ranges_7424_8189[] = {
+ 7424, 192,
+ 7680, 278,
+ 7960, 6,
+ 7968, 38,
+ 8008, 6,
+ 8016, 8,
+ 8025, 1,
+ 8027, 1,
+ 8029, 1,
+ 8031, 31,
+ 8064, 53,
+ 8118, 7,
+ 8126, 1,
+ 8130, 3,
+ 8134, 7,
+ 8144, 4,
+ 8150, 6,
+ 8160, 13,
+ 8178, 3,
+ 8182, 7,
+ };
+
+ // skip 0x2000 - 0x10FFFF
+
+ const int *ranges = NULL;
+ int rangeSize = 0;
+
+#define RANGE(LPS,LPE) ranges_##LPS##_##LPE
+#define CHECK_RANGE(LPS,LPE) \
+ else if (ch < LPS) { \
+ } else if (ch < LPE) { \
+ ranges = RANGE(LPS,LPE); rangeSize = sizeof(RANGE(LPS,LPE)) / sizeof(RANGE(LPS,LPE)[0]); \
+ }
+
+ if (false) {}
+ CHECK_RANGE(65, 247)
+ CHECK_RANGE(248, 751)
+ CHECK_RANGE(880, 1328)
+ CHECK_RANGE(7424, 8189)
+
+ for (int i = 0; i < rangeSize; i += 2) {
+ const int lps = ranges[i];
+ const int lpe = lps + ranges[i + 1];
+ if (ch < lps) break;
+ else if (ch < lpe) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+int utf32ToLower(int ch) {
+ //ripped from the output of glib-2.60.0
+
+ static const int ranges_65_223[] = {
+ 65, 26, 32,
+ 192, 23, 32,
+ 216, 7, 32,
+ };
+ static const int ranges_304_504[] = {
+ 304, 1, -199,
+ 376, 1, -121,
+ 385, 1, 210,
+ 390, 1, 206,
+ 393, 2, 205,
+ 398, 1, 79,
+ 399, 1, 202,
+ 400, 1, 203,
+ 403, 1, 205,
+ 404, 1, 207,
+ 406, 1, 211,
+ 407, 1, 209,
+ 412, 1, 211,
+ 413, 1, 213,
+ 415, 1, 214,
+ 422, 1, 218,
+ 425, 1, 218,
+ 430, 1, 218,
+ 433, 2, 217,
+ 439, 1, 219,
+ 452, 1, 2,
+ 455, 1, 2,
+ 458, 1, 2,
+ 497, 1, 2,
+ 502, 1, -97,
+ 503, 1, -56,
+ };
+ static const int ranges_544_582[] = {
+ 544, 1, -130,
+ 570, 1, 10795,
+ 573, 1, -163,
+ 574, 1, 10792,
+ 579, 1, -195,
+ 580, 1, 69,
+ 581, 1, 71,
+ };
+ static const int ranges_895_1018[] = {
+ 895, 1, 116,
+ 902, 1, 38,
+ 904, 3, 37,
+ 908, 1, 64,
+ 910, 2, 63,
+ 913, 17, 32,
+ 931, 9, 32,
+ 975, 1, 8,
+ 1012, 1, -60,
+ 1017, 1, -7,
+ };
+ static const int ranges_1021_1367[] = {
+ 1021, 3, -130,
+ 1024, 16, 80,
+ 1040, 32, 32,
+ 1216, 1, 15,
+ 1329, 38, 48,
+ };
+ static const int ranges_4256_5110[] = {
+ 4256, 38, 7264,
+ 4295, 1, 7264,
+ 4301, 1, 7264,
+ 5024, 80, 38864,
+ 5104, 6, 8,
+ };
+ static const int ranges_7312_8499[] = {
+ 7312, 43, -3008,
+ 7357, 3, -3008,
+ 7838, 1, -7615,
+ 7944, 8, -8,
+ 7960, 6, -8,
+ 7976, 8, -8,
+ 7992, 8, -8,
+ 8008, 6, -8,
+ 8025, 1, -8,
+ 8027, 1, -8,
+ 8029, 1, -8,
+ 8031, 1, -8,
+ 8040, 8, -8,
+ 8072, 8, -8,
+ 8088, 8, -8,
+ 8104, 8, -8,
+ 8120, 2, -8,
+ 8122, 2, -74,
+ 8124, 1, -9,
+ 8136, 4, -86,
+ 8140, 1, -9,
+ 8152, 2, -8,
+ 8154, 2, -100,
+ 8168, 2, -8,
+ 8170, 2, -112,
+ 8172, 1, -7,
+ 8184, 2, -128,
+ 8186, 2, -126,
+ 8188, 1, -9,
+ 8486, 1, -7517,
+ 8490, 1, -8383,
+ 8491, 1, -8262,
+ 8498, 1, 28,
+ };
+ static const int ranges_11264_11392[] = {
+ 11264, 47, 48,
+ 11362, 1, -10743,
+ 11363, 1, -3814,
+ 11364, 1, -10727,
+ 11373, 1, -10780,
+ 11374, 1, -10749,
+ 11375, 1, -10783,
+ 11376, 1, -10782,
+ 11390, 2, -10815,
+ };
+ static const int ranges_42877_42932[] = {
+ 42877, 1, -35332,
+ 42893, 1, -42280,
+ 42922, 1, -42308,
+ 42923, 1, -42319,
+ 42924, 1, -42315,
+ 42925, 1, -42305,
+ 42926, 1, -42308,
+ 42928, 1, -42258,
+ 42929, 1, -42282,
+ 42930, 1, -42261,
+ 42931, 1, 928,
+ };
+ static const int ranges_65313_125218[] = {
+ 65313, 26, 32,
+ 66560, 40, 40,
+ 66736, 36, 40,
+ 68736, 51, 64,
+ 71840, 32, 32,
+ 93760, 32, 32,
+ 125184, 34, 34,
+ };
+
+ static const int ranges2_256_440[] = {
+ 256, 302,
+ 306, 310,
+ 313, 327,
+ 330, 374,
+ 377, 381,
+ 386, 388,
+ 391, 391,
+ 395, 395,
+ 401, 401,
+ 408, 408,
+ 416, 420,
+ 423, 423,
+ 428, 428,
+ 431, 431,
+ 435, 437,
+ 440, 440,
+ };
+ static const int ranges2_444_590[] = {
+ 444, 444,
+ 453, 453,
+ 456, 456,
+ 459, 475,
+ 478, 494,
+ 498, 500,
+ 504, 542,
+ 546, 562,
+ 571, 571,
+ 577, 577,
+ 582, 590,
+ };
+ static const int ranges2_880_1326[] = {
+ 880, 882,
+ 886, 886,
+ 984, 1006,
+ 1015, 1015,
+ 1018, 1018,
+ 1120, 1152,
+ 1162, 1214,
+ 1217, 1229,
+ 1232, 1326,
+ };
+ static const int ranges2_7680_11506[] = {
+ 7680, 7828,
+ 7840, 7934,
+ 8579, 8579,
+ 11360, 11360,
+ 11367, 11371,
+ 11378, 11378,
+ 11381, 11381,
+ 11392, 11490,
+ 11499, 11501,
+ 11506, 11506,
+ };
+ static const int ranges2_42560_42936[] = {
+ 42560, 42604,
+ 42624, 42650,
+ 42786, 42798,
+ 42802, 42862,
+ 42873, 42875,
+ 42878, 42886,
+ 42891, 42891,
+ 42896, 42898,
+ 42902, 42920,
+ 42932, 42936,
+ };
+
+ const int *ranges = NULL, *ranges2 = NULL;
+ int rangeSize = 0, range2Size = 0;
+
+#define RANGE(LPS,LPE) ranges_##LPS##_##LPE
+#define CHECK_RANGE(LPS,LPE) \
+ else if (ch < LPS) { \
+ } else if (ch < LPE) { \
+ ranges = RANGE(LPS,LPE); rangeSize = sizeof(RANGE(LPS,LPE)) / sizeof(RANGE(LPS,LPE)[0]); \
+ }
+
+#define RANGE2(LPS,LPE) ranges2_##LPS##_##LPE
+#define CHECK_RANGE2(LPS,LPE) \
+ else if (ch < LPS) { \
+ } else if (ch <= LPE) { \
+ ranges2 = RANGE2(LPS,LPE); range2Size = sizeof(RANGE2(LPS,LPE)) / sizeof(RANGE2(LPS,LPE)[0]); \
+ }
+
+ if (false) {}
+ CHECK_RANGE(65, 223)
+ CHECK_RANGE(304, 504)
+ CHECK_RANGE(544, 582)
+ CHECK_RANGE(895, 1018)
+ CHECK_RANGE(1021, 1367)
+ CHECK_RANGE(4256, 5110)
+ CHECK_RANGE(7312, 8499)
+ CHECK_RANGE(11264, 11392)
+ CHECK_RANGE(42877, 42932)
+ CHECK_RANGE(65313, 125218)
+
+ for (int i = 0; i < rangeSize; i += 3) {
+ const int lps = ranges[i];
+ const int lpe = lps + ranges[i + 1];
+ if (ch < lps) break;
+ else if (ch < lpe) {
+ return ch + ranges[i + 2];
+ }
+ }
+
+ if (false) {}
+ CHECK_RANGE2(256, 440)
+ CHECK_RANGE2(444, 590)
+ CHECK_RANGE2(880, 1326)
+ CHECK_RANGE2(7680, 11506)
+ CHECK_RANGE2(42560, 42936)
+
+ for (int i = 0; i < range2Size; i += 2) {
+ const int lps = ranges2[i];
+ const int lpe = ranges2[i + 1];
+ if (ch < lps) break;
+ else if (ch <= lpe) {
+ if (((ch - lps) & 0x1) == 0) return ch + 1;
+ else break;
+ }
+ }
+
+#undef RANGE
+#undef RANGE2
+#undef CHECK_RANGE
+#undef CHECK_RANGE2
+
+ return ch;
+}
diff --git a/src/UTF8Functions.h b/src/UTF8Functions.h
new file mode 100644
index 0000000..2a932b5
--- /dev/null
+++ b/src/UTF8Functions.h
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2019 Me and My Shadow
+ *
+ * This file is part of Me and My Shadow.
+ *
+ * Me and My Shadow 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Me and My Shadow 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Me and My Shadow. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UTF8FUNCTIONS_H
+#define UTF8FUNCTIONS_H
+
+// A helper function to read a character from utf8 string and advance the pointer
+// s: the string
+// p [in,out]: the position
+// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
+int utf8ReadForward(const char* s, int& p);
+
+// A helper function to read a character backward from utf8 string and advance the pointer (experimental)
+// s: the string
+// p [in,out]: the position
+// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
+int utf8ReadBackward(const char* s, int& p);
+
+// A helper function to read the first character from utf8 string
+// s: the string
+// return value: the character readed, in utf32 format, 0 means end of string, -1 means error
+// NOTE: Consider utf8ReadForward() instead if you want to read multiple characters
+inline int utf8GetCharacter(const char* s) {
+ int tmp = 0;
+ return utf8ReadForward(s, tmp);
+}
+
+// A helper function to advance the pointer in a utf8 string to next character
+// s: the pointer
+// return value: the new pointer
+// WARNING: there is no sanity check!
+const char* utf8GoToNextCharacter(const char* s);
+
+// A helper function to advance the pointer in a utf8 string to previous character
+// s: the pointer
+// return value: the new pointer
+// WARNING: there is no sanity check!
+const char* utf8GoToPrevCharacter(const char* s);
+
+bool utf32IsSpace(int ch);
+bool utf32IsAlpha(int ch);
+int utf32ToLower(int ch);
+
+#define U8STRING_FOR_EACH_CHARACTER_DO_BEGIN(STR,I,M,CH,INVALID_CH) \
+ for(size_t I=0;I<M;I++){ \
+ int CH=(unsigned char)STR[I]; \
+ if(CH<0x80){ \
+ }else if(CH<0xC0){ \
+ CH=INVALID_CH; \
+ }else if(CH<0xE0){ \
+ if(I+1>=M) CH=INVALID_CH; \
+ else{ \
+ int c2=(unsigned char)STR[I+1]; \
+ if((c2&0xC0)!=0x80) CH=INVALID_CH; \
+ else{ \
+ CH=((CH & 0x1F)<<6) | (c2 & 0x3F); \
+ I++; \
+ } \
+ } \
+ }else if(CH<0xF0){ \
+ if(I+2>=M) CH=INVALID_CH; \
+ else{ \
+ int c2=(unsigned char)STR[I+1]; \
+ int c3=(unsigned char)STR[I+2]; \
+ if((c2&0xC0)!=0x80 || (c3&0xC0)!=0x80) CH=INVALID_CH; \
+ else{ \
+ CH=((CH & 0xF)<<12) | ((c2 & 0x3F)<<6) | (c3 & 0x3F); \
+ I+=2; \
+ } \
+ } \
+ }else if(CH<0xF8){ \
+ if(I+3>=M) CH=INVALID_CH; \
+ else{ \
+ int c2=(unsigned char)STR[I+1]; \
+ int c3=(unsigned char)STR[I+2]; \
+ int c4=(unsigned char)STR[I+3]; \
+ if((c2&0xC0)!=0x80 || (c3&0xC0)!=0x80 || (c4&0xC0)!=0x80) CH=INVALID_CH; \
+ else{ \
+ CH=((CH & 0x7)<<18) | ((c2 & 0x3F)<<12) | ((c3 & 0x3F)<<6) | (c4 & 0x3F); \
+ if(CH>=0x110000) CH=INVALID_CH; \
+ else I+=3; \
+ } \
+ } \
+ }else{ \
+ CH=INVALID_CH; \
+ }
+
+#define U8STRING_FOR_EACH_CHARACTER_DO_END() }
+
+#define U8_ENCODE(CH,OPERATION) \
+ if(CH<0x80){ \
+ OPERATION(CH); \
+ }else if(CH<0x800){ \
+ OPERATION(0xC0 | (CH>>6)); \
+ OPERATION(0x80 | (CH & 0x3F)); \
+ }else if(CH<0x10000){ \
+ OPERATION(0xE0 | (CH>>12)); \
+ OPERATION(0x80 | ((CH>>6) & 0x3F)); \
+ OPERATION(0x80 | (CH & 0x3F)); \
+ }else{ \
+ OPERATION(0xF0 | (CH>>18)); \
+ OPERATION(0x80 | ((CH>>12) & 0x3F)); \
+ OPERATION(0x80 | ((CH>>6) & 0x3F)); \
+ OPERATION(0x80 | (CH & 0x3F)); \
+ }
+
+#define U16STRING_FOR_EACH_CHARACTER_DO_BEGIN(STR,I,M,CH,INVALID_CH) \
+ for(size_t I=0;I<M;I++){ \
+ int CH=(unsigned short)(STR[I]); \
+ if(CH<0xD800){ \
+ }else if(CH<0xDC00){ \
+ /* lead surrogate */ \
+ I++; \
+ if(I>=M) CH=INVALID_CH; \
+ else{ \
+ int c2=(unsigned short)STR[I]; \
+ if(CH>=0xDC00 && CH<0xE000){ \
+ /* trail surrogate */ \
+ CH=0x10000 + (((CH & 0x3FF)<<10) | (c2 & 0x3FF)); \
+ }else{ \
+ /* invalid */ \
+ CH=INVALID_CH; \
+ I--; \
+ } \
+ } \
+ }else if(CH<0xE000){ \
+ /* invalid trail surrogate */ \
+ CH=INVALID_CH; \
+ }
+
+#define U16STRING_FOR_EACH_CHARACTER_DO_END() }
+
+#define U16_ENCODE(CH,OPERATION) \
+ if(CH<0x10000){ \
+ OPERATION(CH); \
+ }else{ \
+ OPERATION(0xD800 | ((CH-0x10000)>>10)); \
+ OPERATION(0xDC00 | (CH & 0x3FF)); \
+ }
+
+#endif

File Metadata

Mime Type
text/x-diff
Expires
Sat, May 9, 7:59 PM (6 d, 20 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
62835
Default Alt Text
(195 KB)

Event Timeline