Extract calltip logic from the edit ctrl to libaegisub

This commit is contained in:
Thomas Goyne 2012-10-31 19:49:29 -07:00
parent e4d6b8661b
commit 238356406f
8 changed files with 266 additions and 264 deletions

View File

@ -92,6 +92,7 @@
<ClInclude Include="$(SrcDir)include\libaegisub\background_runner.h" />
<ClInclude Include="$(SrcDir)include\libaegisub\color.h" />
<ClInclude Include="$(SrcDir)include\libaegisub\spellchecker.h" />
<ClInclude Include="..\..\libaegisub\include\libaegisub\calltip_provider.h" />
<ClInclude Include="..\..\libaegisub\include\libaegisub\of_type_adaptor.h" />
</ItemGroup>
<ItemGroup>
@ -123,6 +124,7 @@
<ClCompile Include="$(SrcDir)ass\dialogue_parser.cpp" />
<ClCompile Include="$(SrcDir)common\color.cpp" />
<ClCompile Include="$(SrcDir)common\parser.cpp" />
<ClCompile Include="..\..\libaegisub\common\calltip_provider.cpp" />
<ClCompile Include="..\..\libaegisub\common\io.cpp" />
</ItemGroup>
<ItemGroup>

View File

@ -137,6 +137,9 @@
<ClInclude Include="..\..\libaegisub\include\libaegisub\of_type_adaptor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\libaegisub\include\libaegisub\calltip_provider.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="$(SrcDir)windows\lagi_pre.cpp">
@ -220,6 +223,9 @@
<ClCompile Include="..\..\libaegisub\common\io.cpp">
<Filter>Source Files\Common</Filter>
</ClCompile>
<ClCompile Include="..\..\libaegisub\common\calltip_provider.cpp">
<Filter>Source Files\Common</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="$(SrcDir)include\libaegisub\charsets.def">

View File

@ -20,6 +20,7 @@ SRC += \
common/cajun/elements.cpp \
common/cajun/reader.cpp \
common/cajun/writer.cpp \
common/calltip_provider.cpp \
common/charset.cpp \
common/charset_6937.cpp \
common/charset_conv.cpp \

View File

@ -59,7 +59,8 @@ class SyntaxHighlighter {
char *outptr = (char *)&chr;
size_t outlen = sizeof chr;
if (iconv(utf8_to_utf32, &inptr, &inlen, &outptr, &outlen) != 1)
iconv(utf8_to_utf32, &inptr, &inlen, &outptr, &outlen);
if (outlen != 0)
return 0;
char_len = len - inlen;
@ -105,7 +106,7 @@ public:
SyntaxHighlighter(std::string const& text, agi::SpellChecker *spellchecker)
: text(text)
, spellchecker(spellchecker)
, utf8_to_utf32(iconv_open("utf-32", "utf-8"), iconv_close)
, utf8_to_utf32(iconv_open("utf-32le", "utf-8"), iconv_close)
{ }
TokenVec Highlight(TokenVec const& tokens, bool template_line) {

View File

@ -0,0 +1,186 @@
// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#include "../config.h"
#include "libaegisub/calltip_provider.h"
#include "libaegisub/ass/dialogue_parser.h"
#include <boost/range.hpp>
namespace {
struct proto_lit {
const char *name;
bool has_parens;
const char *args;
};
proto_lit calltip_protos[] = {
{ "move", true, "X1\0Y1\0X2\0Y2\0" },
{ "move", true, "X1\0Y1\0X2\0Y2\0Start Time\0End Time\0" },
{ "fn", false, "Font Name\0" },
{ "bord", false, "Width\0" },
{ "xbord", false, "Width\0" },
{ "ybord", false, "Width\0" },
{ "shad", false, "Depth\0" },
{ "xshad", false, "Depth\0" },
{ "yshad", false, "Depth\0" },
{ "be", false, "Strength\0" },
{ "blur", false, "Strength\0" },
{ "fscx", false, "Scale\0" },
{ "fscy", false, "Scale\0" },
{ "fsp", false, "Spacing\0" },
{ "fs", false, "Font Size\0" },
{ "fe", false, "Encoding\0" },
{ "frx", false, "Angle\0" },
{ "fry", false, "Angle\0" },
{ "frz", false, "Angle\0" },
{ "fr", false, "Angle\0" },
{ "pbo", false, "Offset\0" },
{ "clip", true, "Command\0" },
{ "clip", true, "Scale\0Command\0" },
{ "clip", true, "X1\0Y1\0X2\0Y2\0" },
{ "iclip", true, "Command\0" },
{ "iclip", true, "Scale\0Command\0" },
{ "iclip", true, "X1\0Y1\0X2\0Y2\0" },
{ "t", true, "Acceleration\0Tags\0" },
{ "t", true, "Start Time\0End Time\0Tags\0" },
{ "t", true, "Start Time\0End Time\0Acceleration\0Tags\0" },
{ "pos", true, "X\0Y\0" },
{ "p", false, "Exponent\0" },
{ "org", true, "X\0Y\0" },
{ "fade", true, "Start Alpha\0Middle Alpha\0End Alpha\0Start In\0End In\0Start Out\0End Out\0" },
{ "fad", true, "Start Time\0End Time\0" },
{ "c", false, "Colour\0" },
{ "1c", false, "Colour\0" },
{ "2c", false, "Colour\0" },
{ "3c", false, "Colour\0" },
{ "4c", false, "Colour\0" },
{ "alpha", false, "Alpha\0" },
{ "1a", false, "Alpha\0" },
{ "2a", false, "Alpha\0" },
{ "3a", false, "Alpha\0" },
{ "4a", false, "Alpha\0" },
{ "an", false, "Alignment\0" },
{ "a", false, "Alignment\0" },
{ "b", false, "Weight\0" },
{ "i", false, "1/0\0" },
{ "u", false, "1/0\0" },
{ "s", false, "1/0\0" },
{ "kf", false, "Duration\0" },
{ "ko", false, "Duration\0" },
{ "k", false, "Duration\0" },
{ "K", false, "Duration\0" },
{ "q", false, "Wrap Style\0" },
{ "r", false, "Style\0" },
{ "fax", false, "Factor\0" },
{ "fay", false, "Factor\0" }
};
}
namespace agi {
CalltipProvider::CalltipProvider() {
for (auto proto : calltip_protos) {
CalltipProto p;
std::string tag_name = proto.name;
p.text = '\\' + tag_name;
if (proto.has_parens)
p.text += '(';
for (const char *arg = proto.args; *arg; ) {
size_t start = p.text.size();
p.text += arg;
size_t end = p.text.size();
if (proto.has_parens)
p.text += ',';
arg += end - start + 1;
p.args.emplace_back(start, end);
}
// replace trailing comma
if (proto.has_parens)
p.text.back() = ')';
protos.emplace(std::move(tag_name), std::move(p));
}
}
Calltip CalltipProvider::GetCalltip(std::vector<ass::DialogueToken> const& tokens, std::string const& text, size_t pos) {
namespace dt = ass::DialogueTokenType;
Calltip ret = { "", 0, 0, 0 };
size_t idx = 0;
size_t tag_name_idx = 0;
size_t commas = 0;
for (; idx < tokens.size() && pos >= tokens[idx].length; ++idx) {
switch (tokens[idx].type) {
case dt::COMMENT:
case dt::OVR_END:
tag_name_idx = 0;
break;
case dt::TAG_NAME:
tag_name_idx = idx;
commas = 0;
break;
case dt::ARG_SEP:
++commas;
break;
default: break;
}
pos -= tokens[idx].length;
}
// Either didn't hit a tag or the override block ended before reaching the
// current position
if (tag_name_idx == 0)
return ret;
// Find the prototype for this tag
size_t tag_name_start = 0;
for (size_t i = 0; i < tag_name_idx; ++i)
tag_name_start += tokens[i].length;
auto it = protos.equal_range(text.substr(tag_name_start, tokens[tag_name_idx].length));
// If there's multiple overloads, check how many total arguments we have
// and pick the one with the least args >= current arg count
if (distance(it.first, it.second) > 1) {
size_t args = commas + 1;
for (size_t i = idx; i < tokens.size(); ++i) {
int type = tokens[i].type;
if (type == dt::ARG_SEP)
++args;
else if (type != dt::ARG && type != dt::WHITESPACE)
break;
}
while (it.first != it.second && args > it.first->second.args.size())
++it.first;
}
// Unknown tag or too many arguments
if (it.first == it.second || it.first->second.args.size() <= commas)
return ret;
ret.text = it.first->second.text;
ret.highlight_start = it.first->second.args[commas].first;
ret.highlight_end = it.first->second.args[commas].second;
ret.tag_position = tag_name_start;
return ret;
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2012, Thomas Goyne <plorkyeran@aegisub.org>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
// ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
// OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
//
// Aegisub Project http://www.aegisub.org/
#ifndef LAGI_PRE
#include <map>
#include <string>
#include <vector>
#endif
namespace agi {
namespace ass { struct DialogueToken; }
struct Calltip {
std::string text; ///< Text of the calltip
size_t highlight_start; ///< Start index of the current parameter in text
size_t highlight_end; ///< End index of the current parameter in text
size_t tag_position; ///< Start index of the tag in the input line
};
class CalltipProvider {
struct CalltipProto {
std::string text;
std::vector<std::pair<size_t, size_t>> args;
};
std::multimap<std::string, CalltipProto> protos;
public:
CalltipProvider();
/// Get the calltip to show for the given cursor position in the text
Calltip GetCalltip(std::vector<ass::DialogueToken> const& tokens, std::string const& text, size_t pos);
};
}

View File

@ -57,6 +57,7 @@
#include "utils.h"
#include <libaegisub/ass/dialogue_parser.h>
#include <libaegisub/calltip_provider.h>
#include <libaegisub/spellchecker.h>
/// Event ids
@ -82,6 +83,7 @@ SubsTextEditCtrl::SubsTextEditCtrl(wxWindow* parent, wxSize wsize, long style, a
: ScintillaTextCtrl(parent, -1, "", wxDefaultPosition, wsize, style)
, spellchecker(SpellCheckerFactory::GetSpellChecker())
, context(context)
, calltip_position(0)
{
// Set properties
SetWrapMode(wxSTC_WRAP_WORD);
@ -101,68 +103,6 @@ SubsTextEditCtrl::SubsTextEditCtrl(wxWindow* parent, wxSize wsize, long style, a
CmdKeyClear('T',wxSTC_SCMOD_CTRL | wxSTC_SCMOD_SHIFT);
CmdKeyClear('U',wxSTC_SCMOD_CTRL);
// Prototypes for call tips
tipProtoN = -1;
proto.Add("move(x1,y1,x2,y2)");
proto.Add("move(x1,y1,x2,y2,startTime,endTime)");
proto.Add("fn;FontName");
proto.Add("bord;Width");
proto.Add("xbord;Width");
proto.Add("ybord;Width");
proto.Add("shad;Depth");
proto.Add("xshad;Depth");
proto.Add("yshad;Depth");
proto.Add("be;Strength");
proto.Add("blur;Strength");
proto.Add("fscx;Scale");
proto.Add("fscy;Scale");
proto.Add("fsp;Spacing");
proto.Add("fs;FontSize");
proto.Add("fe;Encoding");
proto.Add("frx;Angle");
proto.Add("fry;Angle");
proto.Add("frz;Angle");
proto.Add("fr;Angle");
proto.Add("pbo;Offset");
proto.Add("clip(command)");
proto.Add("clip(scale,command)");
proto.Add("clip(x1,y1,x2,y2)");
proto.Add("iclip(command)");
proto.Add("iclip(scale,command)");
proto.Add("iclip(x1,y1,x2,y2)");
proto.Add("t(acceleration,tags)");
proto.Add("t(startTime,endTime,tags)");
proto.Add("t(startTime,endTime,acceleration,tags)");
proto.Add("pos(x,y)");
proto.Add("p;Exponent");
proto.Add("org(x,y)");
proto.Add("fade(startAlpha,middleAlpha,endAlpha,startIn,endIn,startOut,endOut)");
proto.Add("fad(startTime,endTime)");
proto.Add("c;Colour");
proto.Add("1c;Colour");
proto.Add("2c;Colour");
proto.Add("3c;Colour");
proto.Add("4c;Colour");
proto.Add("alpha;Alpha");
proto.Add("1a;Alpha");
proto.Add("2a;Alpha");
proto.Add("3a;Alpha");
proto.Add("4a;Alpha");
proto.Add("an;Alignment");
proto.Add("a;Alignment");
proto.Add("b;Weight");
proto.Add("i;1/0");
proto.Add("u;1/0");
proto.Add("s;1/0");
proto.Add("kf;Duration");
proto.Add("ko;Duration");
proto.Add("k;Duration");
proto.Add("K;Duration");
proto.Add("q;WrapStyle");
proto.Add("r;Style");
proto.Add("fax;Factor");
proto.Add("fay;Factor");
using std::bind;
Bind(wxEVT_CHAR_HOOK, &SubsTextEditCtrl::OnKeyDown, this);
@ -292,211 +232,27 @@ void SubsTextEditCtrl::UpdateCallTip(wxStyledTextEvent &) {
if (!OPT_GET("App/Call Tips")->GetBool()) return;
// Get position and text
const unsigned int pos = GetReverseUnicodePosition(GetCurrentPos());
wxString text = GetText();
if (!calltip_provider)
calltip_provider.reset(new agi::CalltipProvider);
// Find the start and end of current tag
wxChar prevChar = 0;
int depth = 0;
int inDepth = 0;
int tagStart = -1;
int tagEnd = -1;
for (unsigned int i=0;i<text.Length()+1;i++) {
wxChar curChar = i < text.size() ? text[i] : 0;
std::string text = GetTextRaw().data();
auto tokens = agi::ass::TokenizeDialogueBody(text);
int pos = GetCurrentPos();
// Change depth
if (curChar == '{') {
depth++;
continue;
}
if (curChar == '}') {
depth--;
if (i >= pos && depth == 0) {
tagEnd = i-1;
break;
}
continue;
}
agi::Calltip new_calltip = calltip_provider->GetCalltip(tokens, text, GetCurrentPos());
// Outside
if (depth == 0) {
tagStart = -1;
if (i == pos) break;
continue;
}
// Inside overrides
if (depth == 1) {
// Inner depth
if (tagStart != -1) {
if (curChar == '(') inDepth++;
else if (curChar == ')') inDepth--;
}
// Not inside parenthesis
if (inDepth == 0) {
if (prevChar == '\\') {
// Found start
if (i <= pos) tagStart = i;
// Found end
else {
tagEnd = i-2;
break;
}
}
}
}
// Previous character
prevChar = curChar;
}
// Calculate length
int len;
if (tagEnd != -1) len = tagEnd - tagStart + 1;
else len = text.Length() - tagStart;
// No tag available
int textLen = text.Length();
unsigned int posInTag = pos - tagStart;
if (tagStart+len > textLen || len <= 0 || tagStart < 0) {
if (new_calltip.text.empty()) {
CallTipCancel();
return;
}
// Current tag
wxString tag = text.Mid(tagStart,len);
if (!CallTipActive() || calltip_position != new_calltip.tag_position || calltip_text != new_calltip.text)
CallTipShow(new_calltip.tag_position, to_wx(new_calltip.text));
// Metrics in tag
int tagCommas = 0;
int tagParenthesis = 0;
int parN = 0;
int parPos = -1;
bool gotName = false;
wxString tagName = tag;
for (unsigned int i=0;i<tag.Length();i++) {
wxChar curChar = tag[i];
bool isEnd = false;
calltip_position = new_calltip.tag_position;
calltip_text = new_calltip.text;
// Commas
if (curChar == ',') {
tagCommas++;
parN++;
}
// Parenthesis
else if (curChar == '(') {
tagParenthesis++;
parN++;
}
else if (curChar == ')') {
tagParenthesis++;
parN++;
isEnd = true;
}
// Tag name
if (parN == 1 && !gotName) {
tagName = tag.Left(i);
gotName = true;
}
// Parameter it's on
if (i == posInTag) {
parPos = parN;
if (curChar == ',' || curChar == '(' || curChar == ')') {
parPos--;
}
}
else if (isEnd) {
parN = 1000;
break;
}
}
if (parPos == -1) parPos = parN;
// Tag name
if (tagName.IsEmpty()) {
CallTipCancel();
return;
}
// Find matching prototype
wxString useProto;
wxString cleanProto;
wxString protoName;
int protoN = 0;
bool semiProto = false;
for (unsigned int i=0;i<proto.Count();i++) {
// Get prototype name
int div = proto[i].Find(';');
if (div != wxNOT_FOUND) protoName = proto[i].Left(div);
else {
div = proto[i].Find('(');
protoName = proto[i].Left(div);
}
// Fix name
semiProto = false;
cleanProto = proto[i];
if (cleanProto.Freq(';') > 0) {
cleanProto.Replace(";","");
semiProto = true;
}
// Prototype match
wxString temp;
if (semiProto) temp = tagName.Left(protoName.Length());
else temp = tagName;
if (protoName == temp) {
// Parameter count match
if (proto[i].Freq(',') >= tagCommas) {
// Found
useProto = proto[i];
protoN = i;
break;
}
}
}
// No matching prototype
if (useProto.IsEmpty()) {
CallTipCancel();
return;
}
// Parameter number for tags without "(),"
if (semiProto && parPos == 0 && posInTag >= protoName.Length()) parPos = 1;
// Highlight start/end
int highStart = useProto.Length();
int highEnd = -1;
parN = 0;
int delta = 0;
for (unsigned int i=0;i<useProto.Length();i++) {
wxChar curChar = useProto[i];
if (i == 0 || curChar == ',' || curChar == ';' || curChar == '(' || curChar == ')') {
if (curChar == ';') delta++;
if (parN == parPos) highStart = i+1-delta;
else if (parN == parPos+1) highEnd = i;
parN++;
}
}
if (highStart <= 1) highStart = 0;
if (highEnd == -1) highEnd = useProto.Length();
// Calltip is over
if (highStart == (signed) useProto.Length()) {
CallTipCancel();
return;
}
// Show calltip
if (!CallTipActive() || tipProtoN != protoN) CallTipShow(GetUnicodePosition(tagStart),cleanProto);
tipProtoN = protoN;
CallTipSetHighlight(highStart,highEnd);
CallTipSetHighlight(new_calltip.highlight_start, new_calltip.highlight_end);
}
void SubsTextEditCtrl::SetTextTo(wxString text) {

View File

@ -44,6 +44,7 @@
class SubsEditBox;
class Thesaurus;
namespace agi {
class CalltipProvider;
class SpellChecker;
struct Context;
}
@ -57,6 +58,8 @@ class SubsTextEditCtrl : public ScintillaTextCtrl {
/// Backend thesaurus to use
agi::scoped_ptr<Thesaurus> thesaurus;
agi::scoped_ptr<agi::CalltipProvider> calltip_provider;
/// Project context, for splitting lines
agi::Context *context;
@ -72,11 +75,12 @@ class SubsTextEditCtrl : public ScintillaTextCtrl {
/// Thesaurus suggestions for the last right-clicked word
std::vector<std::string> thesSugs;
/// Calltip prototypes
wxArrayString proto;
/// Text of the currently shown calltip, to avoid flickering from
/// pointlessly reshowing the current tip
std::string calltip_text;
/// Last show calltip, to avoid flickering the currently active one
int tipProtoN;
/// Position of the currently show calltip
size_t calltip_position;
void OnContextMenu(wxContextMenuEvent &);
void OnAddToDictionary(wxCommandEvent &event);