Continuing auto3 removal/auto4 merge

Originally committed to SVN as r644.
This commit is contained in:
Niels Martin Hansen 2006-12-28 21:18:35 +00:00
parent fbb881f823
commit b39451823e
44 changed files with 4068 additions and 1455 deletions

View file

@ -0,0 +1,15 @@
-- Automation 4 test file
-- Create a Macro feature, that displays some text
script_name = "Automation 4 test 1"
script_description = "Hello World in Automation 4/Lua"
script_author = "Niels Martin Hansen"
script_version = "1"
function macro_test1(subtitles, selected_lines, active_line)
--aegisub.debug.out(3, "Hello World from %s", "Automation 4/Lua")
aegisub.debug.out("Hello Automation 4 World!")
end
aegisub.register_macro("Hello", "Shows a message", "tools", macro_test1)

View file

@ -0,0 +1,50 @@
-- Automation 4 test file
-- Create a Macro feature, that displays some text
script_name = "Automation 4 test 2"
script_description = "Some additional non-hello-world tests"
script_author = "Niels Martin Hansen"
script_version = "2"
function macro_test2(subtitles, selected_lines, active_line)
aegisub.debug.out(subtitles.n .. " and " .. #subtitles .. " should be the same value")
aegisub.debug.out(subtitles[selected_lines[1]].raw)
end
function dumper(subtitles, selected_lines, active_line)
for i = 1, #subtitles do
local l = subtitles[i]
local s = l.raw .. "\n"
s = s .. l.class .. "\n"
if l.class == "comment" then
s = s .. "text: " .. l.text .. "\n"
elseif l.class == "info" then
s = s .. string.format("key: %s\nvalue:%s\n", l.key, l.value)
elseif l.class == "format" then
-- skip
elseif l.class == "style" then
s = s .. string.format("name: %s\nfont: %s %d\ncolors: %s %s %s %s\n", l.name, l.fontname, l.fontsize, l.color1, l.color2, l.color3, l.color4)
elseif l.class == "dialogue" then
s = s .. string.format("layer: %d\nstyle: %s\ntext: %s\n", l.layer, l.style, l.text)
end
aegisub.debug.out(s)
end
end
function inserttest(subtitles, selected_lines, active_line)
local lid = selected_lines[1]
subtitles[-lid] = subtitles[lid]
subtitles[0] = subtitles[lid]
local l = subtitles[lid]
l.text = "A4 was here!"
subtitles[lid] = l
aegisub.set_undo_point("Insert Stuff")
end
aegisub.register_macro("File line count", "Count the number of lines in the ASS file", "tools", macro_test2, nil)
aegisub.register_macro("Dump", "Dumps info on every line in the file", "tools", dumper, nil)
aegisub.register_macro("Insert stuff", "Inserts some lines near the active line", "edit", inserttest, nil)

View file

@ -0,0 +1,33 @@
-- Automation 4 test file
-- Create a Macro feature, that displays some text
script_name = "Automation 4 test 3"
script_description = "Test parse_karaoke_data"
script_author = "Niels Martin Hansen"
script_version = "1"
function copy_table(tab)
local res = {}
for key, val in pairs(tab) do
res[key] = val
end
return res
end
function karatest(subtitles, selected_lines, active_line)
for i = #selected_lines, 1, -1 do
local ri = selected_lines[i]
local l = subtitles[ri]
local k = aegisub.parse_karaoke_data(l)
for j = 1, #k do
local nl = copy_table(l)
l.text = string.format("{\\t(%d,%d,\\fscx50)}%s", k[j].start_time, k[j].end_time, k[j].text_stripped)
subtitles.insert(ri+j, l)
end
end
aegisub.set_undo_point("karaoke-like stuff")
end
aegisub.register_macro("Karaoke fun", "Makes karaoke-like stuff", "edit", karatest, nil)

View file

@ -0,0 +1,40 @@
-- Automation 4 test file
-- Create a Macro feature, that displays some text
script_name = "Automation 4 test 4"
script_description = "Test progress reporting"
script_author = "Niels Martin Hansen"
script_version = "1"
function wait()
local s = ""
for i = 0, 500 do
s = s .. i
end
return s
end
function progression(subtitles, selected_lines, active_line)
while not aegisub.progress.is_cancelled() do
aegisub.progress.task("Counting up...")
for i = 0, 100, 0.2 do
aegisub.progress.set(i)
if aegisub.progress.is_cancelled() then
break
end
wait()
end
if aegisub.progress.is_cancelled() then
break
end
aegisub.progress.task("Counting down...")
for i = 0, 100 do
aegisub.progress.set(100-i)
wait()
end
end
end
aegisub.register_macro("Progress fun", "Does absolutely nothing", "video", progression, nil)

View file

@ -0,0 +1,40 @@
-- Automation 4 test file
-- Create a Macro feature, that displays some text
script_name = "Automation 4 test 5"
script_description = "Test include and text_extents"
script_author = "Niels Martin Hansen"
script_version = "1"
include("utils.lua")
function test5(subtitles, selected_lines, active_line)
local styles = {}
for i = 1, #subtitles do
local l = subtitles[i]
if l.class == "dialogue" then
break
end
if l.class == "style" then
styles[l.name] = l
end
end
for i = #selected_lines, 1, -1 do
local ri = selected_lines[i]
local l = subtitles[ri]
local k = aegisub.parse_karaoke_data(l)
local left = 0
for j = 1, #k do
local nl = table.copy(l)
l.text = string.format("{\\t(%d,%d,\\fscx50)\\pos(%d,20)}%s", k[j].start_time, k[j].end_time, left, k[j].text_stripped)
left = left + (aegisub.text_extents(styles[l.style], k[j].text_stripped))
subtitles.insert(ri+j, l)
end
end
aegisub.set_undo_point("karaoke-like stuff")
end
aegisub.register_macro("More karaoke fun", "Makes some more karaoke-like stuff", "tools", test5, nil)

View file

@ -0,0 +1,82 @@
-- Automation 4 test file
-- Create a Filter feature that does some kara stuff
script_name = "Automation 4 test 6"
script_description = "Test basic export filters"
script_author = "Niels Martin Hansen"
script_version = "1"
include("utils.lua")
function test6_2(subtitles, config)
--[[for i = 1, #subtitles do
local l = subtitles[i]
if l.class == "dialogue" then
local nl = table.copy(l)
nl.text = "Copied!"
subtitles.insert(i, nl)
break
end
end]]
end
function test6(subtitles, config)
aegisub.progress.task("Collecting style data")
local styles = {}
for i = 1, #subtitles do
aegisub.debug.out("finding styles, line " .. i)
local l = subtitles[i]
if l.class == "dialogue" then
break
end
if l.class == "style" then
aegisub.debug.out(" found style: " .. l.name)
styles[l.name] = l
end
aegisub.progress.set(100 * i / #subtitles)
end
local res = {}
local i = 1
while i <= #subtitles do
aegisub.debug.out("producing effect, line " .. i)
local l = subtitles[i]
if l.class == "dialogue" then
aegisub.debug.out(" found dialogue: " .. l.text)
local res = {}
do_line(styles, l, config, res)
aegisub.debug.out(" lines returned by do_line: " .. #res)
for j,nl in ipairs(res) do
subtitles.insert(i+j, nl)
end
aegisub.debug.out(" done inserting generated lines")
subtitles.delete(i)
i = i + #res
else
aegisub.debug.out(" not dialogue")
i = i + 1
end
aegisub.progress.task(string.format("Producing effect (%d/%d)", i, #subtitles))
aegisub.progress.set(100 * i / #subtitles)
end
end
function do_line(styles, line, config, res)
local k = aegisub.parse_karaoke_data(line)
aegisub.debug.out(" syllables generated from line: " .. #k)
local left = 0
for j = 1, #k do
aegisub.debug.out(" syllable " .. j .. " is: " .. k[j].text)
local nl = table.copy(line)
nl.text = string.format("{\\t(%d,%d,\\fscx50)\\pos(%d,20)}%s", k[j].start_time, k[j].end_time, left, k[j].text_stripped)
left = left + (aegisub.text_extents(styles[nl.style], k[j].text_stripped))
table.insert(res, nl)
end
end
aegisub.register_filter("Stupid karaoke", "Makes some more karaoke-like stuff", 2000, test6, nil)
aegisub.register_filter("Lame test", "Simple test of filters, just inserting a new line", 2000, test6_2)

View file

@ -0,0 +1,92 @@
-- Automation 4 test file
-- Create a Filter feature that does some kara stuff
script_name = "Automation 4 test 7"
script_description = "Test config dialogs"
script_author = "Niels Martin Hansen"
script_version = "1"
include("utils.lua")
function test7(subtitles, selected_lines, active_line)
local a, b = aegisub.dialog.display({{class="label", label="Test..."}}, {})
report_dialog_result(a, b)
aegisub.progress.set(50)
a, b = aegisub.dialog.display({{class="edit", name="foo", text=""}}, {"foo", "bar"})
report_dialog_result(a, b)
end
function report_dialog_result(button, controls)
aegisub.debug.out("Dialog closed: ")
if button == false then
aegisub.debug.out("cancelled\n")
elseif button == true then
aegisub.debug.out("clicked Ok\n")
else
aegisub.debug.out("clicked '" .. button .. "'\n")
end
for key, val in pairs(controls) do
aegisub.debug.out(key .. ': ' .. val .. '\n')
end
aegisub.debug.out(" - - - - -\n")
end
function exporter(subs, config)
for i = 1, #subs do
local l = subs[i]
if l.class == "dialogue" and not l.comment then
if config.style == "" or l.style == config.style then
l.text = config.text .. l.text
subs[i] = l
end
end
end
end
function export_config_dialog(subs, store)
local styles = {""}
for i = 1, #subs do
local l = subs[i]
if l.class == "style" then
table.insert(styles, l.name)
end
end
return {
{
class = "label",
label = "This will insert a given text in\n" ..
"front of all dialogue lines of\n" ..
"the given style, or every line\n" ..
"if no specific style is selected.",
x = 0, y = 0, width = 2, height = 1
},
{
class = "label",
label = "Text to insert:",
x = 0, y = 1, width = 1, height = 1
},
{
class = "edit",
name = "text",
x = 1, y = 1, width = 1, height = 1
},
{
class = "label",
label = "Style to apply on:",
x = 0, y = 2, width = 1, height = 1
},
{
class = "dropdown",
name = "style",
value = "",
items = styles,
x = 1, y = 2, width = 1, height = 1
}
}
end
aegisub.register_macro("Config Dialog 1", "Show a stupid config dialog", "video", test7, nil)
aegisub.register_filter("Export Config", "Test export filter config dialog stuff", 500, exporter, export_config_dialog)

View file

@ -1,351 +0,0 @@
Automation 4 Basic Interface
This document described the basic functions needed to define an Automation 4
script. This covers Feature registration and the prototypes of functions used
to implement the various features.
---
Macro Registation Function
This is a function called from top-level of an Automation script to register
a new Macro Feature.
function aegisub.register_macro(
name,
description,
menu,
processing_function,
validation_function)
@name (string)
The displayed name of the menu item this macro will generate.
@description (string)
A longer description of the function of this macro. This will appear
on the status bar when hovering over the menu item.
@menu (string)
The menu this macro will appear in. Must be one of:
o "edit"
o "video"
o "audio"
o "tools"
o "right" (the subtitles grid right-click menu)
The menu chosen should be relevant to the function of the macro.
@processing_function (function)
The actual function called for the macro execution.
This function must be an instance of the Macro Processing Function
described below.
@validation_function (function)
Optional. A function called when it is to be determined whether the
macro can act on the current subtitles.
This function, if provided, must execute very quickly to avoid lag
in the GUI.
This function must be an instance of the Macro Validation Function
described below.
Returns: nothing.
---
Filter Registration Function
This is a function called from top level of an Automation script to register
a new Export Filter Feature.
function aegisub.register_filter(
name,
description,
priority,
processing_function,
options_window_provider)
@name (string)
The name of the filter, as presented to the user.
@description (string)
A longer description of the filter presented to the user.
@priority (number)
A number determining the default order the enabled filters will be
processed. The order can be overridden by the user.
Priorities of some built-in filters:
o Clean Script Info = 0
o Fix Styles = -5000
o Transform Framerate = 1000
Filters with higher priority will be executed earlier by default.
@processing_function (function)
The function called to do the actual filter processing.
This function must be an instance of the Filter Processing Function
described below.
@options_window_provider (function)
Optional. A function providing a dialog template for setting options
prior to filter processing.
This function must be an instance of the Filter Options Window Provider
function described below.
Returns: nothing.
---
Format Reader Registration
This is a function called from top level in an Automation script to register
a new File Format Reader Feature.
function aegisub.register_reader(
name,
extension,
is_text_format,
processing_function)
@name (string)
The name of the file format.
@extension (string)
The file extension usually given to this file format. This must not
include any wildcards. (Ie. extension could be "srt", "sub", "ssa" and
so on.)
@is_text_format (boolean)
Determines whether the user can select a default encoding for reading text
from the file. This only affects whether a default encoding is chosen or not,
even with this argument set to false, you can still read text. You should set
this to true, if the format allows storing in different encodings, and doesn't
explicitly store the used encoding anywhere.
@processing_function (function)
The function called to do the actual file import.
This function must be an instance of the Format Reader Function described
below.
Returns: nothing.
---
Format Writer Registration
This is a function called from top level in an Automation script to register
a new File Format Writer Feature.
function aegisub.register_writer(
name,
extension,
is_text_format,
processing_function)
@name (string)
Name of the file format, as presented to the user.
@extension (string)
The usual file extension given to this file format. This is automatically
be attached to the file name on export, unless the user chooses to
override it.
@is_text_format (boolean)
Determines whether the user can select a target encoding or not. This
option should be true if the format written allows choosing between
different encodings, and the user should be able to select which one to
use. This option doesn't affect whether you can write text to the file or
not, but only whether the user can select an encoding or not.
@processing_function (function)
The function doing the actual file export.
This function must be an instance of the Format Writer Function described
below.
Returns: nothing.
---
Macro Processing Function
This function is called by Aegisub to execute a macro.
function process_macro(
subtitles,
selected_lines,
active_line)
The name of the function is script-defined. (It doesn't have to be
process_macro.)
@subtitles (user data)
A Subtitles Object, that can be used to retrieve information about the
subtitle file the macro is being applied on.
@selected_lines (table)
An Array Table of numbers, each entry being an index into the file
represented by @subtitles. Each of the entries in this table describe that
a line is marked as selected by the user.
@active_line (number)
Index of the currently active line in the subtitle file.
Returns: nothing.
---
Macro Validation Function
This function is called by Aegisub to determine whether a macro can be applied
to the current state of the subtitles and selection.
This function needs to execute very fast, since it may be called for several
macros whenever a menu is opened. It is suggested not to use @subtitles at all
in this function.
This function does not have to be defined. If it's undefined, it's taken as if
it always returned true.
function validate_macro(
subtitles,
selected_lines,
active_line)
The name of the function is script-defined. (It doesn't have to be
validate_macro.)
@subtitles (user data)
A Subtitles Object, that can be used to retrieve information about the
subtitle file the macro is to be be applied on.
@selected_lines (table)
An Array Table of numbers, each entry being an index into the file
represented by @subtitles. Each of the entries in this table describe that
a line is marked as selected by the user.
@active_line (number)
Index of the currently active line in the subtitle file.
Returns: Boolean.
true is the macro can be applied to the current state of the subtitles,
false if not.
---
Filter Processing Function
This function is called by Aegisub to filter the subtitles during an export
operation.
function process_filter(
subtitles,
config)
The name of the function is script-defined. (It doesn't have to be
process_filter.)
@subtitles (user data)
A Subtitles Object, that can be used to retrieve information about the
subtitle file the filter is being applied on.
@config (table)
A Dialog Result table representing the options the user selected for the
filter before starting the export operation. The fields present in this
table are defined by the dialog provided by the Filter Options Window
Provider function.
Returns: nothing.
---
Filter Options Window Provider function
This function is called by Aegisub to get a Dialog Window definition to prompt
the user for input before an export operation.
The data input into the dialog returned by this function are automatically
stored into the original subtitle file when an export operation is started.
function filter_options_dialog(
subtitles,
stored_options)
The name of the function is script-defined. (It doesn't have to be
filter_options_dialog.)
@subtitles (user data)
A Subtitles Object, that can be used to retrieve information about the
subtitle file the filter is to be applied on.
@stored_options (table)
The currently stored options for this export filter. The keys in this table
are the option names, and the values are the values stored for those options.
Returns: A Dialog Window table.
---
Format Reader Function
This function is called by Aegisub to import a file from a foreign file
format.
function read_format(
input_file,
output_subs)
The name of the function is script-defined. (It doesn't have to be
read_format.)
@input_file (user data)
An Input File Stream, representing the file selected for import.
@output_subs (user data)
An empty Subtitles Object the imported data should be added to.
Returns: Boolean.
True if the import succeeded, false if it failed.
---
Format Writer Function
This function is called by Aegisub to export a file to a foreign file format.
function write_format(
input_subs,
output_file)
The name of the function is script-defined. (It doesn't have to be
write_format.)
@input_subs (user data)
A Subtitles Object representing the subtitles to be exported.
@output_file (user data)
An Ouput File Stream, representing the file the exported data should be
written to.
Returns: Boolean.
True if the export succeeded, false if it failed.
If this function returns false, the output file is deleted from disk.
---
Script information globals
These are a series of global variables, the script author can set. They are
purely informational, and won't have any actual influence on how the script
is treated.
script_name (string)
A short, descriptive name for the script, used for presenting it to the
user in the UI.
script_description (string)
A longer description of the purpose of the script, presented to the user
in the UI.
script_author (string)
Name(s) of the author(s) of the script.
script_version (string)
Version number of the script.
---

View file

@ -1,180 +0,0 @@
Automation 4 Configuration Dialog interface
This file describes the functions and data structures used for the
Configuration Dialog functionality in Automation 4.
---
Dialog Control table format
A Dialog Control table describes a single control in a configuration dialog,
which can display information to the user and allow them to change it.
There are a number of different classes of controls, and the keys a Dialog
Control table must contain depends on the control class.
Common keys for all control classes:
class (string)
Defines which class this control has. Must be one of:
"label",
"edit", "intedit", "floatedit", "textbox",
"dropdown",
"checkbox",
"color", "coloralpha", "alpha"
name (string)
A name that uniquely identifies the control. This is recommended to be a
string easily used as an identifier in Lua, since it will be used to access
the value input into the control.
x (number)
y (number)
width (number)
height (number)
Determines the position and size of the control in the dialog. These values
are used to create a grid containing the controls. They should all be
integer. The top left corner is x,y=0,0.
If any of width and height are set to zero or less, it will be set to one
instead.
Key defined for all classes except "label":
hint (string)
A string displayed to the
Keys defined only for "label" and "checkbox" classes:
label (string)
The text displayed to the user on the control.
Key defined only for the "edit" and "textbox" classes:
text (string)
The contents of the control when the dialog is first displayed.
This can contain newlines if the control is of the "textbox" class.
Keys defined only for the "intedit" and "floatedit" classes:
value (number)
The value in the control when the dialog is first displayed. For the
"intedit" class, if this is a non-integer point number it is truncated
before being used.
min (number or nil)
max (number or nil)
step (number or nil)
If one of these are nil, the other must also be nil. (Ie. undefined.)
If all are present, the control gets a spin button, the user can click to
update the value of the control. The value is changed by "step" amount
every time, up to "max" or down to "min". The user won't be able to close
the dialog if the value is outside the range between "min" and "max" either.
Keys defined only for the "dropdown" class:
items (table)
This is an Array Table containing only strings. They are used for the
options displayed to the user in the dropdown box.
All strings in the array table should be unique. (There is not way to
distinguish non-unique strings from each other.)
value (string)
Determines which item is selected when the dialog id first displayed. If
this is not one of the items specified, no item is selected. This is case-
sensitive.
Key defined only for the "checkbox" class:
value (boolean)
Determines whether the checkbox is checked or not when the dialog is first
displayed.
Keys defined only for the "color", "coloralpha" and "alpha" classes:
value (string)
A color value in VB or HTML hexadecimal format.
For the "color" class, this should be a 3 byte value, ie. "#RRGGBB".
For the "coloralpha" class, this should be a 4 byte value, ie. "#RRGGBBAA".
For the "alpha" class, this should be a one-byte value, ie. "#AA".
---
Dialog Definition table format
The Dialog Definition table is simply an Array Table of Dialog Control tables.
Note, however, that while the visual ordering of the controls are decided
entirely by the "x", "y", "width" and "height" of the controls, the
"tab order" of the controls are decided by their ordering in the Dialog
Definition table.
---
Dialog Result table format
A Dialog Result table contains the user input from a configuration dialog.
The control "name" properties are used as keys in this table.
The type of the value for each entry in the table depends on the class of the
control. The control classes map to types in the following manner:
"label"
None. Since the user cannot change a label, they do not produce any value.
"edit", "textbox"
String. The text input in the box. This can contain newlines in the case of
a "textbox" class control.
"intedit", "floatedit"
Number. The number input into the control, guaranteed to be within the
constraints set by the class (integer or float) and the min/max properties.
"dropdown"
String. The case-exact text of the selected item.
"checkbox",
Boolean. The checked-state of the checkbox.
"color", "coloralpha", "alpha"
String. A VB colorstring following the same scheme as for setting the
"value" property.
---
Display Configuration Dialog function
This function displays a configuration dialog to the user and waits for it to
close. It then returns whether the user accepted or cancelled the dialog, and
what values were input.
function aegisub.dialog.display(dialog, buttons)
@dialog (table)
A Dialog Definition table containing the controls to be in the dialog.
@buttons (table)
Optional. This is an Array Table of strings defining the buttons that appear
in the dialog. If this is left out or is otherwise not a table, the standard
Ok and Cancel buttons appear.
The strings in this Array Table are used as labels on the buttons, and for
identifying them in the return values of the function.
Returns: Two values.
1. Boolean or string.
If no custom buttons were specified, this is a boolean telling whether Ok
(true) or Cancel (false) were clicked in the dialog.
If custom buttons were specified, this is the text on the button clicked
by the user.
2. Table.
The Dialog Result table corresponding to the values the user input in the
dialog.
---

View file

@ -1,216 +0,0 @@
Automation 4 File Stream interface
This file describes the interface used for reading and writing files in
Automation 4. This includes text encoding conversion routines.
---
About encodings
All file streams always have a text encoding. By default, this is 'utf-8',
unless the file format reader/writer was registered as a text format. In
that case, the default encoding will be the one set by the user before the
reader or writer is invoked.
All string operations on a stream follow the current encoding. You can
change the encoding during reading/writing, and the change will only take
effect from that point on.
You can perform binary IO by setting the encoding to 'binary' and using
strings consisting only of codepoints 0 to 255.
---
Output File Stream user data object
This object is passed to functions operating on an Output File Stream.
---
Input File Stream user data object
This object is passed to functions operating on an Input File Stream.
---
Getting text encoding
This function returns a string describing the current text encoding used for
a file stream.
function aegisub.fstream.get_encoding(stream)
@stream (user data)
The Input File Stream or Output File Stream to get the encoding for.
Returns: String describing the encoding. This string can be used for setting
the encoding later.
---
Setting text encoding
This function changes the current text encoding used for a file stream.
function aegisub.fstream.set_encoding(stream, encoding)
@stream (user data)
The Input File Stream or Output File Stream to change the encoding for.
@encoding (string)
The new encoding to use.
Returns: String describing the old encoding.
---
File Pointer operations
function aegisub.fstream.tell(stream)
@stream (user data)
The Input File Stream or Output File Stream get position of.
Returns: Number, the number of bytes since the beginning of the file.
function aegisub.fstream.seek(stream, length)
@stream (user data)
The Input File Stream or Output File Stream to seek in.
@length (number)
Number of bytes to skip. This can be negative.
You can only seek backwards in an Output File Stream, and doing so truncates
the file.
Returns: nothing.
function aegisub.fstream.reset(stream)
Resets the file pointer to the start of the file. Truncates an Output File
Stream to zero bytes.
@stream (user data)
The Input File Stream or Output File Stream to seek in.
Returns: nothing.
---
Reading text
All these functions assume the file is in the current encoding specified.
function aegisub.fstream.skip_utf_bom(stream, change_encoding)
This function has undefined behaviour unless called as the first
read-operation on the stream.
It detects whether the file stream starts with an UTF Byte Order Mark, skips
the number of bytes used by that BOM, and optionally changes the current file
encoding to match the detected BOM.
@stream (user data)
The Input File Stream to read from.
@change_encoding (boolean)
If true, change encoding to match the detected BOM.
Returns: Boolean, whether a BOM was detected or not.
function aegisub.fstream.read(stream, length)
Read a number of characters from a file.
@stream (user data)
The Input File Stream to read from.
@length (number)
Number of characters to read. If this is zero, no data are read. If this
is larger than the number of characters available, data are read until the
end of file.
Returns: String, the string read from the file.
function aegisub.fstream.read_bytes(stream, length)
Read a number of bytes from a file and convert to a string.
@stream (user data)
The Input File Stream to read from.
@length (number)
The number of bytes to read.
Returns: String, best-effort converted from the bytes read.
function aegisub.fstream.read_line(stream, include_newline)
Read until next newline in the file. A newline is defined asone of these
sequences of Unicode codepoints in the decoded text:
0x0A ("\n")
0x0D ("\r")
0x0D 0x0A ("\r\n")
The sequence "\n\r" is interpreted as two newlines, ie. a newline, a blank
line and yet another newline.
@stream (user data)
The Input File Stream to read from.
@include_newline (boolean)
If true, include the newline character(s) in the returned string.
Returns: String.
---
Writing text
All these functions assume the file is in the current encoding specified.
function aegisub.fstream.write_utf_bom(stream)
This function will corrupt your file if used anywhere else than on position 0.
Write the correct UTF BOM character to the file, or nothing if not currently
in an UTF encoding.
@stream (user data)
The Output File Stream to write the BOM to.
Returns: nothing.
function aegisub.fstream.write(stream, text)
Write a string to a file.
@stream (user data)
The Output File Stream to write to.
@text (string)
The text to write.
Returns: nothing.
function aegisub.fstream.write_line(stream, text)
Write a string to a file, followed by an "\r\n" newline.
@stream (user data)
The Output File Stream to write to.
@text (string)
The text to write.
Returns: nothing.
---

View file

@ -1,32 +0,0 @@
Miscellaneous functions in Automation 4
This document describes various functions that couldn't be placed in any of
the other Automation 4 documents.
---
Getting the rendered size of a string
This function might later on be part of a full rendering-interface for
creating actual bitmaps of text.
This function does NOT attempt to handle line breaks, automatic line breaking,
fomatting override tags, vector drawings or anything else to that effect.
If you need such functionality, you need to implement it yourself. (For now,
at least.)
function aegisub.text_extents(style, text)
@style (table)
A "style" class Subtitle Line table. This
@text (string)
The text to calculate the rendered size of.
Returns: 4 values, all numbers.
1. Width of text in pixels.
2. Height of text in pixels.
3. Descent of text in pixels.
4. External leading of text in pixels.
---

View file

@ -1,176 +0,0 @@
Aegisub Automation documentation
Version 4
Copyright 2005-2006 Niels Martin Hansen
THIS IS OUT OF DATE COMPARED TO THE REST OF THE DOCS!
---
This document describes version 4 of the automation system used in Aegisub.
The automation system uses the Lua language for scripting engine.
See <http://www.lua.org/> for more information.
---
Overview
Aegisub Automation is a scripting environment that allows you to automate
almost any task working with subtitles in Aegisub, ie. a macro environment.
Automation allows you to:
- Create macros (adding extra menu items to the main menu)
o Those macros can optionally also display dialog boxes to the user
o Allows adding new features to Aegisub without recompiling the entire
program!
- Write export filters
o This is what Automation 3 did, but with more options
o Useful for adding complicated special effects to a script
- Write file-format importers and exporters
o Load every strange subtitle format you come by
o Save in those formats as well
o Exporters write directly to a file stream, allowing you to generate
those huge karaoke effects much faster!
Automation runs in a true Unicode environment, meaning strings are internally
represented as UTF-32, so you, as programmer, don't have to worry about text
encodings, prefix encodings etc. to write scripts that can handle text in
mostly any language of the world.
---
Scripts, files functions
An automation script is a Lua script following certain conventions described
in this document. A script consists of one or more files, with one of them
being the master script, and the others being include files.
Every script runs in a separate Lua interpreter, so separate scripts cannot
communicate directly with each other. Scripts can share code by having common
include files. Scripts can share data by storing data in the subtitle files,
either in the dialogue lines or in the Script Info headers.
Files containing Automation scripts must in UTF-8 encoding, with or without
BOM (Byte Order Mark). Compiled Lua scripts should also work, as long as all
strings are UTF-8 encoded, but this is untested and unsupported.
Automation scripts implement one or more of four possible features. A feature
is implemented by filling a specially named global table with certain values.
See below for a discussion about the various features and how they differ.
---
Scriptable features
The following four features can be implemented by an Automation script:
- Macro
A macro is presented as a new menu item in the Automation menu on the menu
bar in Aegisub. When the user select the menu item, a function in the
Automation script is called to do processing. Features are present to allow
direct interaction with the subtitle data.
The macro can create and display dialog windows to the user.
A macro can provide a function, that determines whether the macro cen be
run, based on the current selection in the program, and the contents of
the subtitles.
- Export filter
An export filter is presented as a filter in the Export dialog accessed
from the File menu. The export filter is called when the user uses the
Export feature. The export filter is given access every line (including
Styles and Script Info lines) in the subtitle file, and can add/modify/
remove lines in those.
The export filter can provide a function, that returns a configuration
dialog, which is presented to the user before the export is run. This
function can access the subtitle data in order to customise the
configuration dialog, before it's presented to the user.
- File format reader
It is not yet decided how the file format reader is accessed.
Current ideas:
o It provides two functions, one to test whether it can handle a given
file and one to actually convert that file to ASS. Which import filter
to use is decided by Aegisub, based on the result of the first function.
o The user selects an import filter and a file. The import filter is
applied to the selected file.
The file format reader can present dialog windows to the user.
The file format reader is given access to the raw file stream.
- File format writer
The file format writer is selected in the Export dialog access from the
File menu. The file format writer is handed all the lines of the subtitles
file and a file stream to write to.
The file format writer can report itself as writing a binary format or a
text format. In the case of a text format, all output is passed through the
character set conversion routines in Aegisub.
The file format writer can present dialog windows to the user.
Every feature is given access to the following in addition to what's described
above:
- Displaying/hiding/updating a progress bar.
- Outputting messages to the user.
- Accessing framerate data
- (Not fully decided yet) Raw video frame data (RGB and/or YUV)
- (Not fully decided yet) Raw and FFT transformed wave data
- (Not fully decided yet) Utilising FexTracker functions
- Calculating the rendered size of a text string, given a style definition
---
Script registration
Scripts can be loaded in two ways, through autoload or by assigning them to
a subtitle file.
Autoloading of scripts happens by placing the master script file into the
"automation/autoload" directory under the Aegisub installation directory.
Assining scripts to a subtitle file is done through the Automation Manager
GUI. Scripts assigned to a subtitle file are stored in the ASS Script Info
line "Automation Scripts", using a pipe character as separator between the
master script filenames.
The automatic loading/storing of configuration options from Automation 3 has
been removed, but can still be implemented in an Export Filter feature using
the initialisation function.
---
Actual documentation for functions, data structures and other interfaces is
yet to be written.
---
Versions of the scripting interface
Here's a quick history of the scripting interface:
Version 1
Using Lua as engine.
The scripts used in the Karaoke Effector application, avaible at:
<http://www.jiifurusu.dk/files/programming/effector/> (currently down)
Version 2
Using Python as engine.
The first draft for an Aegisub automation engine.
Never implemented.
Version 3
Using Lua as engine.
Aegisub 1.09 was the last release-version to use Automation 3.
(Tentative release date only!)
Version 4
Using Lua as engine
Present in Aegisub 1.10 and later (tentative!)
Heavily expanded feature set, allowing a much wider range of modifications,
and more direct integration into the Aegisub user interface.

View file

@ -1,102 +0,0 @@
Automation 4 Progress Reporting and Debugging interface
This document describes the functions used for reporting progress and
outputting debug information during the running of a script.
---
Showing/hiding the progress dialog
This function is used to show or hide the progress dialog.
function aegisub.progress.show(do_show, can_cancel)
@do_show (boolean)
True if the dialog should be shown, false if it should be hidden.
@can_cancel (boolean)
Determines whether the Cancel button is shown. If you set this to true,
you should remember to periodically test whether the script has been
cancelled.
Returns: nothing.
---
Setting the progress bar position
function aegisub.progress.set(precent)
@percent (number)
The percentage completed.
Returns: nothing.
---
Showing the current task
Used to set a message describing the current task being done.
function aegisub.progress.task(msg, ...)
@msg (string)
A format string used for the message.
@...
Parameters to the format string.
Returns: nothing.
---
Setting the progress dialog title
function aegisub.progress.title(title, ...)
@title (string)
A format string used for the title.
@...
Parameters to the format string.
Returns: nothing.
---
Getting the "cancelled" status
Call this function to determine whether the Cancel button in the progress
dialog has been clicked.
function aegisub.progress.is_cancelled()
Returns: Boolean. True is the user has clicked the Cancel button, false if it
has not been clicked, nil if there is no Cancel button.
---
Outputting text to the debug log
function aegisub.debug.out(level, msg, ...)
@level (number)
Integer describing the verbosity of this message. Here are some suggested
values you can use:
0: Fatal, this is really an error that can't be ignored.
1: Error, this kind of error can be recovered from, but might result in a
fatal error later on.
2: Warning, something might be going wrong.
3: Hint, something isn't entirely sane, but nothing wrong.
4: Debug, some additional data only needed for development.
5: Trace, extremely verbose data showing every tiny step during execution.
@msg (string)
A format string used for the message.
@...
Parameters for the format string.
Returns: nothing.
---

View file

@ -1,398 +0,0 @@
Automation 4 Subtitle Data format and functions API
This file describes the API for retrieving and manipulating subtitle data in
Automation 4, as well as the data structures generated and accepted by these
APIs.
---
Subtitle Line table
A Subtitle Line table contains various information about a line in the
subtitle file being processed. There are several classes of subtitle lines,
which all have a few fields in common, but otherwise have different fields.
Common keys for all Subtitle Line classes:
class (string)
The class of the Subtitle Line. Must be one of:
"clear", (empty line)
"comment", (semicolon-style comment line)
"head", (section heading)
"info", (key/value pair in Script Info section)
"format", (line format definition)
"style", (style definition line)
"stylex", (style extension line, tentative AS5 feature)
"dialogue", (dialogue line or dialogue-style comment line)
"unknown" (unknown kind of line)
Lines of the "unknown" class should never appear in well-formed subtitle
scripts. They will usually be a result of a line being outside the section
it belongs in.
raw (string)
The raw text of the line.
You should not change this field directly, since the data in it will never
be used for generating lines in internal representation. It will, however,
be updated from the remaining data in the line whenever it is required for
one thing or another by an internal function.
section (string)
The section this line is placed in. If it is placed before the first section
heading, this field is nil.
Key defined only for the "comment" class:
text (string)
The text of the comment line, ie. everything after the initial semicolon,
including any spaces.
Key defined only for the "head" class:
No special keys are defined for this class, but the "section" field will be
the name of the new section started.
Keys defined only for the "info" class:
key (string)
The "key" part of the line, ie. everything before the first colon.
value (string)
The "value" part of the line, ie. everything after the first colon,
discarding any leading whitespace.
Keys defined only for the "format" class:
fields (table)
An Array Table of strings, each being the name of a field on the line.
Keys defined only for the "style" class:
name (string)
Name of the style.
fontname (string)
Name of the font used.
fontsize (string)
Size of the font used, in pixels.
color1 (string)
color2 (string)
color3 (string)
color4 (string)
The four colors for the style. (Fill, pre-karaoke fill, border, shadow)
In VB hexadecimal, ie. "&HAABBGGRR&"
bold (boolean/number)
The boldness/weight of the font. This will usually be a boolean, but it
can be a number, in which case it must be one of 0, 100, 200, ..., 900.
italic (boolean)
underline (boolean)
strikeout (boolean)
Other properties of the font.
scale_x (number)
scale_y (number)
Scaling of the text, in percent.
spacing (number)
Additional spacing between letters. Always integer.
angle (number)
Rotation of the text on the Z axis in degrees.
borderstyle (number)
1 = outline and drop shadow; 3 = opaque box.
outline (number)
Width of the outline.
shadow (number)
Distance between shadow and text.
align (number)
Numpad alignment of the text.
margin_l (number)
margin_r (number)
margin_t (number)
margin_b (number)
Left/right/top/bottom margins of the text in pixels.
If using a format without support for separate top and bottom margins, the
margin_t value will be used for vertical margin when converting back to
textual representation.
encoding (number)
Font encoding used for text. This follows the MS Windows font encoding
constants.
relative_to (number)
From STS.h: "0: window, 1: video, 2: undefined (~window)"
vertical (boolean)
Whether vertical text semantics is used or not.
Keys defined only for the "stylex" class:
Remember that this class is only for the tentative AS5 format.
name (string)
Name of the new style defined.
basename (string)
Name of the style the new style is based on.
overrides (string)
String of override tags defining the new style.
Keys only defined for the "dialogue" class:
comment (boolean)
True if the line is a comment line, otherwise false.
layer (number)
The layer the line is rendered in.
start_time (number)
end_time (number)
Start/end time of the line in milliseconds.
style (string)
Name of the style assigned to this line.
actor (string)
Name of the actor performing this line.
margin_l (number)
margin_r (number)
margin_t (number)
margin_b (number)
Left/right/top/bottom margins of the text in pixels.
If any of these are zero, it's considered "not overriding".
Same comment as for style lines applies.
effect (string)
Effect to apply to the line.
userdata (string)
Authoring-application defined data. (Tentative AS5 field.)
text (string)
The text for this line.
---
Subtitle File user data object
The Subtitle File object is a user data object with some of the metatable
methods overridden to provide table-like access to the subtitle lines, as
well as some functions to modify the subtitles.
The following operations are supported.
n = #subs
n = subs.n
Retrieve the number of lines in total.
The first syntax is preferred.
line = subs[i]
Retrieve line i, assuming 1 <= i <= n.
subs[i] = line
Replace line i with new data.
subs[i] = nil
subs.delete(i[, i2, ...])
Delete line i, pushing all following lines up an index. (Ie. by repeatedly
deleting line 1 this way, the file will eventually end up empty.)
The function syntax for this function can also take multiple line indexes,
in which case it deletes each of those lines. All indexes are relative to
the line numbering before the function is called.
subs.deleterange(a, b)
Deletes all lines from index a to index b, both inclusive. If b < a,
nothing is done.
subs[0] = line
subs.append(line[, line2, ...])
Append one or more lines to a file.
subs[-i] = line
subs.insert(i, line[, line2, ...])
Insert one or more lines before index i.
Note that the array-style accessors are most likely faster for any case only
involving one line at a time, while the function syntax versions are probably
faster if operating on multiple lines at a time.
---
Parsing tag data
This function uses the Aegisub SSA parser to split a string into override
blocks and text, and give separate access to each tag in override blocks.
function aegisub.parse_tag_data(text)
@text (string)
The SSA format string to parse.
Returns: A Parsed Tag Data table.
---
Recreating a line from tag data
This function takes a Parsed Tag Data table and creates an SSA string from it.
function aegisub.unparse_tag_data(tagdata)
@tagdata (table)
The Parsed Tag Data table to "unparse".
Returns: A string, being the "unparsed" data.
---
Parsing karaoke data
Tihs function uses the Aegisub SSA parser to split a string into karaoke
syllables with additional calculated information added.
function aegisub.parse_karaoke_data(text)
@text (string)
The SSA format string to parse.
Returns: A Parsed Karaoke Data table.
---
Parsed Tag Data table
The Parsed Tag Data table is an Array Table containing a number of Parsed Line
Block tables.
Parsed Line Block table
A Parsed Line Block describes part of a line. (See ass_dialogue.cpp:70 for a
more complete description of this.
There are several classes of Parsed Line Block tables, which have slightly
varying fields.
Base Parsed Line Block table class
class (string)
One of:
"plain",
"override",
"drawing"
"plain" and "drawing" Parsed Line Block table classes
text (string)
The text contained in this block.
"override" Parsed Line Block table class
This class doesn't have any new, specifically named fields. It does, however,
have multiple integer indexed fields, ie. acts as an Array Table.
Each of these indexes refer to a table of type Parsed Override Tag.
Parsed Override Tag table
This table describes a single override-tag in an SSA line.
valid (boolean)
Whether this tag was parsed as a valid tag, or is just used for representing
junk in the middle of the line.
name (string)
Name of the tag, including leading backslash character. (In the case of
invalid tags, the leading backslash might not be present.)
paran (boolean)
Whether this tag has parantheses in its textual representation.
params (table)
This is an Array Table containing the parameters for this tag. It will
always have the maximum number of entries that can be supported by the tag,
but in case of omitted parameters, the parameters omitted will have 'false'
for value in this table.
---
Parsed Karaoke Data table
The Parsed Karaoke Data table is simply an Array Table of Karaoke Syllable
Data tables. However, the Parsed Karaoke Data table will always have one more
item than its count shows, which is numbered 0 (zero). This item contains
everything before the first syllable.
Karaoke Syllable Data table
This table contains information about a single karaoke syllable.
duration (number)
Duration of the syllable in milliseconds.
(In case of syllable zero, this is always zero.)
start_time (number)
end_time (number)
Start and end times of the syllable, relative to the start of the line,
given in milliseconds.
(While these can technically easily be calculated from the duration data,
they are too convenient to leave out from the standard interface.)
tag (string)
The tag used for marking up this syllable. Usually one of:
"k", "K", "kf", "ko"
(In case of syllable zero, this is always the empty string.)
text (string)
The text of the syllable, including all additional override tags.
text_stripped (string)
The text of the syllable, stripped of any override tags.
---
Setting undo points
This function can only be used in macro features, it will have no effect when
used in any other feature.
It sets an undo point.
You should always call this function after every operation that must be
undoable as a separate step. It is considered very bad practice to modify
the subtitles in a macro without setting at least one undo-point, which must
be at the end.
Furthermore, this function also marks the subtitles as "modified". No other
function does this.
function aegisub.set_undo_point(description)
@description (string)
A short description of the operation that will be undone, if this undo-point
is used. Note that this is currently unused in Aegisub, and this parameter
is simply ignored.
Returns: nothing.
---

77
core/auto4-status.txt Normal file
View file

@ -0,0 +1,77 @@
Aegisub Automation 4 branch build
Alpha-quality
Please install in a folder separate from your main Aegisub installation.
What works:
- UI-wise:
o Scanning the automation/autoload/ dir for scripts and loading them
o Rescanning said dir
o Macros not in right-click menu
o Reloading scripts on command
o Adding arbitrary other scripts to the local-scripts list (ie. bound to
subtitles rather than the application)
o Storing locally loaded scripts into subtiles on save
o Restoring locally loaded scripts from subtitle file on load
o Graceful recovery from (most) errors
o Scripts with errors are not retained, but marked as "has errors"
o Nice presentation of script debug-output
- Scripting-wise
o Global script info variables are read
o Macros:
# Registration function
# Processing function
# Validation function
# Config dialogs
o Export filters:
# Registration function
# Processing function
o Basic subtitle manipulation:
# Getting subtitle data from a line
# Storing subtitle data back into a line
# Appending lines to script
# Inserting lines at random positions in the script
# Deleting lines and (UNTESTED) ranges of lines
# All of the above needs more testing
o Setting undo-points
o Table of selected lines is correctly filled
o Parsing karaoke tags
o Modifying subtitles/setting undo points *should* be impossible in
Validation functions
o text_extents (currently identical to Auto3 version, meaning it also has
the same strange inaccuracies)
o include() function
o Making debug-output
What's broken:
- UI-wise:
o No support for right-click menu macros
o Macro appearance in menus doesn't look too nice (needs a separator before
the macros)
o All functions in the program (including stuff unrelated to Automation)
should mark what kind of operation is put into the undo/redo buffer, but
this seems to be lost somewhere and isn't properly displayed.
- Scripting-wise
o active_line parameter to macro validation and processing functions seems
to not be filled correctly
o Lua needs a string library for processing UTF-8 strings natively, as well
as a regex library for working with Unicode strings. Perhaps even
introduce a new userdata type for storing widestrings?
o Stored options for export script config dialogs aren't supported yet.
What's missing:
- UI wise:
o Apart from things relying on other Features being implemented in
scripting, nothing I can think of.
- Scripting-wise:
o Override (un)parsing of tags
o Subtitle Format reader/writer features
o File streams (required for subtitle format features)
Things to try:
- Test, test, TEST!
Write some scripts that do more or less useful things.
Try to break stuff.
Report bugs.
- Config dialogs definately need more testing, whether all controls
behave sensibly etc.

704
core/auto4_base.cpp Normal file
View file

@ -0,0 +1,704 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#include "auto4_base.h"
#include "ass_style.h"
#include "options.h"
#include <wx/filename.h>
#include <wx/dir.h>
#include <wx/dialog.h>
#include <wx/gauge.h>
#include <wx/button.h>
#include <wx/stattext.h>
#include <wx/thread.h>
#include <wx/sizer.h>
#include <wx/filefn.h>
#include <wx/tokenzr.h>
#ifdef WIN32
#include <windows.h>
#include <wchar.h>
#else
#include <ft2build.h>
#include FT_FREETYPE_H
#endif
namespace Automation4 {
bool CalculateTextExtents(AssStyle *style, wxString &text, int &width, int &height, int &descent, int &extlead)
{
width = height = descent = extlead = 0;
double fontsize = style->fontsize;
#ifdef WIN32
// This is almost copypasta from TextSub
HDC thedc = CreateCompatibleDC(0);
if (!thedc) return false;
SetMapMode(thedc, MM_TEXT);
HDC dczero = GetDC(0);
fontsize = -MulDiv((int)(fontsize+0.5), GetDeviceCaps(dczero, LOGPIXELSY), 72);
ReleaseDC(0, dczero);
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
lf.lfHeight = fontsize;
lf.lfWeight = style->bold ? FW_BOLD : FW_NORMAL;
lf.lfItalic = style->italic;
lf.lfUnderline = style->underline;
lf.lfStrikeOut = style->strikeout;
lf.lfCharSet = style->encoding;
lf.lfOutPrecision = OUT_TT_PRECIS;
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS;
lf.lfQuality = ANTIALIASED_QUALITY;
lf.lfPitchAndFamily = DEFAULT_PITCH|FF_DONTCARE;
wcsncpy(lf.lfFaceName, style->font.wc_str(), 32);
HFONT thefont = CreateFontIndirect(&lf);
if (!thefont) return false;
SelectObject(thedc, thefont);
SIZE sz;
size_t thetextlen = text.length();
const wchar_t *thetext = text.wc_str();
if (style->spacing) {
width = 0;
for (unsigned int i = 0; i < thetextlen; i++) {
GetTextExtentPoint32(thedc, &thetext[i], 1, &sz);
width += sz.cx + (int)style->spacing;
height = sz.cy;
}
} else {
GetTextExtentPoint32(thedc, thetext, (int)thetextlen, &sz);
width = sz.cx;
height = sz.cy;
}
// HACKISH FIX! This seems to work, but why? It shouldn't be needed?!?
fontsize = style->fontsize;
width = (int)(width * fontsize/height + 0.5);
height = (int)(fontsize + 0.5);
TEXTMETRIC tm;
GetTextMetrics(thedc, &tm);
descent = tm.tmDescent;
extlead= tm.tmExternalLeading;
DeleteObject(thedc);
DeleteObject(thefont);
#else // not WIN32
wxMemoryDC thedc;
// fix fontsize to be 72 DPI
fontsize = -FT_MulDiv((int)(fontsize+0.5), 72, thedc.GetPPI().y);
// now try to get a font!
// use the font list to get some caching... (chance is the script will need the same font very often)
// USING wxTheFontList SEEMS TO CAUSE BAD LEAKS!
//wxFont *thefont = wxTheFontList->FindOrCreateFont(
wxFont thefont(
fontsize,
wxFONTFAMILY_DEFAULT,
style->italic ? wxFONTSTYLE_ITALIC : wxFONTSTYLE_NORMAL,
style->bold ? wxFONTWEIGHT_BOLD : wxFONTWEIGHT_NORMAL,
style->underline,
style->font,
wxFONTENCODING_SYSTEM); // FIXME! make sure to get the right encoding here, make some translation table between windows and wx encodings
thedc.SetFont(thefont);
if (style->spacing) {
// If there's inter-character spacing, kerning info must not be used, so calculate width per character
// NOTE: Is kerning actually done either way?!
for (unsigned int i = 0; i < intext.length(); i++) {
int a, b, c, d;
thedc.GetTextExtent(intext[i], &a, &b, &c, &d);
width += a + spacing;
height = b > height ? b : height;
descent = c > descent ? c : descent;
extlead= d > extlead ? d : extlead;
}
} else {
// If the inter-character spacing should be zero, kerning info can (and must) be used, so calculate everything in one go
thedc.GetTextExtent(intext, &width, &height, &descent, &extlead);
}
#endif
// Compensate for scaling
width = (int)(style->scalex / 100 * width + 0.5);
height = (int)(style->scaley / 100 * height + 0.5);
descent = (int)(style->scaley / 100 * descent + 0.5);
extlead = (int)(style->scaley / 100 * extlead + 0.5);
return true;
}
// Feature
Feature::Feature(ScriptFeatureClass _featureclass, const wxString &_name)
: featureclass(_featureclass)
, name(_name)
{
// nothing to do
}
ScriptFeatureClass Feature::GetClass() const
{
return featureclass;
}
FeatureMacro* Feature::AsMacro()
{
if (featureclass == SCRIPTFEATURE_MACRO)
// For VS, remember to enable building with RTTI, otherwise dynamic_cast<> won't work
return dynamic_cast<FeatureMacro*>(this);
return 0;
}
FeatureFilter* Feature::AsFilter()
{
if (featureclass == SCRIPTFEATURE_FILTER)
return dynamic_cast<FeatureFilter*>(this);
return 0;
}
FeatureSubtitleFormat* Feature::AsSubFormat()
{
if (featureclass == SCRIPTFEATURE_SUBFORMAT)
return dynamic_cast<FeatureSubtitleFormat*>(this);
return 0;
}
const wxString& Feature::GetName() const
{
return name;
}
// FeatureMacro
FeatureMacro::FeatureMacro(const wxString &_name, const wxString &_description, MacroMenu _menu)
: Feature(SCRIPTFEATURE_MACRO, _name)
, description(_description)
, menu(_menu)
{
// nothing to do
}
const wxString& FeatureMacro::GetDescription() const
{
return description;
}
MacroMenu FeatureMacro::GetMenu() const
{
return menu;
}
// FeatureFilter
FeatureFilter::FeatureFilter(const wxString &_name, const wxString &_description, int _priority)
: Feature(SCRIPTFEATURE_FILTER, _name)
, AssExportFilter()
, config_dialog(0)
{
description = _description; // from AssExportFilter
Register(_name, _priority);
}
FeatureFilter::~FeatureFilter()
{
Unregister();
}
wxWindow* FeatureFilter::GetConfigDialogWindow(wxWindow *parent) {
if (config_dialog) {
delete config_dialog;
config_dialog = 0;
}
if (config_dialog = GenerateConfigDialog(parent)) {
return config_dialog->GetWindow(parent);
} else {
return 0;
}
}
void FeatureFilter::LoadSettings(bool IsDefault) {
if (config_dialog) {
config_dialog->ReadBack();
}
}
// FeatureSubtitleFormat
FeatureSubtitleFormat::FeatureSubtitleFormat(const wxString &_name, const wxString &_extension)
: Feature(SCRIPTFEATURE_SUBFORMAT, _name)
, extension(_extension)
{
// nothing to do
}
const wxString& FeatureSubtitleFormat::GetExtension() const
{
return extension;
}
bool FeatureSubtitleFormat::CanWriteFile(wxString filename)
{
return !filename.Right(extension.Length()).CmpNoCase(extension);
}
bool FeatureSubtitleFormat::CanReadFile(wxString filename)
{
return !filename.Right(extension.Length()).CmpNoCase(extension);
}
// ShowConfigDialogEvent
const wxEventType EVT_SHOW_CONFIG_DIALOG_t = wxNewEventType();
// ScriptConfigDialog
wxWindow* ScriptConfigDialog::GetWindow(wxWindow *parent)
{
if (win) return win;
return win = CreateWindow(parent);
}
void ScriptConfigDialog::DeleteWindow()
{
if (win) delete win;
win = 0;
}
// ProgressSink
ProgressSink::ProgressSink(wxWindow *parent)
: wxDialog(parent, -1, _T("Automation"), wxDefaultPosition, wxDefaultSize, wxDOUBLE_BORDER)
, cancelled(false)
, has_inited(false)
, script_finished(false)
, debug_visible(false)
{
// make the controls
progress_display = new wxGauge(this, -1, 1000, wxDefaultPosition, wxSize(300, 20));
title_display = new wxStaticText(this, -1, _T(""), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
task_display = new wxStaticText(this, -1, _T(""), wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE|wxST_NO_AUTORESIZE);
cancel_button = new wxButton(this, wxID_CANCEL);
debug_output = new wxTextCtrl(this, -1, _T(""), wxDefaultPosition, wxSize(300, 120), wxTE_MULTILINE|wxTE_READONLY);
// put it in a sizer
sizer = new wxBoxSizer(wxVERTICAL);
sizer->Add(title_display, 0, wxEXPAND | wxALL, 5);
sizer->Add(progress_display, 0, wxALL&~wxTOP, 5);
sizer->Add(task_display, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Add(cancel_button, 0, wxALIGN_CENTER | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Add(debug_output, 0, wxEXPAND | wxLEFT | wxRIGHT | wxBOTTOM, 5);
sizer->Show(debug_output, false);
// make the title a slightly larger font
wxFont title_font = title_display->GetFont();
int fontsize = title_font.GetPointSize();
title_font.SetPointSize(fontsize + fontsize/4 + fontsize/8);
title_font.SetWeight(wxFONTWEIGHT_BOLD);
title_display->SetFont(title_font);
sizer->SetSizeHints(this);
SetSizer(sizer);
Center();
}
ProgressSink::~ProgressSink()
{
}
void ProgressSink::OnIdle(wxIdleEvent &evt)
{
// The big glossy "update display" event
DoUpdateDisplay();
if (script_finished) {
if (!debug_visible) {
EndModal(0);
} else {
cancel_button->Enable(true);
cancel_button->SetLabel(_("Close"));
SetProgress(100.0);
SetTask(_("Script completed"));
}
}
}
void ProgressSink::DoUpdateDisplay()
{
// If debug output isn't handled before the test for script_finished later,
// there might actually be some debug output but the debug_visible flag won't
// be set before the dialog closes itself.
wxMutexLocker lock(data_mutex);
if (!pending_debug_output.IsEmpty()) {
if (!debug_visible) {
sizer->Show(debug_output, true);
Layout();
sizer->Fit(this);
debug_visible = true;
}
*debug_output << pending_debug_output;
debug_output->SetInsertionPointEnd();
pending_debug_output = _T("");
}
progress_display->SetValue((int)(progress*10));
task_display->SetLabel(task);
title_display->SetLabel(title);
}
void ProgressSink::SetProgress(float _progress)
{
wxMutexLocker lock(data_mutex);
progress = _progress;
}
void ProgressSink::SetTask(const wxString &_task)
{
wxMutexLocker lock(data_mutex);
task = _task;
}
void ProgressSink::SetTitle(const wxString &_title)
{
wxMutexLocker lock(data_mutex);
title = _title;
}
void ProgressSink::AddDebugOutput(const wxString &msg)
{
wxMutexLocker lock(data_mutex);
pending_debug_output << msg;
}
BEGIN_EVENT_TABLE(ProgressSink, wxWindow)
EVT_INIT_DIALOG(ProgressSink::OnInit)
EVT_BUTTON(wxID_CANCEL, ProgressSink::OnCancel)
EVT_IDLE(ProgressSink::OnIdle)
EVT_SHOW_CONFIG_DIALOG(ProgressSink::OnConfigDialog)
END_EVENT_TABLE()
void ProgressSink::OnInit(wxInitDialogEvent &evt)
{
has_inited = true;
}
void ProgressSink::OnCancel(wxCommandEvent &evt)
{
if (!script_finished) {
cancelled = true;
cancel_button->Enable(false);
} else {
EndModal(0);
}
}
void ProgressSink::OnConfigDialog(ShowConfigDialogEvent &evt)
{
// assume we're in the GUI thread here
DoUpdateDisplay();
if (evt.config_dialog) {
wxDialog *w = new wxDialog(this, -1, title); // container dialog box
wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL); // sizer for putting contents in
wxWindow *ww = evt.config_dialog->GetWindow(w); // get/generate actual dialog contents
s->Add(ww, 0, wxALL, 5); // add contents to dialog
w->SetSizerAndFit(s);
w->CenterOnParent();
w->ShowModal();
evt.config_dialog->ReadBack();
evt.config_dialog->DeleteWindow();
delete w;
} else {
wxMessageBox(_T("Uh... no config dialog?"));
}
// See note in auto4_base.h
if (evt.sync_sema) {
evt.sync_sema->Post();
}
}
// Script
Script::Script(const wxString &_filename)
: filename(_filename)
, name(_T(""))
, description(_T(""))
, author(_T(""))
, version(_T(""))
, loaded(false)
{
// copied from auto3
include_path.clear();
include_path.EnsureFileAccessible(filename);
wxStringTokenizer toker(Options.AsText(_T("Automation Include Path")), _T("|"), false);
while (toker.HasMoreTokens()) {
// todo? make some error reporting here
wxFileName path(toker.GetNextToken());
if (!path.IsOk()) continue;
if (path.IsRelative()) continue;
if (!path.DirExists()) continue;
if (include_path.Member(path.GetLongPath())) continue;
include_path.Add(path.GetLongPath());
}
}
Script::~Script()
{
for (std::vector<Feature*>::iterator f = features.begin(); f != features.end(); ++f) {
delete *f;
}
}
const wxString& Script::GetFilename() const
{
return filename;
}
const wxString& Script::GetName() const
{
return name;
}
const wxString& Script::GetDescription() const
{
return description;
}
const wxString& Script::GetAuthor() const
{
return author;
}
const wxString& Script::GetVersion() const
{
return version;
}
bool Script::GetLoadedState() const
{
return loaded;
}
std::vector<Feature*>& Script::GetFeatures()
{
return features;
}
// ScriptManager
ScriptManager::ScriptManager()
{
// do nothing...?
}
ScriptManager::~ScriptManager()
{
RemoveAll();
}
void ScriptManager::Add(Script *script)
{
for (std::vector<Script*>::iterator i = scripts.begin(); i != scripts.end(); ++i) {
if (script == *i) return;
}
scripts.push_back(script);
}
void ScriptManager::Remove(Script *script)
{
for (std::vector<Script*>::iterator i = scripts.begin(); i != scripts.end(); ++i) {
if (script == *i) {
delete *i;
scripts.erase(i);
return;
}
}
}
void ScriptManager::RemoveAll()
{
for (std::vector<Script*>::iterator i = scripts.begin(); i != scripts.end(); ++i) {
delete *i;
}
scripts.clear();
}
const std::vector<Script*>& ScriptManager::GetScripts() const
{
return scripts;
}
const std::vector<FeatureMacro*>& ScriptManager::GetMacros(MacroMenu menu)
{
macros[menu].clear();
for (std::vector<Script*>::iterator i = scripts.begin(); i != scripts.end(); ++i) {
std::vector<Feature*> &sfs = (*i)->GetFeatures();
for (std::vector<Feature*>::iterator j = sfs.begin(); j != sfs.end(); ++j) {
FeatureMacro *m = dynamic_cast<FeatureMacro*>(*j);
if (!m) continue;
if (menu == MACROMENU_ALL || m->GetMenu() == menu)
macros[menu].push_back(m);
}
}
return macros[menu];
}
// AutoloadScriptManager
AutoloadScriptManager::AutoloadScriptManager(const wxString &_path)
: path(_path)
{
Reload();
}
void AutoloadScriptManager::Reload()
{
wxDir dir(path);
if (!dir.IsOpened()) {
// crap
return;
}
RemoveAll();
int error_count = 0;
wxString fn;
wxFileName script_path(path, _T(""));
bool more = dir.GetFirst(&fn, wxEmptyString, wxDIR_FILES);
while (more) {
script_path.SetName(fn);
try {
Add(ScriptFactory::CreateFromFile(script_path.GetFullPath()));
}
catch (const wchar_t *e) {
error_count++;
wxLogError(_T("Error loading Automation script: %s\n%s"), fn.c_str(), e);
}
catch (...) {
error_count++;
wxLogError(_T("Error loading Automation script: %s\nUnknown error."), fn.c_str());
}
more = dir.GetNext(&fn);
}
if (error_count) {
wxLogWarning(_T("One or more scripts placed in the Automation autoload directory failed to load\nPlease review the errors above, correct them and use the Reload Autoload dir button in Automation Manager to attempt loading the scripts again."));
}
}
// ScriptFactory
std::vector<ScriptFactory*> ScriptFactory::factories;
const wxString& ScriptFactory::GetEngineName() const
{
return engine_name;
}
const wxString& ScriptFactory::GetFilenamePattern() const
{
return filename_pattern;
}
void ScriptFactory::Register(ScriptFactory *factory)
{
for (std::vector<ScriptFactory*>::iterator i = factories.begin(); i != factories.end(); ++i) {
if (*i == factory) {
throw _T("Automation 4: Attempt to register the same script factory multiple times.");
}
}
factories.push_back(factory);
}
void ScriptFactory::Unregister(ScriptFactory *factory)
{
for (std::vector<ScriptFactory*>::iterator i = factories.begin(); i != factories.end(); ++i) {
if (*i == factory) {
factories.erase(i);
return;
}
}
}
Script* ScriptFactory::CreateFromFile(const wxString &filename)
{
for (std::vector<ScriptFactory*>::iterator i = factories.begin(); i != factories.end(); ++i) {
Script *s = (*i)->Produce(filename);
if (s) return s;
}
return new UnknownScript(filename);
}
const std::vector<ScriptFactory*>& ScriptFactory::GetFactories()
{
return factories;
}
// UnknownScript
UnknownScript::UnknownScript(const wxString &filename)
: Script(filename)
{
wxFileName fn(filename);
name = fn.GetName();
description = _("File was not recognized as a script");
loaded = false;
}
};

363
core/auto4_base.h Normal file
View file

@ -0,0 +1,363 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#pragma once
#ifndef _AUTO4_BASE_H
#define _AUTO4_BASE_H
#include <wx/string.h>
#include <vector>
#include "ass_export_filter.h"
#include "subtitle_format.h"
class AssFile;
class AssStyle;
class wxWindow;
class wxDialog;
class wxStopWatch;
class wxPathList;
DECLARE_EVENT_TYPE(wxEVT_AUTOMATION_SCRIPT_COMPLETED, -1)
namespace Automation4 {
// Calculate the extents of a text string given a style
bool CalculateTextExtents(AssStyle *style, wxString &text, int &width, int &height, int &descent, int &extlead);
// The top-level menus a macro can appear in
enum MacroMenu {
MACROMENU_NONE = 0, // pseudo-index, dunno if this has any real meaning
MACROMENU_ALL = 0, // pseudo-index, used to retrieve information about all macros loaded
MACROMENU_EDIT,
MACROMENU_VIDEO,
MACROMENU_AUDIO,
MACROMENU_TOOLS,
MACROMENU_RIGHT, // right-click menu
MACROMENU_MAX // must be last, one higher than the last, used for array sizing
};
// The class of a Feature...
enum ScriptFeatureClass {
SCRIPTFEATURE_MACRO = 0,
SCRIPTFEATURE_FILTER,
SCRIPTFEATURE_SUBFORMAT,
SCRIPTFEATURE_MAX // must be last
};
// A Feature describes a function provided by a Script.
// There are several distinct classes of features.
class FeatureMacro;
class FeatureFilter;
class FeatureSubtitleFormat;
class Feature {
private:
ScriptFeatureClass featureclass;
wxString name;
protected:
Feature(ScriptFeatureClass _featureclass, const wxString &_name);
public:
virtual ~Feature() { }
ScriptFeatureClass GetClass() const;
FeatureMacro* AsMacro();
FeatureFilter* AsFilter();
FeatureSubtitleFormat* AsSubFormat();
virtual const wxString& GetName() const;
};
// The Macro feature; adds a menu item that runs script code
class FeatureMacro : public virtual Feature {
private:
wxString description;
MacroMenu menu;
protected:
FeatureMacro(const wxString &_name, const wxString &_description, MacroMenu _menu);
public:
virtual ~FeatureMacro() { }
const wxString& GetDescription() const;
MacroMenu GetMenu() const;
virtual bool Validate(AssFile *subs, std::vector<int> &selected, int active) = 0;
virtual void Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow *progress_parent) = 0;
};
class ScriptConfigDialog;
// The Export Filter feature; adds a new export filter
class FeatureFilter : public virtual Feature, public AssExportFilter {
private:
ScriptConfigDialog *config_dialog;
protected:
FeatureFilter(const wxString &_name, const wxString &_description, int _priority);
// Subclasses should probably implement AssExportFilter::Init
virtual ScriptConfigDialog* GenerateConfigDialog(wxWindow *parent) = 0; // subclasses should implement this, producing a new ScriptConfigDialog
public:
virtual ~FeatureFilter();
wxWindow* GetConfigDialogWindow(wxWindow *parent);
void LoadSettings(bool IsDefault);
// Subclasses must implement ProcessSubs from AssExportFilter
};
// The Subtitle Format feature; adds new subtitle format readers/writers
class FeatureSubtitleFormat : public virtual Feature, public SubtitleFormat {
private:
wxString extension;
protected:
FeatureSubtitleFormat(const wxString &_name, const wxString &_extension);
public:
virtual ~FeatureSubtitleFormat() { }
const wxString& GetExtension() const;
// Default implementations of these are provided, that just checks extension,
// but subclasses can provide more elaborate implementations, or go back to
// the "return false" implementation, in case of reader-only or writer-only.
virtual bool CanWriteFile(wxString filename);
virtual bool CanReadFile(wxString filename);
// Subclasses should implement ReadFile and/or WriteFile here
};
// Base class for script-provided config dialogs
class ScriptConfigDialog {
private:
wxWindow *win;
protected:
virtual wxWindow* CreateWindow(wxWindow *parent) = 0;
public:
ScriptConfigDialog() : win(0) { }
wxWindow* GetWindow(wxWindow *parent);
void DeleteWindow();
virtual void ReadBack() = 0;
};
// Config dialog event class and related stuff (wx </3)
extern const wxEventType EVT_SHOW_CONFIG_DIALOG_t;
class ShowConfigDialogEvent : public wxCommandEvent {
public:
ShowConfigDialogEvent(const wxEventType &event = EVT_SHOW_CONFIG_DIALOG_t)
: wxCommandEvent(event)
, config_dialog(0)
, sync_sema(0) { };
virtual wxEvent *Clone() const { return new ShowConfigDialogEvent(*this); }
ScriptConfigDialog *config_dialog;
// Synchronisation for config dialog events:
// You don't want the script asynchronically continue executing while the dialog
// is displaying, so a synchronisation mechanism is used.
// The poster of the event should supply a semaphore object with an initial count
// of zero. After posting the event, the poster should wait for the semaphore.
// When the dialog is finished, the semaphore is posted, and the poster can
// continue.
// The poster is responsible for cleaning up the semaphore.
wxSemaphore *sync_sema;
};
typedef void (wxEvtHandler::*ShowConfigDialogEventFunction)(ShowConfigDialogEvent&);
#define EVT_SHOW_CONFIG_DIALOG(fn) DECLARE_EVENT_TABLE_ENTRY( EVT_SHOW_CONFIG_DIALOG_t, -1, -1, (wxObjectEventFunction)(wxEventFunction)(ShowConfigDialogEventFunction)&fn, (wxObject*)0 ),
// Base class for progress reporting/other output
class ProgressSink : public wxDialog {
private:
wxBoxSizer *sizer;
wxGauge *progress_display;
wxButton *cancel_button;
wxStaticText *title_display;
wxStaticText *task_display;
wxTextCtrl *debug_output;
bool debug_visible;
float progress;
wxString task;
wxString title;
wxString pending_debug_output;
wxMutex data_mutex;
void OnCancel(wxCommandEvent &evt);
void OnInit(wxInitDialogEvent &evt);
void OnIdle(wxIdleEvent &evt);
void OnConfigDialog(ShowConfigDialogEvent &evt);
void DoUpdateDisplay();
protected:
volatile bool cancelled;
ProgressSink(wxWindow *parent);
virtual ~ProgressSink();
public:
void SetProgress(float _progress);
void SetTask(const wxString &_task);
void SetTitle(const wxString &_title);
void AddDebugOutput(const wxString &msg);
volatile bool has_inited;
volatile bool script_finished;
DECLARE_EVENT_TABLE()
};
// Base class for Scripts
class Script {
private:
wxString filename;
protected:
wxString name;
wxString description;
wxString author;
wxString version;
bool loaded; // is the script properly loaded?
wxPathList include_path;
std::vector<Feature*> features;
Script(const wxString &_filename);
public:
virtual ~Script();
virtual void Reload() = 0;
const wxString& GetFilename() const;
const wxString& GetName() const;
const wxString& GetDescription() const;
const wxString& GetAuthor() const;
const wxString& GetVersion() const;
bool GetLoadedState() const;
std::vector<Feature*>& GetFeatures();
};
// Manages loaded scripts; for whatever reason, multiple managers might be instantiated. In truth, this is more
// like a macro manager at the moment, since Export Filter and Subtitle Format are already managed by other
// classes.
class ScriptManager {
private:
std::vector<Script*> scripts;
std::vector<FeatureMacro*> macros[MACROMENU_MAX]; // array of vectors...
public:
ScriptManager();
virtual ~ScriptManager(); // Deletes all scripts managed
void Add(Script *script); // Add a script to the manager. The ScriptManager takes owvership of the script and will automatically delete it.
void Remove(Script *script); // Remove a script from the manager, and delete the Script object.
void RemoveAll(); // Deletes all scripts managed
const std::vector<Script*>& GetScripts() const;
const std::vector<FeatureMacro*>& GetMacros(MacroMenu menu);
// No need to have getters for the other kinds of features, I think.
// They automatically register themselves in the relevant places.
};
// Scans a directory for scripts and attempts to load all of them
class AutoloadScriptManager : public ScriptManager {
private:
wxString path;
public:
AutoloadScriptManager(const wxString &_path);
void Reload();
};
// Script factory; each scripting engine should create exactly one instance of this object and register it.
// This is used to create Script objects from a file.
class ScriptFactory {
private:
static std::vector<ScriptFactory*> factories;
protected:
ScriptFactory() { }
wxString engine_name;
wxString filename_pattern;
public:
virtual Script* Produce(const wxString &filename) const = 0;
const wxString& GetEngineName() const;
const wxString& GetFilenamePattern() const;
static void Register(ScriptFactory *factory);
static void Unregister(ScriptFactory *factory);
static Script* CreateFromFile(const wxString &filename);
static const std::vector<ScriptFactory*>& GetFactories();
};
// Dummy class for scripts that could not be loaded by the ScriptFactory
class UnknownScript : public Script {
public:
UnknownScript(const wxString &filename);
void Reload() { };
};
};
#endif

778
core/auto4_lua.cpp Normal file
View file

@ -0,0 +1,778 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#include "auto4_lua.h"
#include "ass_dialogue.h"
#include "ass_style.h"
#include "ass_file.h"
#include "ass_override.h"
#include <lualib.h>
#include <lauxlib.h>
#include <wx/msgdlg.h>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/window.h>
#include <assert.h>
#include <algorithm>
namespace Automation4 {
// LuaStackcheck
#ifdef _DEBUG
struct LuaStackcheck {
lua_State *L;
int startstack;
void check(int additional)
{
int top = lua_gettop(L);
if (top - additional != startstack) {
wxLogDebug(_T("Lua stack size mismatch."));
dump();
assert(top - additional == startstack);
}
}
void dump()
{
int top = lua_gettop(L);
wxLogDebug(_T("Dumping Lua stack..."));
for (int i = top; i > 0; i--) {
lua_pushvalue(L, i);
wxString type(lua_typename(L, lua_type(L, -1)), wxConvUTF8);
if (lua_isstring(L, i)) {
wxLogDebug(type + _T(": ") + wxString(lua_tostring(L, -1), wxConvUTF8));
} else {
wxLogDebug(type);
}
lua_pop(L, 1);
}
wxLogDebug(_T("--- end dump"));
}
LuaStackcheck(lua_State *_L) : L(_L) { startstack = lua_gettop(L); }
~LuaStackcheck() { check(0); }
};
#else
struct LuaStackcheck {
void check(int additional) { }
void dump() { }
LuaStackcheck(lua_State *L) { }
~LuaStackcheck() { }
};
#endif
// LuaScript
LuaScript::LuaScript(const wxString &filename)
: Script(filename)
, L(0)
{
Create();
}
LuaScript::~LuaScript()
{
if (L) Destroy();
}
void LuaScript::Create()
{
Destroy();
loaded = true;
try {
// create lua environment
L = lua_open();
LuaStackcheck _stackcheck(L);
// register standard libs
lua_pushcfunction(L, luaopen_base); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_package); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_string); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_table); lua_call(L, 0, 0);
lua_pushcfunction(L, luaopen_math); lua_call(L, 0, 0);
_stackcheck.check(0);
// dofile and loadfile are replaced with include
lua_pushnil(L);
lua_setglobal(L, "dofile");
lua_pushnil(L);
lua_setglobal(L, "loadfile");
lua_pushcfunction(L, LuaInclude);
lua_setglobal(L, "include");
// prepare stuff in the registry
// reference to the script object
lua_pushlightuserdata(L, this);
lua_setfield(L, LUA_REGISTRYINDEX, "aegisub");
// the "feature" table
// integer indexed, using same indexes as "features" vector in the base Script class
lua_newtable(L);
lua_setfield(L, LUA_REGISTRYINDEX, "features");
_stackcheck.check(0);
// make "aegisub" table
lua_pushstring(L, "aegisub");
lua_newtable(L);
// aegisub.register_macro
lua_pushcfunction(L, LuaFeatureMacro::LuaRegister);
lua_setfield(L, -2, "register_macro");
// aegisub.register_filter
lua_pushcfunction(L, LuaFeatureFilter::LuaRegister);
lua_setfield(L, -2, "register_filter");
// aegisub.text_extents
lua_pushcfunction(L, LuaTextExtents);
lua_setfield(L, -2, "text_extents");
// store aegisub table to globals
lua_settable(L, LUA_GLOBALSINDEX);
_stackcheck.check(0);
// load user script
if (luaL_loadfile(L, GetFilename().mb_str(wxConvUTF8))) {
wxString *err = new wxString(lua_tostring(L, -1), wxConvUTF8);
err->Prepend(_T("An error occurred loading the Lua script file \"") + GetFilename() + _T("\":\n\n"));
throw err->c_str();
}
_stackcheck.check(1);
// and execute it
// this is where features are registered
// this should run really fast so a progress window isn't needed
// (if it infinite-loops, scripter is an idiot and already got his punishment)
{
LuaThreadedCall call(L, 0, 0);
if (call.Wait()) {
// error occurred, assumed to be on top of Lua stack
wxString *err = new wxString(lua_tostring(L, -1), wxConvUTF8);
err->Prepend(_T("An error occurred initialising the Lua script file \"") + GetFilename() + _T("\":\n\n"));
throw err->c_str();
}
}
_stackcheck.check(0);
lua_getglobal(L, "script_name");
if (lua_isstring(L, -1)) {
name = wxString(lua_tostring(L, -1), wxConvUTF8);
} else {
name = GetFilename();
}
lua_getglobal(L, "script_description");
if (lua_isstring(L, -1)) {
description = wxString(lua_tostring(L, -1), wxConvUTF8);
}
lua_getglobal(L, "script_author");
if (lua_isstring(L, -1)) {
author = wxString(lua_tostring(L, -1), wxConvUTF8);
}
lua_getglobal(L, "script_version");
if (lua_isstring(L, -1)) {
version = wxString(lua_tostring(L, -1), wxConvUTF8);
}
lua_pop(L, 4);
// if we got this far, the script should be ready
_stackcheck.check(0);
}
catch (...) {
Destroy();
loaded = false;
throw;
}
}
void LuaScript::Destroy()
{
// Assume the script object is clean if there's no Lua state
if (!L) return;
// remove features
for (int i = 0; i < (int)features.size(); i++) {
Feature *f = features[i];
delete f;
}
features.clear();
// delete environment
lua_close(L);
L = 0;
loaded = false;
}
void LuaScript::Reload()
{
Destroy();
Create();
}
LuaScript* LuaScript::GetScriptObject(lua_State *L)
{
lua_getfield(L, LUA_REGISTRYINDEX, "aegisub");
void *ptr = lua_touserdata(L, -1);
lua_pop(L, 1);
return (LuaScript*)ptr;
}
int LuaScript::LuaTextExtents(lua_State *L)
{
if (!lua_istable(L, 1)) {
lua_pushstring(L, "First argument to text_extents must be a table");
lua_error(L);
}
if (!lua_isstring(L, 2)) {
lua_pushstring(L, "Second argument to text_extents must be a string");
lua_error(L);
}
lua_pushvalue(L, 1);
AssEntry *et = LuaAssFile::LuaToAssEntry(L);
AssStyle *st = dynamic_cast<AssStyle*>(et);
lua_pop(L, 1);
if (!st) {
delete et; // Make sure to delete the "live" pointer
lua_pushstring(L, "Not a style entry");
lua_error(L);
}
wxString text(lua_tostring(L, 2), wxConvUTF8);
int width, height, descent, extlead;
if (!CalculateTextExtents(st, text, width, height, descent, extlead)) {
delete st;
lua_pushstring(L, "Some internal error occurred calculating text_extents");
lua_error(L);
}
delete st;
lua_pushnumber(L, width);
lua_pushnumber(L, height);
lua_pushnumber(L, descent);
lua_pushnumber(L, extlead);
return 4;
}
int LuaScript::LuaInclude(lua_State *L)
{
LuaScript *s = GetScriptObject(L);
if (!lua_isstring(L, 1)) {
lua_pushstring(L, "Argument to include must be a string");
lua_error(L);
return 0;
}
wxString fnames(lua_tostring(L, 1), wxConvUTF8);
wxFileName fname(fnames);
if (fname.GetDirCount() == 0) {
// filename only
fname = s->include_path.FindAbsoluteValidPath(fnames);
} else if (fname.IsRelative()) {
// relative path
wxFileName sfname(s->GetFilename());
fname.MakeAbsolute(sfname.GetPath(true));
} else {
// absolute path, do nothing
}
if (!fname.IsOk() || !fname.FileExists()) {
lua_pushfstring(L, "Could not find Lua script for inclusion: %s", fnames.mb_str(wxConvUTF8));
lua_error(L);
}
if (luaL_loadfile(L, fname.GetFullPath().mb_str(wxConvUTF8))) {
lua_pushfstring(L, "An error occurred loading the Lua script file \"%s\":\n\n%s", fname.GetFullPath().mb_str(wxConvUTF8), lua_tostring(L, -1));
lua_error(L);
return 0;
}
int pretop = lua_gettop(L) - 1; // don't count the function value itself
lua_call(L, 0, LUA_MULTRET);
return lua_gettop(L) - pretop;
}
// LuaThreadedCall
LuaThreadedCall::LuaThreadedCall(lua_State *_L, int _nargs, int _nresults)
: wxThread(wxTHREAD_JOINABLE)
, L(_L)
, nargs(_nargs)
, nresults(_nresults)
{
Create();
Run();
}
wxThread::ExitCode LuaThreadedCall::Entry()
{
int result = lua_pcall(L, nargs, nresults, 0);
// see if there's a progress sink window to close
lua_getfield(L, LUA_REGISTRYINDEX, "progress_sink");
if (lua_isuserdata(L, -1)) {
LuaProgressSink *ps = LuaProgressSink::GetObjPointer(L, -1);
// don't bother protecting this with a mutex, it should be safe enough like this
ps->script_finished = true;
// tell wx to run its idle-events now, just to make the progress window notice earlier that we're done
wxWakeUpIdle();
}
lua_pop(L, 1);
lua_gc(L, LUA_GCCOLLECT, 0);
return (wxThread::ExitCode)result;
}
// LuaFeature
LuaFeature::LuaFeature(lua_State *_L, ScriptFeatureClass _featureclass, const wxString &_name)
: Feature(_featureclass, _name)
, L(_L)
{
}
void LuaFeature::RegisterFeature()
{
// get the LuaScript objects
lua_getfield(L, LUA_REGISTRYINDEX, "aegisub");
LuaScript *s = (LuaScript*)lua_touserdata(L, -1);
lua_pop(L, 1);
// add the Feature object
s->features.push_back(this);
// get the index+1 it was pushed into
myid = (int)s->features.size()-1;
// create table with the functions
// get features table
lua_getfield(L, LUA_REGISTRYINDEX, "features");
lua_pushvalue(L, -2);
lua_rawseti(L, -2, myid);
lua_pop(L, 1);
}
void LuaFeature::GetFeatureFunction(int functionid)
{
// get feature table
lua_getfield(L, LUA_REGISTRYINDEX, "features");
// get this feature's function pointers
lua_rawgeti(L, -1, myid);
// get pointer for validation function
lua_rawgeti(L, -1, functionid);
lua_remove(L, -2);
lua_remove(L, -2);
}
void LuaFeature::CreateIntegerArray(std::vector<int> &ints)
{
// create an array-style table with an integer vector in it
// leave the new table on top of the stack
lua_newtable(L);
for (int i = 0; i != ints.size(); ++i) {
lua_pushinteger(L, ints[i]+1);
lua_rawseti(L, -2, i+1);
}
}
void LuaFeature::ThrowError()
{
wxString err(lua_tostring(L, -1), wxConvUTF8);
lua_pop(L, 1);
wxLogError(err);
}
// LuaFeatureMacro
int LuaFeatureMacro::LuaRegister(lua_State *L)
{
wxString _name(lua_tostring(L, 1), wxConvUTF8);
wxString _description(lua_tostring(L, 2), wxConvUTF8);
const char *_menustring = lua_tostring(L, 3);
MacroMenu _menu = MACROMENU_NONE;
if (strcmp(_menustring, "edit") == 0) _menu = MACROMENU_EDIT;
else if (strcmp(_menustring, "video") == 0) _menu = MACROMENU_VIDEO;
else if (strcmp(_menustring, "audio") == 0) _menu = MACROMENU_AUDIO;
else if (strcmp(_menustring, "tools") == 0) _menu = MACROMENU_TOOLS;
else if (strcmp(_menustring, "right") == 0) _menu = MACROMENU_RIGHT;
if (_menu == MACROMENU_NONE) {
lua_pushstring(L, "Error registering macro: Invalid menu name.");
lua_error(L);
}
LuaFeatureMacro *macro = new LuaFeatureMacro(_name, _description, _menu, L);
return 0;
}
LuaFeatureMacro::LuaFeatureMacro(const wxString &_name, const wxString &_description, MacroMenu _menu, lua_State *_L)
: LuaFeature(_L, SCRIPTFEATURE_MACRO, _name)
, FeatureMacro(_name, _description, _menu)
, Feature(SCRIPTFEATURE_MACRO, _name)
{
// new table for containing the functions for this feature
lua_newtable(L);
// store processing function
if (!lua_isfunction(L, 4)) {
lua_pushstring(L, "The macro processing function must be a function");
lua_error(L);
}
lua_pushvalue(L, 4);
lua_rawseti(L, -2, 1);
// and validation function
lua_pushvalue(L, 5);
no_validate = !lua_isfunction(L, -1);
lua_rawseti(L, -2, 2);
// make the feature known
RegisterFeature();
// and remove the feature function table again
lua_pop(L, 1);
}
bool LuaFeatureMacro::Validate(AssFile *subs, std::vector<int> &selected, int active)
{
if (no_validate)
return true;
GetFeatureFunction(2); // 2 = validation function
// prepare function call
LuaAssFile *subsobj = new LuaAssFile(L, subs, false, false);
CreateIntegerArray(selected); // selected items
lua_pushinteger(L, -1); // active line
// do call
LuaThreadedCall call(L, 3, 1);
wxThread::ExitCode code = call.Wait();
// get result
bool result = !!lua_toboolean(L, -1);
// clean up stack
lua_pop(L, 1);
return result;
}
void LuaFeatureMacro::Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow *progress_parent)
{
GetFeatureFunction(1); // 1 = processing function
// prepare function call
LuaAssFile *subsobj = new LuaAssFile(L, subs, true, true);
CreateIntegerArray(selected); // selected items
lua_pushinteger(L, -1); // active line
LuaProgressSink *ps = new LuaProgressSink(L, progress_parent);
ps->SetTitle(GetName());
// do call
LuaThreadedCall call(L, 3, 0);
ps->ShowModal();
wxThread::ExitCode code = call.Wait();
if (code) ThrowError();
delete ps;
}
// LuaFeatureFilter
LuaFeatureFilter::LuaFeatureFilter(const wxString &_name, const wxString &_description, int merit, lua_State *_L)
: LuaFeature(_L, SCRIPTFEATURE_FILTER, _name)
, FeatureFilter(_name, _description, merit)
, Feature(SCRIPTFEATURE_FILTER, _name)
{
// Works the same as in LuaFeatureMacro
lua_newtable(L);
if (!lua_isfunction(L, 4)) {
lua_pushstring(L, "The filter processing function must be a function");
lua_error(L);
}
lua_pushvalue(L, 4);
lua_rawseti(L, -2, 1);
lua_pushvalue(L, 5);
has_config = lua_isfunction(L, -1);
lua_rawseti(L, -2, 2);
RegisterFeature();
lua_pop(L, 1);
}
void LuaFeatureFilter::Init()
{
// Don't think there's anything to do here... (empty in auto3)
}
int LuaFeatureFilter::LuaRegister(lua_State *L)
{
wxString _name(lua_tostring(L, 1), wxConvUTF8);
wxString _description(lua_tostring(L, 2), wxConvUTF8);
int _merit = lua_tointeger(L, 3);
LuaFeatureFilter *filter = new LuaFeatureFilter(_name, _description, _merit, L);
return 0;
}
void LuaFeatureFilter::ProcessSubs(AssFile *subs, wxWindow *export_dialog)
{
GetFeatureFunction(1); // 1 = processing function
// prepare function call
// subtitles (undo doesn't make sense in exported subs, in fact it'll totally break the undo system)
LuaAssFile *subsobj = new LuaAssFile(L, subs, true/*allow modifications*/, false/*disallow undo*/);
// config
if (has_config && config_dialog) {
assert(config_dialog->LuaReadBack(L) == 1);
// TODO, write back stored options here
}
LuaProgressSink *ps = new LuaProgressSink(L, export_dialog, false);
ps->SetTitle(GetName());
// do call
LuaThreadedCall call(L, 2, 0);
ps->ShowModal();
wxThread::ExitCode code = call.Wait();
if (code) ThrowError();
delete ps;
}
ScriptConfigDialog* LuaFeatureFilter::GenerateConfigDialog(wxWindow *parent)
{
if (!has_config)
return 0;
GetFeatureFunction(2); // 2 = config dialog function
// prepare function call
// subtitles (don't allow any modifications during dialog creation, ideally the subs aren't even accessed)
LuaAssFile *subsobj = new LuaAssFile(L, AssFile::top, false/*allow modifications*/, false/*disallow undo*/);
// stored options
lua_newtable(L); // TODO, nothing for now
LuaProgressSink *ps = new LuaProgressSink(L, 0, false);
ps->SetTitle(GetName());
// do call
LuaThreadedCall call(L, 2, 1);
ps->ShowModal();
wxThread::ExitCode code = call.Wait();
if (code) ThrowError();
delete ps;
// The config dialog table should now be on stack
return config_dialog = new LuaConfigDialog(L, false);
}
// LuaProgressSink
LuaProgressSink::LuaProgressSink(lua_State *_L, wxWindow *parent, bool allow_config_dialog)
: ProgressSink(parent)
, L(_L)
{
LuaProgressSink **ud = (LuaProgressSink**)lua_newuserdata(L, sizeof(LuaProgressSink*));
*ud = this;
// register progress reporting stuff
lua_getglobal(L, "aegisub");
lua_newtable(L);
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaSetProgress, 1);
lua_setfield(L, -2, "set");
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaSetTask, 1);
lua_setfield(L, -2, "task");
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaSetTitle, 1);
lua_setfield(L, -2, "title");
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaGetCancelled, 1);
lua_setfield(L, -2, "is_cancelled");
lua_setfield(L, -2, "progress");
lua_newtable(L);
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaDebugOut, 1);
lua_setfield(L, -2, "out");
lua_setfield(L, -2, "debug");
if (allow_config_dialog) {
lua_newtable(L);
lua_pushvalue(L, -3);
lua_pushcclosure(L, LuaDisplayDialog, 1);
lua_setfield(L, -2, "display");
lua_setfield(L, -2, "dialog");
}
// reference so other objects can also find the progress sink
lua_pushvalue(L, -2);
lua_setfield(L, LUA_REGISTRYINDEX, "progress_sink");
lua_pop(L, 2);
}
LuaProgressSink::~LuaProgressSink()
{
// remove progress reporting stuff
lua_getglobal(L, "aegisub");
lua_pushnil(L);
lua_setfield(L, -2, "progress");
lua_pushnil(L);
lua_setfield(L, -2, "debug");
lua_pop(L, 1);
lua_pushnil(L);
lua_setfield(L, LUA_REGISTRYINDEX, "progress_sink");
}
LuaProgressSink* LuaProgressSink::GetObjPointer(lua_State *L, int idx)
{
assert(lua_type(L, idx) == LUA_TUSERDATA);
void *ud = lua_touserdata(L, idx);
return *((LuaProgressSink**)ud);
}
int LuaProgressSink::LuaSetProgress(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
float progress = lua_tonumber(L, 1);
ps->SetProgress(progress);
return 0;
}
int LuaProgressSink::LuaSetTask(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
wxString task(lua_tostring(L, 1), wxConvUTF8);
ps->SetTask(task);
return 0;
}
int LuaProgressSink::LuaSetTitle(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
wxString title(lua_tostring(L, 1), wxConvUTF8);
ps->SetTitle(title);
return 0;
}
int LuaProgressSink::LuaGetCancelled(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
lua_pushboolean(L, ps->cancelled);
return 1;
}
int LuaProgressSink::LuaDebugOut(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
wxString msg(lua_tostring(L, 1), wxConvUTF8);
ps->AddDebugOutput(msg);
return 0;
}
int LuaProgressSink::LuaDisplayDialog(lua_State *L)
{
LuaProgressSink *ps = GetObjPointer(L, lua_upvalueindex(1));
// Check that two arguments were actually given
// If only one, add another empty table for buttons
if (lua_gettop(L) == 1) {
lua_newtable(L);
}
// If more than two, remove the excess
if (lua_gettop(L) > 2) {
lua_settop(L, 2);
}
// Send the "show dialog" event
// See comments in auto4_base.h for more info on this synchronisation
ShowConfigDialogEvent evt;
LuaConfigDialog dlg(L, true); // magically creates the config dialog structure etc
evt.config_dialog = &dlg;
wxSemaphore sema(0, 1);
evt.sync_sema = &sema;
ps->AddPendingEvent(evt);
sema.Wait();
// more magic: puts two values on stack: button pushed and table with control results
return dlg.LuaReadBack(L);
}
// Factory class for Lua scripts
// Not declared in header, since it doesn't need to be accessed from outside
// except through polymorphism
class LuaScriptFactory : public ScriptFactory {
public:
LuaScriptFactory()
{
engine_name = _T("Lua");
filename_pattern = _T("*.lua");
Register(this);
}
virtual Script* Produce(const wxString &filename) const
{
// Just check if file extension is .lua
// Reject anything else
if (filename.Right(4).Lower() == _T(".lua")) {
return new LuaScript(filename);
} else {
return 0;
}
}
};
LuaScriptFactory *_script_factory;
};
void Initialise_Auto4Lua()
{
Automation4::_script_factory = new Automation4::LuaScriptFactory;
}

244
core/auto4_lua.h Normal file
View file

@ -0,0 +1,244 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#pragma once
#ifndef _AUTO4_LUA_H
#define _AUTO4_LUA_H
#include "auto4_base.h"
#include <wx/thread.h>
#include <wx/event.h>
#include <lua.h>
#include <lauxlib.h>
class wxWindow;
namespace Automation4 {
// Provides access to an AssFile object (and all lines contained) for a Lua script
class LuaAssFile {
private:
AssFile *ass;
lua_State *L;
bool can_modify;
bool can_set_undo;
void CheckAllowModify(); // throws an error if modification is disallowed
// keep a cursor of last accessed item to avoid walking over the entire file on every access
std::list<AssEntry*>::iterator last_entry_ptr;
int last_entry_id;
void GetAssEntry(int n); // set last_entry_ptr to point to item n
static int ObjectIndexRead(lua_State *L);
static int ObjectIndexWrite(lua_State *L);
static int ObjectGetLen(lua_State *L);
static int ObjectDelete(lua_State *L);
static int ObjectDeleteRange(lua_State *L);
static int ObjectAppend(lua_State *L);
static int ObjectInsert(lua_State *L);
static int ObjectGarbageCollect(lua_State *L);
static int LuaParseTagData(lua_State *L);
static int LuaUnparseTagData(lua_State *L);
static int LuaParseKaraokeData(lua_State *L);
static int LuaSetUndoPoint(lua_State *L);
static LuaAssFile *GetObjPointer(lua_State *L, int idx);
~LuaAssFile();
public:
static void AssEntryToLua(lua_State *L, AssEntry *e); // makes a Lua representation of AssEntry and places on the top of the stack
static AssEntry *LuaToAssEntry(lua_State *L); // assumes a Lua representation of AssEntry on the top of the stack, and creates an AssEntry object of it
LuaAssFile(lua_State *_L, AssFile *_ass, bool _can_modify, bool _can_set_undo);
};
class LuaConfigDialog;
// Provides progress UI and control functions for a Lua script
class LuaProgressSink : public ProgressSink {
private:
lua_State *L;
static int LuaSetProgress(lua_State *L);
static int LuaSetTask(lua_State *L);
static int LuaSetTitle(lua_State *L);
static int LuaGetCancelled(lua_State *L);
static int LuaDebugOut(lua_State *L);
static int LuaDisplayDialog(lua_State *L);
public:
LuaProgressSink(lua_State *_L, wxWindow *parent, bool allow_config_dialog = true);
virtual ~LuaProgressSink();
static LuaProgressSink* GetObjPointer(lua_State *L, int idx);
};
// Provides Config UI functions for a Lua script
class LuaConfigDialogControl {
public:
wxControl *cw; // control window
wxString name, hint;
int x, y, width, height;
virtual wxControl *Create(wxWindow *parent) = 0;
virtual void ControlReadBack() = 0;
virtual void LuaReadBack(lua_State *L) = 0;
LuaConfigDialogControl(lua_State *L);
};
class LuaConfigDialog : public ScriptConfigDialog {
private:
std::vector<LuaConfigDialogControl*> controls;
std::vector<wxString> buttons;
bool use_buttons;
class ButtonEventHandler : public wxEvtHandler {
public:
int *button_pushed;
void OnButtonPush(wxCommandEvent &evt);
};
ButtonEventHandler *button_event;
int button_pushed;
protected:
wxWindow* CreateWindow(wxWindow *parent);
public:
LuaConfigDialog(lua_State *_L, bool include_buttons);
virtual ~LuaConfigDialog();
int LuaReadBack(lua_State *L); // read back internal structure to lua structures
void ReadBack(); // from auto4 base
};
// Second base-class for Lua implemented Features
class LuaFeature : public virtual Feature {
protected:
lua_State *L;
int myid;
LuaFeature(lua_State *_L, ScriptFeatureClass _featureclass, const wxString &_name);
void RegisterFeature();
void GetFeatureFunction(int functionid);
void CreateIntegerArray(std::vector<int> &ints);
void ThrowError();
};
// Class of Lua scripts
class LuaScript : public Script {
friend class LuaFeature;
private:
lua_State *L;
void Create(); // load script and create internal structures etc.
void Destroy(); // destroy internal structures, unreg features and delete environment
static LuaScript* GetScriptObject(lua_State *L);
static int LuaTextExtents(lua_State *L);
static int LuaInclude(lua_State *L);
public:
LuaScript(const wxString &filename);
virtual ~LuaScript();
virtual void Reload();
};
// A single call to a Lua function, run inside a separate thread.
// This object should be created on the stack in the function that does the call.
class LuaThreadedCall : public wxThread {
private:
lua_State *L;
int nargs;
int nresults;
public:
LuaThreadedCall(lua_State *_L, int _nargs, int _nresults);
virtual ExitCode Entry();
};
// Implementation of the Macro Feature for Lua scripts
class LuaFeatureMacro : public FeatureMacro, LuaFeature {
private:
bool no_validate;
protected:
LuaFeatureMacro(const wxString &_name, const wxString &_description, MacroMenu _menu, lua_State *_L);
public:
static int LuaRegister(lua_State *L);
virtual ~LuaFeatureMacro() { }
virtual bool Validate(AssFile *subs, std::vector<int> &selected, int active);
virtual void Process(AssFile *subs, std::vector<int> &selected, int active, wxWindow *progress_parent);
};
// Implementation of the Export Filter Feature for Lua scripts
class LuaFeatureFilter : public FeatureFilter, LuaFeature {
private:
bool has_config;
LuaConfigDialog *config_dialog;
protected:
LuaFeatureFilter(const wxString &_name, const wxString &_description, int merit, lua_State *_L);
ScriptConfigDialog* GenerateConfigDialog(wxWindow *parent);
void Init();
public:
static int LuaRegister(lua_State *L);
void ProcessSubs(AssFile *subs, wxWindow *export_dialog);
};
};
// More or less dummy-function to make sure auto4_lua.cpp is linked in
void Initialise_Auto4Lua();
#endif

954
core/auto4_lua_assfile.cpp Normal file
View file

@ -0,0 +1,954 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#include "auto4_lua.h"
#include "ass_dialogue.h"
#include "ass_style.h"
#include "ass_file.h"
#include "ass_override.h"
#include <lualib.h>
#include <lauxlib.h>
#include <assert.h>
#include <algorithm>
namespace Automation4 {
// LuaAssFile
void LuaAssFile::CheckAllowModify()
{
if (can_modify)
return;
lua_pushstring(L, "Attempt to modify subtitles in read-only feature context.");
lua_error(L);
}
void LuaAssFile::AssEntryToLua(lua_State *L, AssEntry *e)
{
lua_newtable(L);
wxString section(e->group);
lua_pushstring(L, section.mb_str(wxConvUTF8));
lua_setfield(L, -2, "section");
wxString raw(e->GetEntryData());
lua_pushstring(L, raw.mb_str(wxConvUTF8));
lua_setfield(L, -2, "raw");
if (raw.Trim().IsEmpty()) {
lua_pushstring(L, "clear");
} else if (raw[0] == _T(';')) {
// "text" field, same as "raw" but with semicolon stripped
wxString text(raw, 1, raw.size()-1);
lua_pushstring(L, text.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text");
lua_pushstring(L, "comment");
} else if (raw[0] == _T('[')) {
lua_pushstring(L, "head");
} else if (section.Lower() == _T("[script info]")) {
// assumed "info" class
// first "key"
wxString key = raw.BeforeFirst(_T(':'));
lua_pushstring(L, key.mb_str(wxConvUTF8));
lua_setfield(L, -2, "key");
// then "value"
wxString value = raw.AfterFirst(_T(':'));
lua_pushstring(L, value.mb_str(wxConvUTF8));
lua_setfield(L, -2, "value");
lua_pushstring(L, "info");
} else if (raw.Left(7).Lower() == _T("format:")) {
// TODO: parse the format line; just use a tokenizer
lua_pushstring(L, "format");
} else if (e->GetType() == ENTRY_DIALOGUE) {
AssDialogue *dia = e->GetAsDialogue(e);
lua_pushboolean(L, (int)dia->Comment);
lua_setfield(L, -2, "comment");
lua_pushnumber(L, dia->Layer);
lua_setfield(L, -2, "layer");
lua_pushnumber(L, dia->Start.GetMS());
lua_setfield(L, -2, "start_time");
lua_pushnumber(L, dia->End.GetMS());
lua_setfield(L, -2, "end_time");
lua_pushstring(L, dia->Style.mb_str(wxConvUTF8));
lua_setfield(L, -2, "style");
lua_pushstring(L, dia->Actor.mb_str(wxConvUTF8));
lua_setfield(L, -2, "actor");
lua_pushnumber(L, dia->MarginL);
lua_setfield(L, -2, "margin_l");
lua_pushnumber(L, dia->MarginR);
lua_setfield(L, -2, "margin_r");
lua_pushnumber(L, dia->MarginV); // duplicating MarginV to margin_t and margin_b here
lua_setfield(L, -2, "margin_t");
lua_pushnumber(L, dia->MarginV);
lua_setfield(L, -2, "margin_b");
lua_pushstring(L, dia->Effect.mb_str(wxConvUTF8));
lua_setfield(L, -2, "effect");
lua_pushstring(L, ""); // tentative AS5 field
lua_setfield(L, -2, "userdata");
lua_pushstring(L, dia->Text.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text");
lua_pushstring(L, "dialogue");
} else if (e->GetType() == ENTRY_STYLE) {
AssStyle *sty = e->GetAsStyle(e);
lua_pushstring(L, sty->name.mb_str(wxConvUTF8));
lua_setfield(L, -2, "name");
lua_pushstring(L, sty->font.mb_str(wxConvUTF8));
lua_setfield(L, -2, "fontname");
lua_pushnumber(L, sty->fontsize);
lua_setfield(L, -2, "fontsize");
lua_pushstring(L, sty->primary.GetASSFormatted(true).mb_str(wxConvUTF8));
lua_setfield(L, -2, "color1");
lua_pushstring(L, sty->secondary.GetASSFormatted(true).mb_str(wxConvUTF8));
lua_setfield(L, -2, "color2");
lua_pushstring(L, sty->outline.GetASSFormatted(true).mb_str(wxConvUTF8));
lua_setfield(L, -2, "color3");
lua_pushstring(L, sty->shadow.GetASSFormatted(true).mb_str(wxConvUTF8));
lua_setfield(L, -2, "color4");
lua_pushboolean(L, (int)sty->bold);
lua_setfield(L, -2, "bold");
lua_pushboolean(L, (int)sty->italic);
lua_setfield(L, -2, "italic");
lua_pushboolean(L, (int)sty->underline);
lua_setfield(L, -2, "underline");
lua_pushboolean(L, (int)sty->strikeout);
lua_setfield(L, -2, "strikeout");
lua_pushnumber(L, sty->scalex);
lua_setfield(L, -2, "scale_x");
lua_pushnumber(L, sty->scaley);
lua_setfield(L, -2, "scale_y");
lua_pushnumber(L, sty->spacing);
lua_setfield(L, -2, "spacing");
lua_pushnumber(L, sty->angle);
lua_setfield(L, -2, "angle");
lua_pushnumber(L, sty->borderstyle);
lua_setfield(L, -2, "borderstyle");
lua_pushnumber(L, sty->outline_w);
lua_setfield(L, -2, "outline");
lua_pushnumber(L, sty->shadow_w);
lua_setfield(L, -2, "shadow");
lua_pushnumber(L, sty->alignment);
lua_setfield(L, -2, "align");
lua_pushnumber(L, sty->MarginL);
lua_setfield(L, -2, "margin_l");
lua_pushnumber(L, sty->MarginR);
lua_setfield(L, -2, "margin_r");
lua_pushnumber(L, sty->MarginV); // duplicating MarginV to margin_t and margin_b here
lua_setfield(L, -2, "margin_t");
lua_pushnumber(L, sty->MarginV);
lua_setfield(L, -2, "margin_b");
lua_pushnumber(L, sty->encoding);
lua_setfield(L, -2, "encoding");
lua_pushnumber(L, 2); // From STS.h: "0: window, 1: video, 2: undefined (~window)"
lua_setfield(L, -2, "relative_to");
lua_pushboolean(L, false); // vertical writing, tentative AS5 field
lua_setfield(L, -2, "vertical");
lua_pushstring(L, "style");
} else {
lua_pushstring(L, "unknown");
}
// store class of item; last thing done for each class specific code must be pushing the class name
lua_setfield(L, -2, "class");
}
AssEntry *LuaAssFile::LuaToAssEntry(lua_State *L)
{
// assume an assentry table is on the top of the stack
// convert it to a real AssEntry object, and pop the table from the stack
if (!lua_istable(L, -1)) {
lua_pushstring(L, "Can't convert a non-table value to AssEntry");
lua_error(L);
return 0;
}
lua_getfield(L, -1, "class");
if (!lua_isstring(L, -1)) {
lua_pushstring(L, "Table lacks 'class' field, can't convert to AssEntry");
lua_error(L);
return 0;
}
wxString lclass(lua_tostring(L, -1), wxConvUTF8);
lclass.MakeLower();
lua_pop(L, 1);
AssEntry *result;
#define GETSTRING(varname, fieldname, lineclass) \
lua_getfield(L, -1, fieldname); \
if (!lua_isstring(L, -1)) { \
lua_pushstring(L, "Invalid string '" fieldname "' field in '" lineclass "' class subtitle line"); \
lua_error(L); \
return 0; \
} \
wxString varname (lua_tostring(L, -1), wxConvUTF8); \
lua_pop(L, 1);
#define GETFLOAT(varname, fieldname, lineclass) \
lua_getfield(L, -1, fieldname); \
if (!lua_isnumber(L, -1)) { \
lua_pushstring(L, "Invalid number '" fieldname "' field in '" lineclass "' class subtitle line"); \
lua_error(L); \
return 0; \
} \
float varname = lua_tonumber(L, -1); \
lua_pop(L, 1);
#define GETINT(varname, fieldname, lineclass) \
lua_getfield(L, -1, fieldname); \
if (!lua_isnumber(L, -1)) { \
lua_pushstring(L, "Invalid number '" fieldname "' field in '" lineclass "' class subtitle line"); \
lua_error(L); \
return 0; \
} \
int varname = lua_tointeger(L, -1); \
lua_pop(L, 1);
#define GETBOOL(varname, fieldname, lineclass) \
lua_getfield(L, -1, fieldname); \
if (!lua_isboolean(L, -1)) { \
lua_pushstring(L, "Invalid boolean '" fieldname "' field in '" lineclass "' class subtitle line"); \
lua_error(L); \
return 0; \
} \
bool varname = !!lua_toboolean(L, -1); \
lua_pop(L, 1);
GETSTRING(section, "section", "common")
if (lclass == _T("clear")) {
result = new AssEntry(_T(""));
result->group = section;
} else if (lclass == _T("comment")) {
GETSTRING(raw, "text", "comment")
raw.Prepend(_T(";"));
result = new AssEntry(raw);
result->group = section;
} else if (lclass == _T("head")) {
result = new AssEntry(section);
result->group = section;
} else if (lclass == _T("info")) {
GETSTRING(key, "key", "info")
GETSTRING(value, "value", "info")
result = new AssEntry(wxString::Format(_T("%s: %s"), key.c_str(), value.c_str()));
result->group = _T("[Script Info]"); // just so it can be read correctly back
} else if (lclass == _T("format")) {
// ohshi- ...
// *FIXME* maybe ignore the actual data and just put some default stuff based on section?
result = new AssEntry(_T("Format: Auto4,Is,Broken"));
result->group = section;
} else if (lclass == _T("style")) {
GETSTRING(name, "name", "style")
GETSTRING(fontname, "fontname", "style")
GETFLOAT(fontsize, "fontsize", "style")
GETSTRING(color1, "color1", "style")
GETSTRING(color2, "color2", "style")
GETSTRING(color3, "color3", "style")
GETSTRING(color4, "color4", "style")
GETBOOL(bold, "bold", "style")
GETBOOL(italic, "italic", "style")
GETBOOL(underline, "underline", "style")
GETBOOL(strikeout, "strikeout", "style")
GETFLOAT(scale_x, "scale_x", "style")
GETFLOAT(scale_y, "scale_y", "style")
GETINT(spacing, "spacing", "style")
GETFLOAT(angle, "angle", "style")
GETINT(borderstyle, "borderstyle", "style")
GETFLOAT(outline, "outline", "style")
GETFLOAT(shadow, "shadow", "style")
GETINT(align, "align", "style")
GETINT(margin_l, "margin_l", "style")
GETINT(margin_r, "margin_r", "style")
GETINT(margin_t, "margin_t", "style")
//GETINT(margin_b, "margin_b", "style") // skipping for now, since it's not used anyway
GETINT(encoding, "encoding", "style")
// leaving out relative_to and vertical
AssStyle *sty = new AssStyle();
sty->name = name;
sty->font = fontname;
sty->fontsize = fontsize;
sty->primary.ParseASS(color1);
sty->secondary.ParseASS(color2);
sty->outline.ParseASS(color3);
sty->shadow.ParseASS(color4);
sty->bold = bold;
sty->italic = italic;
sty->underline = underline;
sty->strikeout = strikeout;
sty->scalex = scale_x;
sty->scaley = scale_y;
sty->spacing = spacing;
sty->angle = angle;
sty->borderstyle = borderstyle;
sty->outline_w = outline;
sty->shadow_w = shadow;
sty->alignment = align;
sty->MarginL = margin_l;
sty->MarginR = margin_r;
sty->MarginV = margin_t;
sty->encoding = encoding;
sty->UpdateData();
result = sty;
} else if (lclass == _T("styleex")) {
lua_pushstring(L, "Found line with class 'styleex' which is not supported. Wait until AS5 is a reality.");
lua_error(L);
return 0;
} else if (lclass == _T("dialogue")) {
GETBOOL(comment, "comment", "dialogue")
GETINT(layer, "layer", "dialogue")
GETINT(start_time, "start_time", "dialogue")
GETINT(end_time, "end_time", "dialogue")
GETSTRING(style, "style", "dialogue")
GETSTRING(actor, "actor", "dialogue")
GETINT(margin_l, "margin_l", "dialogue")
GETINT(margin_r, "margin_r", "dialogue")
GETINT(margin_t, "margin_t", "dialogue")
//GETINT(margin_b, "margin_b", "dialogue") // skipping for now, since it's not used anyway
GETSTRING(effect, "effect", "dialogue")
//GETSTRING(userdata, "userdata", "dialogue")
GETSTRING(text, "text", "dialogue")
AssDialogue *dia = new AssDialogue();
dia->Comment = comment;
dia->Layer = layer;
dia->Start.SetMS(start_time);
dia->End.SetMS(end_time);
dia->Style = style;
dia->Actor = actor;
dia->MarginL = margin_l;
dia->MarginR = margin_r;
dia->MarginV = margin_t;
dia->Effect = effect;
dia->Text = text;
dia->UpdateData();
result = dia;
} else {
lua_pushfstring(L, "Found line with unknown class: %s", lclass.mb_str(wxConvUTF8));
lua_error(L);
return 0;
}
#undef GETSTRING
#undef GETFLOAT
#undef GETINT
#undef GETBOOL
//lua_pop(L, 1); // the function shouldn't eat the table it converted
return result;
}
void LuaAssFile::GetAssEntry(int n)
{
entryIter e;
if (n < last_entry_id/2) {
// fastest to search from start
e = ass->Line.begin();
last_entry_id = n;
while (n-- > 0) e++;
last_entry_ptr = e;
} else if (last_entry_id + n > last_entry_id + ((int)ass->Line.size() - last_entry_id)/2) {
// fastest to search from end
int i = (int)ass->Line.size();
e = ass->Line.end();
last_entry_id = n;
while (i-- > n) e--;
last_entry_ptr = e;
} else if (last_entry_id > n) {
// search backwards from last_entry_id
e = last_entry_ptr;
while (n < last_entry_id) e--, last_entry_id--;
last_entry_ptr = e;
} else {
// search forwards from last_entry_id
e = last_entry_ptr;
// reqid and last_entry_id might be equal here, make sure the loop will still work
while (n > last_entry_id) e++, last_entry_id++;
last_entry_ptr = e;
}
}
int LuaAssFile::ObjectIndexRead(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, 1);
switch (lua_type(L, 2)) {
case LUA_TNUMBER:
{
// read an indexed AssEntry
// get requested index
int reqid = lua_tointeger(L, 2);
if (reqid <= 0 || reqid > (int)laf->ass->Line.size()) {
lua_pushfstring(L, "Requested out-of-range line from subtitle file: %d", reqid);
lua_error(L);
return 0;
}
laf->GetAssEntry(reqid-1);
laf->AssEntryToLua(L, *laf->last_entry_ptr);
return 1;
}
case LUA_TSTRING:
{
// either return n or a function doing further stuff
const char *idx = lua_tostring(L, 2);
if (strcmp(idx, "n") == 0) {
// get number of items
lua_pushnumber(L, laf->ass->Line.size());
return 1;
} else if (strcmp(idx, "delete") == 0) {
// make a "delete" function
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectDelete, 1);
return 1;
} else if (strcmp(idx, "deleterange") == 0) {
// make a "deleterange" function
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectDeleteRange, 1);
return 1;
} else if (strcmp(idx, "insert") == 0) {
// make an "insert" function
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectInsert, 1);
return 1;
} else if (strcmp(idx, "append") == 0) {
// make an "append" function
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectAppend, 1);
return 1;
} else {
// idiot
lua_pushfstring(L, "Invalid indexing in Subtitle File object: '%s'", idx);
lua_error(L);
// should never return
}
assert(false);
}
default:
{
// crap, user is stupid!
lua_pushfstring(L, "Attempt to index a Subtitle File object with value of type '%s'.", lua_typename(L, lua_type(L, 2)));
lua_error(L);
}
}
assert(false);
return 0;
}
int LuaAssFile::ObjectIndexWrite(lua_State *L)
{
// instead of implementing everything twice, just call the other modification-functions from here
// after modifying the stack to match their expectations
if (!lua_isnumber(L, 2)) {
lua_pushstring(L, "Attempt to write to non-numeric index in subtitle index");
lua_error(L);
return 0;
}
LuaAssFile *laf = GetObjPointer(L, 1);
laf->CheckAllowModify();
int n = lua_tointeger(L, 2);
if (n < 0) {
// insert line so new index is n
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectInsert, 1);
lua_pushinteger(L, -n);
lua_pushvalue(L, 3);
lua_call(L, 2, 0);
return 0;
} else if (n == 0) {
// append line to list
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectAppend, 1);
lua_pushvalue(L, 3);
lua_call(L, 1, 0);
return 0;
} else {
// replace line at index n or delete
if (!lua_isnil(L, 3)) {
// insert
AssEntry *e = LuaToAssEntry(L);
laf->GetAssEntry(n-1);
delete *laf->last_entry_ptr;
*laf->last_entry_ptr = e;
return 0;
} else {
// delete
lua_pushvalue(L, 1);
lua_pushcclosure(L, ObjectDelete, 1);
lua_pushvalue(L, 2);
lua_call(L, 1, 0);
return 0;
}
}
}
int LuaAssFile::ObjectGetLen(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, 1);
lua_pushnumber(L, laf->ass->Line.size());
return 1;
}
int LuaAssFile::ObjectDelete(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
laf->CheckAllowModify();
// get number of items to delete
int itemcount = lua_gettop(L);
std::vector<int> ids;
ids.reserve(itemcount);
// sort the item id's so we can delete from last to first to preserve original numbering
while (itemcount > 0) {
if (!lua_isnumber(L, itemcount)) {
lua_pushstring(L, "Attempt to delete non-numeric line id from Subtitle Object");
lua_error(L);
return 0;
}
int n = lua_tointeger(L, itemcount);
if (n > (int)laf->ass->Line.size() || n < 1) {
lua_pushstring(L, "Attempt to delete out of range line id from Subtitle Object");
lua_error(L);
return 0;
}
ids.push_back(n-1); // make C-style line ids
--itemcount;
}
std::sort(ids.begin(), ids.end());
// now delete the id's backwards
// start with the last one, to initialise things
laf->GetAssEntry(ids.back());
// get an iterator to it, and increase last_entry_ptr so it'll still be valid after deletion, and point to the right index
entryIter e = laf->last_entry_ptr++;
laf->ass->Line.erase(e);
int n = laf->last_entry_id;
for (int i = (int)ids.size()-2; i >= 0; --i) {
int id = ids[i];
while (id > n--) laf->last_entry_ptr--;
e = laf->last_entry_ptr++;
delete *e;
laf->ass->Line.erase(e);
}
laf->last_entry_id = n;
return 0;
}
int LuaAssFile::ObjectDeleteRange(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
laf->CheckAllowModify();
if (!lua_isnumber(L, 1) || !lua_isnumber(L, 2)) {
lua_pushstring(L, "Non-numeric argument given to deleterange");
lua_error(L);
return 0;
}
int a = lua_tointeger(L, 1), b = lua_tointeger(L, 2);
if (a < 1) a = 1;
if (b > (int)laf->ass->Line.size()) b = (int)laf->ass->Line.size();
if (b < a) return 0;
if (a == b) {
laf->GetAssEntry(a-1);
entryIter e = laf->last_entry_ptr++;
delete *e;
laf->ass->Line.erase(e);
return 0;
}
entryIter ai, bi;
laf->GetAssEntry(a-1);
ai = laf->last_entry_ptr;
laf->GetAssEntry(b-1);
bi = laf->last_entry_ptr;
laf->last_entry_ptr++;
laf->ass->Line.erase(ai, bi);
return 0;
}
int LuaAssFile::ObjectAppend(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
laf->CheckAllowModify();
int n = lua_gettop(L);
if (laf->last_entry_ptr != laf->ass->Line.begin()) {
laf->last_entry_ptr--;
laf->last_entry_id--;
}
for (int i = 1; i <= n; i++) {
lua_pushvalue(L, i);
AssEntry *e = LuaToAssEntry(L);
laf->ass->Line.push_back(e);
}
return 0;
}
int LuaAssFile::ObjectInsert(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
laf->CheckAllowModify();
if (!lua_isnumber(L, 1)) {
lua_pushstring(L, "Can't insert at non-numeric index");
lua_error(L);
return 0;
}
int n = lua_gettop(L);
laf->GetAssEntry(lua_tonumber(L, 1)-1);
for (int i = 2; i <= n; i++) {
lua_pushvalue(L, i);
AssEntry *e = LuaToAssEntry(L);
lua_pop(L, 1);
laf->ass->Line.insert(laf->last_entry_ptr, e);
laf->last_entry_id++;
}
return 0;
}
int LuaAssFile::ObjectGarbageCollect(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, 1);
delete laf;
wxLogDebug(_T(">>gc<< Garbage collected LuaAssFile"));
return 0;
}
int LuaAssFile::LuaParseTagData(lua_State *L)
{
lua_newtable(L);
// TODO
return 1;
}
int LuaAssFile::LuaUnparseTagData(lua_State *L)
{
lua_pushstring(L, "");
// TODO
return 1;
}
int LuaAssFile::LuaParseKaraokeData(lua_State *L)
{
AssEntry *e = LuaToAssEntry(L);
if (e->GetType() != ENTRY_DIALOGUE) {
delete e;
lua_pushstring(L, "Attempt to create karaoke table from non-dialogue subtitle line");
lua_error(L);
return 0;
}
AssDialogue *dia = e->GetAsDialogue(e);
dia->ParseASSTags();
int kcount = 0;
int kdur = 0;
int ktime = 0;
wxString ktag = _T("");
wxString ktext = _T("");
wxString ktext_stripped = _T("");
lua_newtable(L);
for (int i = 0; i < (int)dia->Blocks.size(); i++) {
AssDialogueBlock *block = dia->Blocks[i];
switch (block->type) {
case BLOCK_BASE:
assert(block->type != BLOCK_BASE);
break;
case BLOCK_PLAIN:
ktext += block->text;
ktext_stripped += block->text;
break;
case BLOCK_DRAWING:
// a drawing is regarded as a kind of control code here, so it's just stripped away
ktext += block->text;
break;
case BLOCK_OVERRIDE: {
bool brackets_open = false;
AssDialogueBlockOverride *ovr = block->GetAsOverride(block);
for (int j = 0; j < (int)ovr->Tags.size(); j++) {
AssOverrideTag *tag = ovr->Tags[j];
if (tag->IsValid() && tag->Name.Mid(0,2).CmpNoCase(_T("\\k")) == 0) {
// karaoke tag
if (brackets_open) {
ktext += _T("}");
brackets_open = false;
}
// store to lua
lua_newtable(L);
lua_pushnumber(L, kdur);
lua_setfield(L, -2, "duration");
lua_pushnumber(L, ktime);
lua_setfield(L, -2, "start_time");
lua_pushnumber(L, ktime+kdur);
lua_setfield(L, -2, "end_time");
lua_pushstring(L, ktag.mb_str(wxConvUTF8));
lua_setfield(L, -2, "tag");
lua_pushstring(L, ktext.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text");
lua_pushstring(L, ktext_stripped.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text_stripped");
lua_rawseti(L, -2, kcount);
// prepare new syllable
kcount++;
ktag = tag->Name.Mid(1);
// check if it's a "set time" tag, special handling for that (depends on previous syllable duration)
if (ktag == _T("kt")) {
ktime = tag->Params[0]->AsInt() * 10;
kdur = 0;
} else {
ktime += kdur; // duration of previous syllable
kdur = tag->Params[0]->AsInt() * 10;
}
ktext.clear();
ktext_stripped.clear();
} else {
// not karaoke tag
if (!brackets_open) {
ktext += _T("{");
brackets_open = true;
}
ktext += tag->ToString();
}
}
if (brackets_open) {
ktext += _T("}");
brackets_open = false;
}
break;}
}
}
dia->ClearBlocks();
// store final syllable/block to lua
lua_newtable(L);
lua_pushnumber(L, kdur);
lua_setfield(L, -2, "duration");
lua_pushnumber(L, ktime);
lua_setfield(L, -2, "start_time");
lua_pushnumber(L, ktime+kdur);
lua_setfield(L, -2, "end_time");
lua_pushstring(L, ktag.mb_str(wxConvUTF8));
lua_setfield(L, -2, "tag");
lua_pushstring(L, ktext.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text");
lua_pushstring(L, ktext_stripped.mb_str(wxConvUTF8));
lua_setfield(L, -2, "text_stripped");
lua_rawseti(L, -2, kcount);
delete dia;
return 1;
}
int LuaAssFile::LuaSetUndoPoint(lua_State *L)
{
LuaAssFile *laf = GetObjPointer(L, lua_upvalueindex(1));
if (!laf->can_set_undo) {
lua_pushstring(L, "Attempt to set an undo point in a context without undo-support.");
lua_error(L);
return 0;
}
wxString description;
if (lua_isstring(L, 1)) {
description = wxString(lua_tostring(L, 1), wxConvUTF8);
lua_pop(L, 1);
}
AssFile::FlagAsModified(description);
laf->ass = AssFile::top; // make sure we're still working on the most recent undo point
return 0;
}
LuaAssFile *LuaAssFile::GetObjPointer(lua_State *L, int idx)
{
assert(lua_type(L, idx) == LUA_TUSERDATA);
void *ud = lua_touserdata(L, idx);
return *((LuaAssFile**)ud);
}
LuaAssFile::~LuaAssFile()
{
}
LuaAssFile::LuaAssFile(lua_State *_L, AssFile *_ass, bool _can_modify, bool _can_set_undo)
: ass(_ass)
, L(_L)
, can_modify(_can_modify)
, can_set_undo(_can_set_undo)
{
// prepare cursor
last_entry_ptr = ass->Line.begin();
last_entry_id = 0;
// prepare userdata object
void *ud = lua_newuserdata(L, sizeof(LuaAssFile*));
*((LuaAssFile**)ud) = this;
// make the metatable
lua_newtable(L);
lua_pushcfunction(L, ObjectIndexRead);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, ObjectIndexWrite);
lua_setfield(L, -2, "__newindex");
lua_pushcfunction(L, ObjectGetLen);
lua_setfield(L, -2, "__len");
lua_pushcfunction(L, ObjectGarbageCollect);
lua_setfield(L, -2, "__gc");
lua_setmetatable(L, -2);
// register misc functions
// assume the "aegisub" global table exists
lua_getglobal(L, "aegisub");
assert(lua_type(L, -2) == LUA_TUSERDATA);
lua_pushvalue(L, -2); // the userdata object
lua_pushcclosure(L, LuaParseTagData, 1);
lua_setfield(L, -2, "parse_tag_data");
assert(lua_type(L, -2) == LUA_TUSERDATA);
lua_pushvalue(L, -2);
lua_pushcclosure(L, LuaUnparseTagData, 1);
lua_setfield(L, -2, "unparse_tag_data");
assert(lua_type(L, -2) == LUA_TUSERDATA);
lua_pushvalue(L, -2);
lua_pushcclosure(L, LuaParseKaraokeData, 1);
lua_setfield(L, -2, "parse_karaoke_data");
assert(lua_type(L, -2) == LUA_TUSERDATA);
lua_pushvalue(L, -2);
lua_pushcclosure(L, LuaSetUndoPoint, 1);
lua_setfield(L, -2, "set_undo_point");
lua_pop(L, 1);
}
};

592
core/auto4_lua_dialog.cpp Normal file
View file

@ -0,0 +1,592 @@
// Copyright (c) 2006, Niels Martin Hansen
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// * Neither the name of the Aegisub Group nor the names of its contributors
// may be used to endorse or promote products derived from this software
// without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// -----------------------------------------------------------------------------
//
// AEGISUB
//
// Website: http://aegisub.cellosoft.com
// Contact: mailto:jiifurusu@gmail.com
//
#include "auto4_lua.h"
#include <lualib.h>
#include <lauxlib.h>
#include <wx/window.h>
#include <wx/spinctrl.h>
#include <wx/gbsizer.h>
#include <wx/button.h>
#include <wx/validate.h>
#include <assert.h>
namespace Automation4 {
// LuaConfigDialogControl
LuaConfigDialogControl::LuaConfigDialogControl(lua_State *L)
{
// Assume top of stack is a control table (don't do checking)
lua_getfield(L, -1, "name");
if (lua_isstring(L, -1)) {
name = wxString(lua_tostring(L, -1), wxConvUTF8);
} else {
name = _T("");
}
lua_pop(L, 1);
lua_getfield(L, -1, "x");
if (lua_isnumber(L, -1)) {
x = lua_tointeger(L, -1);
if (x < 0) x = 0;
} else {
x = 0;
}
lua_pop(L, 1);
lua_getfield(L, -1, "y");
if (lua_isnumber(L, -1)) {
y = lua_tointeger(L, -1);
if (y < 0) y = 0;
} else {
y = 0;
}
lua_pop(L, 1);
lua_getfield(L, -1, "width");
if (lua_isnumber(L, -1)) {
width = lua_tointeger(L, -1);
if (width < 1) width = 1;
} else {
width = 1;
}
lua_pop(L, 1);
lua_getfield(L, -1, "height");
if (lua_isnumber(L, -1)) {
height = lua_tointeger(L, -1);
if (height < 1) height = 1;
} else {
height = 1;
}
lua_pop(L, 1);
lua_getfield(L, -1, "hint");
if (lua_isstring(L, -1)) {
hint = wxString(lua_tostring(L, -1), wxConvUTF8);
} else {
hint = _T("");
}
lua_pop(L, 1);
wxLogDebug(_T("created control: '%s', (%d,%d)(%d,%d), '%s'"), name.c_str(), x, y, width, height, hint.c_str());
}
namespace LuaControl {
// Label
class Label : public LuaConfigDialogControl {
public:
wxString label;
Label(lua_State *L)
: LuaConfigDialogControl(L)
{
lua_getfield(L, -1, "label");
label = wxString(lua_tostring(L, -1), wxConvUTF8);
lua_pop(L, 1);
}
wxControl *Create(wxWindow *parent)
{
return cw = new wxStaticText(parent, -1, label);
}
void ControlReadBack()
{
// Nothing here
}
void LuaReadBack(lua_State *L)
{
// Label doesn't produce output, so let it be nil
lua_pushnil(L);
}
};
// Basic edit
class Edit : public LuaConfigDialogControl {
public:
wxString text;
Edit(lua_State *L)
: LuaConfigDialogControl(L)
{
lua_getfield(L, -1, "text");
text = wxString(lua_tostring(L, -1), wxConvUTF8);
lua_pop(L, 1);
}
wxControl *Create(wxWindow *parent)
{
return cw = new wxTextCtrl(parent, -1, text, wxDefaultPosition, wxDefaultSize, 0);
}
void ControlReadBack()
{
text = ((wxTextCtrl*)cw)->GetValue();
}
void LuaReadBack(lua_State *L)
{
lua_pushstring(L, text.mb_str(wxConvUTF8));
}
};
// Multiline edit
class Textbox : public Edit {
public:
Textbox(lua_State *L)
: Edit(L)
{
// Nothing more
}
wxControl *Create(wxWindow *parent)
{
cw = new wxTextCtrl(parent, -1, text, wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE);
cw->SetMinSize(wxSize(0, 30));
return cw;
}
};
// Integer only edit
class IntEdit : public Edit {
public:
int value;
bool hasspin;
int min, max;
IntEdit(lua_State *L)
: Edit(L)
{
lua_getfield(L, -1, "value");
value = lua_tointeger(L, -1);
lua_pop(L, 1);
hasspin = false;
lua_getfield(L, -1, "min");
if (!lua_isnumber(L, -1))
goto nospin;
min = lua_tointeger(L, -1);
lua_pop(L, 1);
lua_getfield(L, -1, "max");
if (!lua_isnumber(L, -1))
goto nospin;
max = lua_tointeger(L, -1);
lua_pop(L, 1);
hasspin = true;
nospin:
if (!hasspin) {
lua_pop(L, 1);
}
}
typedef wxValidator IntTextValidator; // TODO
wxControl *Create(wxWindow *parent)
{
if (hasspin) {
return cw = new wxSpinCtrl(parent, -1, wxString::Format(_T("%d"), value), wxDefaultPosition, wxDefaultSize, min, max, value);
} else {
return cw = new wxTextCtrl(parent, -1, text, wxDefaultPosition, wxDefaultSize, 0, IntTextValidator());
}
}
void ControlReadBack()
{
if (hasspin) {
value = ((wxSpinCtrl*)cw)->GetValue();
} else {
long newval;
text = ((wxTextCtrl*)cw)->GetValue();
if (text.ToLong(&newval)) {
value = newval;
}
}
}
void LuaReadBack(lua_State *L)
{
lua_pushinteger(L, value);
}
};
// Float only edit
class FloatEdit : public Edit {
public:
float value;
// FIXME: Can't support spin button atm
FloatEdit(lua_State *L)
: Edit(L)
{
lua_getfield(L, -1, "value");
value = lua_tointeger(L, -1);
lua_pop(L, 1);
// TODO: spin button support
}
typedef wxValidator FloatTextValidator;
wxControl *Create(wxWindow *parent)
{
return cw = new wxTextCtrl(parent, -1, text, wxDefaultPosition, wxDefaultSize, 0, FloatTextValidator());
}
void ControlReadBack()
{
double newval;
text = ((wxTextCtrl*)cw)->GetValue();
if (text.ToDouble(&newval)) {
value = newval;
}
}
void LuaReadBack(lua_State *L)
{
lua_pushnumber(L, value);
}
};
// Dropdown
class Dropdown : public LuaConfigDialogControl {
public:
wxArrayString items;
wxString value;
Dropdown(lua_State *L)
: LuaConfigDialogControl(L)
{
lua_getfield(L, -1, "value");
value = wxString(lua_tostring(L, -1), wxConvUTF8);
lua_pop(L, 1);
lua_getfield(L, -1, "items");
lua_pushnil(L);
while (lua_next(L, -2)) {
if (lua_isstring(L, -1)) {
items.Add(wxString(lua_tostring(L, -1), wxConvUTF8));
}
lua_pop(L, 1);
}
lua_pop(L, 1);
}
wxControl *Create(wxWindow *parent)
{
return cw = new wxComboBox(parent, -1, value, wxDefaultPosition, wxDefaultSize, items, wxCB_READONLY);
}
void ControlReadBack()
{
value = ((wxComboBox*)cw)->GetValue();
}
void LuaReadBack(lua_State *L)
{
lua_pushstring(L, value.mb_str(wxConvUTF8));
}
};
// Checkbox
class Checkbox : public LuaConfigDialogControl {
public:
wxString label;
bool value;
Checkbox(lua_State *L)
: LuaConfigDialogControl(L)
{
lua_getfield(L, -1, "label");
label = wxString(lua_tostring(L, -1), wxConvUTF8);
lua_pop(L, 1);
lua_getfield(L, -1, "value");
value = lua_toboolean(L, -1) != 0;
lua_pop(L, 1);
}
wxControl *Create(wxWindow *parent)
{
return cw = new wxCheckBox(parent, -1, label);
}
void ControlReadBack()
{
value = ((wxCheckBox*)cw)->GetValue();
}
void LuaReadBack(lua_State *L)
{
lua_pushboolean(L, value);
}
};
};
// LuaConfigDialog
LuaConfigDialog::LuaConfigDialog(lua_State *L, bool include_buttons)
: use_buttons(include_buttons)
{
wxLogDebug(_T("creating LuaConfigDialog, this addr is %p"), this);
button_pushed = 0;
if (include_buttons) {
if (!lua_istable(L, -1))
// Just to avoid deeper indentation...
goto skipbuttons;
// Iterate over items in table
lua_pushnil(L); // initial key
while (lua_next(L, -2)) {
// Simply skip invalid items... FIXME, warn here?
if (lua_isstring(L, -1)) {
wxString s(lua_tostring(L, -1), wxConvUTF8);
buttons.push_back(s);
}
lua_pop(L, 1);
}
skipbuttons:
lua_pop(L, 1);
}
// assume top of stack now contains a dialog table
if (!lua_istable(L, -1)) {
lua_pushstring(L, "Cannot create config dialog from something non-table");
lua_error(L);
assert(false);
}
// Ok, so there is a table with controls
lua_pushnil(L); // initial key
while (lua_next(L, -2)) {
if (lua_istable(L, -1)) {
// Get control class
lua_getfield(L, -1, "class");
if (!lua_isstring(L, -1))
goto badcontrol;
wxString controlclass(lua_tostring(L, -1), wxConvUTF8);
controlclass.LowerCase();
lua_pop(L, 1);
LuaConfigDialogControl *ctl;
// Check control class and create relevant control
if (controlclass == _T("label")) {
ctl = new LuaControl::Label(L);
} else if (controlclass == _T("edit")) {
ctl = new LuaControl::Edit(L);
} else if (controlclass == _T("intedit")) {
ctl = new LuaControl::IntEdit(L);
} else if (controlclass == _T("floatedit")) {
ctl = new LuaControl::FloatEdit(L);
} else if (controlclass == _T("textbox")) {
ctl = new LuaControl::Textbox(L);
} else if (controlclass == _T("dropdown")) {
ctl = new LuaControl::Dropdown(L);
} else if (controlclass == _T("checkbox")) {
ctl = new LuaControl::Checkbox(L);
} else if (controlclass == _T("color")) {
// FIXME
ctl = new LuaControl::Edit(L);
} else if (controlclass == _T("coloralpha")) {
// FIXME
ctl = new LuaControl::Edit(L);
} else if (controlclass == _T("alpha")) {
// FIXME
ctl = new LuaControl::Edit(L);
} else {
goto badcontrol;
}
controls.push_back(ctl);
} else {
badcontrol:
// not a control...
// FIXME, better error reporting?
lua_pushstring(L, "bad control table entry");
lua_error(L);
}
lua_pop(L, 1);
}
}
LuaConfigDialog::~LuaConfigDialog()
{
for (size_t i = 0; i < controls.size(); ++i)
delete controls[i];
}
wxWindow* LuaConfigDialog::CreateWindow(wxWindow *parent)
{
wxWindow *w = new wxPanel(parent);
wxGridBagSizer *s = new wxGridBagSizer(4, 4);
for (size_t i = 0; i < controls.size(); ++i) {
LuaConfigDialogControl *c = controls[i];
c->Create(w);
if (dynamic_cast<LuaControl::Label*>(c)) {
s->Add(c->cw, wxGBPosition(c->y, c->x), wxGBSpan(c->height, c->width), wxALIGN_CENTRE_VERTICAL|wxALIGN_LEFT);
} else {
s->Add(c->cw, wxGBPosition(c->y, c->x), wxGBSpan(c->height, c->width), wxEXPAND);
}
}
if (use_buttons) {
wxStdDialogButtonSizer *bs = new wxStdDialogButtonSizer();
if (buttons.size() > 0) {
wxLogDebug(_T("creating user buttons"));
for (size_t i = 0; i < buttons.size(); ++i) {
wxLogDebug(_T("button '%s' gets id %d"), buttons[i].c_str(), 1001+(wxWindowID)i);
bs->Add(new wxButton(w, 1001+(wxWindowID)i, buttons[i]));
}
} else {
wxLogDebug(_T("creating default buttons"));
bs->Add(new wxButton(w, wxID_OK));
bs->Add(new wxButton(w, wxID_CANCEL));
}
bs->Realize();
button_event = new ButtonEventHandler();
button_event->button_pushed = &button_pushed;
// passing button_event as userdata because wx will then delete it
w->Connect(wxID_ANY, wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(LuaConfigDialog::ButtonEventHandler::OnButtonPush), button_event, button_event);
wxLogDebug(_T("set event handler, this addr is %p"), this);
wxBoxSizer *ms = new wxBoxSizer(wxVERTICAL);
ms->Add(s, 0, wxBOTTOM, 5);
ms->Add(bs);
w->SetSizerAndFit(ms);
} else {
w->SetSizerAndFit(s);
}
return w;
}
int LuaConfigDialog::LuaReadBack(lua_State *L)
{
// First read back which button was pressed, if any
if (use_buttons) {
wxLogDebug(_T("reading back button_pushed"));
int btn = button_pushed;
if (btn == 0) {
wxLogDebug(_T("was zero, cancelled"));
// Always cancel/closed
lua_pushboolean(L, 0);
} else {
wxLogDebug(_T("nonzero, something else: %d"), btn);
if (buttons.size() > 0) {
wxLogDebug(_T("user button: %s"), buttons[btn-1].c_str());
// button_pushed is index+1 to reserve 0 for Cancel
lua_pushstring(L, buttons[btn-1].mb_str(wxConvUTF8));
} else {
wxLogDebug(_T("default button, must be Ok"));
// Cancel case already covered, must be Ok then
lua_pushboolean(L, 1);
}
}
}
// Then read controls back
lua_newtable(L);
for (size_t i = 0; i < controls.size(); ++i) {
controls[i]->LuaReadBack(L);
lua_setfield(L, -2, controls[i]->name.mb_str(wxConvUTF8));
}
if (use_buttons) {
return 2;
} else {
return 1;
}
}
void LuaConfigDialog::ReadBack()
{
for (size_t i = 0; i < controls.size(); ++i) {
controls[i]->ControlReadBack();
}
}
void LuaConfigDialog::ButtonEventHandler::OnButtonPush(wxCommandEvent &evt)
{
// Let button_pushed == 0 mean "cancelled", such that pushing Cancel or closing the dialog
// will both result in button_pushed == 0
if (evt.GetId() == wxID_OK) {
wxLogDebug(_T("was wxID_OK"));
*button_pushed = 1;
} else if (evt.GetId() == wxID_CANCEL) {
wxLogDebug(_T("was wxID_CANCEL"));
*button_pushed = 0;
} else {
wxLogDebug(_T("was user button"));
// Therefore, when buttons are numbered from 1001 to 1000+n, make sure to set it to i+1
*button_pushed = evt.GetId() - 1000;
evt.SetId(wxID_OK); // hack to make sure the dialog will be closed
}
wxLogDebug(_T("button_pushed set to %d"), *button_pushed);
evt.Skip();
}
};

View file

@ -4,6 +4,10 @@ Please visit http://aegisub.net to download latest version
= 1.11 beta - 2006.xx.xx =========================== = 1.11 beta - 2006.xx.xx ===========================
- New Aegisub logo. (AMZ) - New Aegisub logo. (AMZ)
- Automation 4 has replaced Automation 3, see the help file for more details (jfs)
o Automation 4 Lua uses Lua 5.1 instead of 5.0, meaning some new language features
o It is now possible to write macros that manipulates subtitles directly
o Scripts have full access to the entire subtitle file, not just the "Events" section
- Support reading SSA/ASS files with intermixed V4 and V4+ Styles sections (jfs) - Support reading SSA/ASS files with intermixed V4 and V4+ Styles sections (jfs)
- Fixed loading of sections with unexpected cases. (AMZ) - Fixed loading of sections with unexpected cases. (AMZ)
- Changes to Audio Spectrum: (jfs) - Changes to Audio Spectrum: (jfs)