Changeset 104:ee209602770d
- Timestamp:
- 11/26/08 08:07:46 (1 month ago)
- Files:
-
- data/L10n/FontOptions.mtt (modified) (1 diff)
- data/L10n/MiscOptions.mtt (modified) (1 diff)
- data/L10n/VideoOptions.mtt (modified) (1 diff)
- data/conf/gui.mtt (modified) (1 diff)
- examples/guiDemo.d (modified) (1 diff)
- mde/font/FontTexture.d (modified) (1 diff)
- mde/gui/content/Content.d (modified) (4 diffs)
- mde/gui/content/Items.d (modified) (3 diffs)
- mde/lookup/Options.d (modified) (19 diffs)
- mde/setup/Screen.d (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
data/L10n/FontOptions.mtt
r95 r104 1 1 {MT01} 2 2 {en-GB} 3 <entry|FontOptions={0:"Font options"}> 3 4 <entry|lcdFilter={0:"LCD filtering",1:"Enable or disable sub-pixel rendering. Note that the FreeType library may be compiled without support due to patent issues."}> 4 5 <entry|renderMode={0:"Font rendering mode",1:"Controls how fonts are rendered: in gray-scale, or for LCDs (with a horizontal (usual) or vertical layout, with an RGB (usual) or BGR sub-pixel mode."}> data/L10n/MiscOptions.mtt
r98 r104 1 1 {MT01} 2 2 {en-GB} 3 <entry|MiscOptions={0:"Miscellaneous options"}> 3 4 <entry|maxThreads={0:"Max threads",1:"Maximum number of threads to use in mde (currently only applies to init stages run in parallel)."}> 4 5 <entry|logLevel={0:"Logging level",1:"Lowest level to log messages; 0=trace, 1=info (default), 2=warn, 3=error, 4=fatal, 5=none."}> data/L10n/VideoOptions.mtt
r95 r104 1 1 {MT01} 2 2 {en-GB} 3 <entry|VideoOptions={0:"Video options"}> 3 4 <entry|fullscreen={0:"Fullscreen",1:"If true use the whole screen, if false use a window."}> 4 5 <entry|hardware={0:"Hardware",1:"Create the video surface in hardware or software memory."}> data/conf/gui.mtt
r103 r104 6 6 <WidgetData|square={0:[0x10,10,10]}> 7 7 <WidgetData|blank={0:[0x2]}> 8 <WidgetData|opts={0:[0x2031,0x6030,0],1:["Options.VideoOptions","optDBox"]}> 8 <WidgetData|opts={0:[0x2031,0x6030,0],1:["Options","optCls"]}> 9 <WidgetData|optCls={0:[0xC100,0,2,1],1:["optName","optVars"]}> 10 <WidgetData|optVars={0:[0x6030,0],1:["optDBox"]}> 9 11 <WidgetData|optDBox={0:[0xC100,1,2,1],1:["optBox","optDesc"]}> 10 12 <WidgetData|optBox={0:[0xC100,1,1,3],1:["optName","optSep","optVal"]}> examples/guiDemo.d
r98 r104 68 68 //END Main loop setup 69 69 70 double pollInterval = miscOpts.pollInterval(); 70 71 while (run) { 71 mainSchedule.execute (Clock.now());72 73 Thread.sleep (miscOpts.pollInterval()); // sleep this many seconds72 mainSchedule.execute (Clock.now()); 73 74 Thread.sleep (pollInterval); // sleep this many seconds 74 75 } 75 76 mde/font/FontTexture.d
r100 r104 465 465 466 466 static this() { 467 fontOpts = new FontOptions; 468 Options.addOptionsClass (fontOpts, "FontOptions"); 467 fontOpts = new FontOptions ("FontOptions"); 469 468 } 470 469 } mde/gui/content/Content.d
r103 r104 66 66 class ContentList : IContent 67 67 { 68 this (IContent[] list = null ) {68 this (IContent[] list = null, char[] n = null, char[] d = null) { 69 69 list_ = list; 70 } 71 this (ValueContent[char[]] l) { 70 name_ = n; 71 desc_ = d; 72 } 73 this (ValueContent[char[]] l, char[] n = null, char[] d = null) { 72 74 list_.length = l.length; 73 75 size_t i; 74 76 foreach (c; l) 75 77 list_[i++] = c; 78 name_ = n; 79 desc_ = d; 76 80 } 77 81 78 82 char[] toString (uint i) { 79 83 return i == 0 ? Int.toString (list_.length) ~ " elements" 80 : i == 1 ? "ContentList" 81 : null; 84 : i == 1 ? name_ 85 : i == 2 ? desc_ 86 : null; 82 87 } 83 88 … … 92 97 protected: 93 98 IContent[] list_; 99 char[] name_, desc_; // name and description 94 100 } 95 101 … … 136 142 /// Get the text. 137 143 char[] toString (uint i) { 138 return (i == 0)? sv139 : (i == 1)? name_140 : (i == 2)? desc_144 return i == 0 ? sv 145 : i == 1 ? name_ 146 : i == 2 ? desc_ 141 147 : null; 142 148 } … … 202 208 size_t pos; // editing position; used by keyStroke 203 209 char[] symb; 204 char[] name_, desc_;// name and description , loaded by lookup.Translation210 char[] name_, desc_;// name and description 205 211 } 206 212 mde/gui/content/Items.d
r103 r104 25 25 import mde.lookup.Translation; 26 26 27 debug { 28 import tango.util.log.Log : Log, Logger; 29 private Logger logger; 30 static this () { 31 logger = Log.getLogger ("mde.gui.content.Items"); 32 } 33 } 34 27 35 /** Get a specific content item. 28 36 * … … 38 46 /** Same as calling get("Options."~item). */ 39 47 IContent getOptions (char[] item) { 48 if (item is null) { 49 IContent[] list; 50 list.length = Options.optionsClasses.length; 51 size_t i; 52 foreach (n,opts; Options.optionsClasses) { 53 if (opts.name is null) loadTransl (opts, n); 54 list[i++] = new ContentList (opts.content, opts.name, opts.desc); 55 } 56 57 return new ContentList (list, "Options"); 58 } 40 59 char[] h = head (item); 41 60 auto p = h in Options.optionsClasses; 42 61 if (p) { 43 if (!p.transLoaded) { 44 Translation trans = Translation.load ("L10n/"~h); 45 foreach (s, v; p.content) { 46 Translation.Entry transled = trans.getStruct (s); 47 v.name (transled.name, transled.desc); 48 } 49 p.transLoaded = true; 50 } 62 if (p.name is null) loadTransl (*p, h); 51 63 52 64 if (item == null) 53 return new ContentList (p.content );65 return new ContentList (p.content, p.name, p.desc); 54 66 55 67 auto q = (h = head (item)) in p.content; … … 74 86 return ret; 75 87 } 88 89 void loadTransl (Options p, char[] n) { 90 debug logger.trace ("Loading translation strings for Options."~n); 91 Translation trans = Translation.load ("L10n/"~n); 92 Translation.Entry transled = trans.getStruct (n); 93 p.name = transled.name; 94 p.desc = transled.desc; 95 foreach (s, v; p.content) { 96 transled = trans.getStruct (s); 97 v.name (transled.name, transled.desc); 98 } 99 } mde/lookup/Options.d
r103 r104 14 14 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 15 15 16 //FIXME: Ddoc is outdated 17 /** This module handles stored options, currently all except input maps. 18 * 19 * The purpose of having all options centrally controlled is to allow generic handling by the GUI 20 * and ease saving and loading of values. The Options class is only really designed around handling 21 * small numbers of variables for now. 22 * 23 * Note: This module uses some non-spec functionality, which "works for me", but may need to be 24 * changed if it throws up problems. Specifically: templated virtual functions (Options.set, get 25 * and list), and accessing private templates from an unrelated class (Options.TName, TYPES). 26 * OptionChanges used to have a templated set member function (used by Options.set), which caused 27 * linker problems when the module wasn't compiled from scratch. 28 */ 16 /** This module handles loading and saving of, and allows generic access to named option variables 17 * of a simple type (see Options.TYPES). */ 29 18 module mde.lookup.Options; 30 19 … … 46 35 } 47 36 48 //FIXME: Ddoc is outdated 49 /** Base class for handling options. 50 * 51 * This class itself handles no options and should not be instantiated, but provides a sub-classable 52 * base for generic options handling. Also, the static portion of this class tracks sub-class 53 * instances and provides loading and saving methods. 54 * 55 * Each sub-class provides named variables for maximal-speed reading. Local sub-class references 56 * should be used for reading variables, and via the addOptionsClass() hook will be loaded from 57 * files during pre-init (init0 stage). Do not write changes directly to the subclasses or they will 58 * not be saved; instead use set(), for example, miscOpts.set!(char[])("L10n","en-GB"). Use an 59 * example like MiscOptions as a template for creating a new Options sub-class. 60 * 61 * Optionally, overload the validate() function. This is called after loading, allowing conditions 62 * to be enforced on variables. Use set!()() to change the variables. If an exception is thrown, 63 * init will abort and the executable won't start. 64 * 65 * Details: Options sub-classes hold associative arrays of pointers to all option variables, with a 66 * char[] id. This list is used for saving, loading and to provide generic GUI options screens. The 67 * built-in support in Options is only for bool, int and char[] types (a float type may get added). 68 * Further to this, a generic class is used to store all options which have been changed, and if any 69 * have been changed, is merged with options from the user conf dir and saved on exit. 70 */ 71 /* An idea for potentially extending Options, but which doesn't seem necessary now: 72 Move static code from Options to an OptionSet class, which may be sub-classed. These sub-classes 73 may be hooked in to the master OptionSet class to shadow all Options classes and be notified of 74 changes, and may or may not have values loaded from files during init. Change-sets could be 75 rewritten to use this. 76 However, only the changesets should need to be notified of each change (gui interfaces could simply 77 be notified that a change occured and redraw everything; users of options can just re-take their 78 values every time they use them). */ 37 /************************************************************************************************* 38 * This class and the OptionChanges class contain all the functionality. 39 * 40 * Options are stored in derived class instances, tracked by the static portion of Options. Each 41 * value is stored in a ValueContent class, whose value can be accessed with opCall, opCast and 42 * opAssign. These class objects can be given callbacks called whenever their value is changed. 43 * 44 * Public static methods allow getting the list of tracked sub-class instances, and loading and saving 45 * option values. A public non-static method allows generic access to option variables. 46 * 47 * Generic access to Options is of most use to a gui, allowing Options to be edited generically. 48 * 49 * The easiest way to use Options is to use an existing sub-class as a template, e.g. MiscOptions. 50 *************************************************************************************************/ 79 51 class Options : IDataSection 80 52 { 81 protected this() {} /// Do not instantiate directly. 82 83 // All supported types, for generic handling via templates. It should be possible to change 84 // the supported types simply by changing this list now (untested). 85 template store(A...) { alias A store; } 86 // NOTE: currently all types have transitioned to the new method, but the old method remains 87 alias store!(bool, int, double, char[]) TYPES; // all types 88 alias store!(bool, int, double, char[]) CTYPES; // types stored with a content 53 /** Do not instantiate directly; use a sub-class. 54 * 55 * CTOR adds any created instance to the list of classes tracked statically for loading/saving 56 * and generic access. 57 * 58 * Normally instances are created by a static CTOR. */ 59 protected this(char[] name) 60 in { 61 assert (((cast(ID) name) in subClasses) is null); // Don't allow a silent replacement 62 } body { 63 subClasses[cast(ID) name] = this; 64 } 65 89 66 //BEGIN Templates: internal 90 private { 91 // Get name of a type. Basically just stringof, but special handling for arrays. 67 package { 68 // All supported types, for generic handling via templates. It should be possible to change 69 // the supported types simply by changing this list. 70 template store(A...) { alias A store; } 71 alias store!(bool, int, double, char[]) TYPES; // types handled 72 73 // Get name of a type. Basically just stringof, but special handling for arrays. 92 74 // Use TName!(T) for a valid symbol name, and T.stringof for a type. 93 75 template TName(T : T[]) { … … 97 79 const char[] TName = T.stringof; 98 80 } 99 100 // Pointer lists 101 template PLists(A...) { 102 static if (A.length) { 103 static if (TIsIn!(A[0], CTYPES)) { 104 const char[] PLists = PLists!(A[1..$]); 105 } else 106 const char[] PLists = A[0].stringof~"*[ID] opts"~TName!(A[0])~";\n" ~ PLists!(A[1..$]); 107 } else 108 const char[] PLists = ""; 109 } 110 81 } 82 private { 111 83 // True if type is one of A 112 84 template TIsIn(T, A...) { … … 122 94 // For addTag 123 95 template addTagMixin(T, A...) { 124 static if (TIsIn!(T, CTYPES)) { 125 const char[] ifBlock = `if (tp == "`~T.stringof~`") { 96 const char[] ifBlock = `if (tp == "`~T.stringof~`") { 126 97 auto p = id in opts; 127 98 if (p) { … … 130 101 } 131 102 }`; 132 } else133 const char[] ifBlock = `if (tp == "`~T.stringof~`") {134 `~T.stringof~`** p = id in opts`~TName!(T)~`;135 if (p !is null) **p = parseTo!(`~T.stringof~`) (dt);136 }`;137 103 static if (A.length) 138 104 const char[] addTagMixin = ifBlock~` else `~addTagMixin!(A).addTagMixin; … … 140 106 const char[] addTagMixin = ifBlock; 141 107 } 142 143 // For list144 template listMixin(A...) {145 static if (A.length) {146 static if (TIsIn!(A, CTYPES))147 const char[] listMixin = listMixin!(A[1..$]);148 else149 const char[] listMixin = `ret ~= opts`~TName!(A[0])~`.keys;` ~ listMixin!(A[1..$]);150 } else151 const char[] listMixin = ``;152 }153 108 } 154 109 //END Templates: internal … … 157 112 //BEGIN Static 158 113 static { 159 /** Add an options sub-class to the list for loading and saving. 160 * 161 * Call from static this() (before Init calls load()). */ 162 void addOptionsClass (Options c, char[] i) 163 in { // Trap a couple of potential coding errors: 164 assert (c !is null); // Instance must be created before calling addOptionsClass 165 assert (((cast(ID) i) in subClasses) is null); // Don't allow a silent replacement 166 } body { 167 c.secName = i; 168 subClasses[cast(ID) i] = c; 169 } 170 171 /** Get the hash map of Options classes. */ 114 /** Get the hash map of Options classes. READ-ONLY. */ 172 115 Options[ID] optionsClasses () { 173 116 return subClasses; … … 240 183 241 184 //BEGIN Non-static 242 /+ NOTE: according to spec: "Templates cannot be used to add non-static members or virtual243 functions to classes." However, this appears to work (but linking problems did occur).244 Alternative: use mixins. From OptionsChanges:245 // setT (used to be a template, but:246 // Templates cannot be used to add non-static members or virtual functions to classes. )247 template setMixin(A...) {248 static if (A.length) {249 const char[] setMixin = `void set`~TName!(A[0])~` (ID id, `~A[0].stringof~` x) {250 `~TName!(T)~`s[id] = x;251 }252 ` ~ setMixin!(A[1..$]);253 } else254 const char[] setMixin = ``;255 }+/256 /+257 /** Set option symbol of an Options sub-class to val.258 *259 * Due to the way options are handled generically, string IDs must be used to access the options260 * via hash-maps, which is a little slower than direct access but necessary since the option261 * must be changed in two separate places. */262 /+deprecated void set(T) (char[] symbol, T val) {263 static assert (TIsIn!(T,TYPES) && !TIsIn!(T, CTYPES), "Options.set does not support type "~T.stringof);264 265 changed = true; // something got set (don't bother checking this isn't what it already was)266 267 try {268 mixin (`*(opts`~TName!(T)~`[cast(ID) symbol]) = val;`);269 mixin (`optionChanges.`~TName!(T)~`s[symbol] = val;`);270 } catch (ArrayBoundsException) {271 // log and ignore:272 logger.error ("Options.set: invalid symbol");273 }274 }+/275 /** Get option symbol of an Options sub-class.276 *277 * Using this method to read an option is not necessary, but allows for generic use. */278 deprecated T get(T) (char[] symbol) {279 static assert (TIsIn!(T,TYPES), "Options does not support type "~T.stringof);280 281 mixin (`alias opts`~TName!(T)~` optsVars;`);282 283 try {284 return *(optsVars[cast(ID) symbol]);285 } catch (ArrayBoundsException) {286 // log and ignore:287 logger.error ("Options.get: invalid symbol");288 }289 }+/290 291 /** List the names of all options of a specific type. */292 deprecated char[][] list () {293 char[][] ret;294 mixin (listMixin!(TYPES));295 return ret;296 }297 298 185 /// Get all Options stored with a ValueContent. 299 186 ValueContent[char[]] content() { … … 301 188 } 302 189 303 /** Variable validate function, called when options are loaded from file. This implementation 304 * does nothing. */ 305 void validate() {} 306 307 /** Boolean, telling whether translation strings have been loaded for the instance. */ 308 bool transLoaded; 190 /** Variable validate function, called when options are loaded from file. 191 * 192 * This can be overridden to enforce limits on option variables, etc. */ 193 protected void validate() {} 194 195 /** Translated name and description of the instance. mde.gui.content.Items loads these and the 196 * translation strings of all enclosed options simultaneously. */ 197 char[] name, desc; 309 198 310 199 protected { 311 char[] secName; // name of this option setting; set null after translation is loaded312 200 OptionChanges optionChanges; // all changes to options (for saving) 313 314 // The "pointer lists", e.g. char[]*[ID] optscharA; 315 mixin (PLists!(TYPES)); 316 ValueContent[char[]] opts; // generic list of option values 201 ValueContent[char[]] opts; // generic list of option values 317 202 } 318 203 … … 329 214 //BEGIN Templates: impl & optionsThis 330 215 private { 331 // Replace, e.g., bool, with BoolContent332 template contentName(A) {333 static if (TIsIn!(A, CTYPES)) {334 const char[] contentName = VContentN!(A);335 } else336 const char[] contentName = A.stringof;337 }338 216 // Return index of first comma, or halts if not found. 339 217 template cIndex(char[] A) { … … 369 247 const char[] parseT = parseT!(type, A[scIndex!(A)+1 .. $]); 370 248 } 371 // May have a trailing comma. Assumes cIndex always returns less than A.$ .372 template aaVars(char[] A) {373 static if (A.length == 0)374 const char[] aaVars = "";375 else static if (A[0] == ' ')376 const char[] aaVars = aaVars!(A[1..$]);377 else378 const char[] aaVars = "\""~A[0..cIndex!(A)]~"\"[]:&"~A[0..cIndex!(A)] ~ "," ~379 aaVars!(A[cIndex!(A)+1..$]);380 }381 // May have a trailing comma. Assumes cIndex always returns less than A.$ .382 template aaVarsContent(char[] A) {//FIXME383 static if (A.length == 0)384 const char[] aaVarsContent = "";385 else static if (A[0] == ' ')386 const char[] aaVarsContent = aaVarsContent!(A[1..$]);387 else388 const char[] aaVarsContent = "\""~A[0..cIndex!(A)]~"\"[]:cast(ValueContent)"~A[0..cIndex!(A)] ~ "," ~389 aaVarsContent!(A[cIndex!(A)+1..$]);390 }391 249 // strip Trailing Comma 392 250 template sTC(char[] A) { … … 395 253 else 396 254 const char[] sTC = A; 397 }398 // if string is empty (other than space) return null, otherwise enclose: [A]399 template listOrNull(char[] A) {400 static if (A.length == 0)401 const char[] listOrNull = "null";402 else static if (A[0] == ' ')403 const char[] listOrNull = listOrNull!(A[1..$]);404 else405 const char[] listOrNull = "["~A~"]";406 255 } 407 256 // if B is empty return an empty string otherswise return what's below: … … 425 274 template optionsThisInternal(char[] A, B...) { 426 275 static if (B.length) { 427 static if (TIsIn!(B[0], CTYPES)) { 428 const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~ 429 optionsThisInternal!(A,B[1..$]); 430 } else 431 const char[] optionsThisInternal = `opts`~TName!(B[0])~` = `~listOrNull!(sTC!(aaVars!(parseT!(B[0].stringof,A))))~";\n" ~ optionsThisInternal!(A,B[1..$]); 432 } else 276 const char[] optionsThisInternal = createContents!(B[0],parseT!(B[0].stringof,A))~ 277 optionsThisInternal!(A,B[1..$]); 278 } else 433 279 const char[] optionsThisInternal = ``; 434 280 } 435 281 template declValsInternal(char[] A, B...) { 436 282 static if (B.length) { 437 const char[] declValsInternal = catOrNothing!( contentName!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]);283 const char[] declValsInternal = catOrNothing!(VContentN!(B[0]),parseT!(B[0].stringof,A)) ~ declValsInternal!(A,B[1..$]); 438 284 } else 439 285 const char[] declValsInternal = ``; … … 451 297 const char[] optionsThis = 452 298 "optionChanges = new OptionChanges;\n" ~ 299 "super (name);\n" ~ 453 300 optionsThisInternal!(A,TYPES); 454 301 } … … 468 315 * --- 469 316 * mixin (declVals!(A)~` 470 this () {471 `~optionsThis!(A)~`317 this (char[] name) { 318 `~optionsThis!(A)~` 472 319 }`); 473 320 * --- … … 481 328 * class, but doing so would rather decrease readability of any implementation. */ 482 329 template impl(char[] A /+, char[] symb+/) { 483 const char[] impl = declVals!(A)~"\nthis( ){\n"~optionsThis!(A)~"}";330 const char[] impl = declVals!(A)~"\nthis(char[] name){\n"~optionsThis!(A)~"}"; 484 331 // ~"\nstatic this(){\n"~optClassAdd!(symb)~"}" 485 332 } … … 488 335 } 489 336 490 /** Special class to store all locally changed options, whatever the section. */ 337 /************************************************************************************************* 338 * Special class to store all locally changed options. 339 * 340 * This allows only changed options and those already stored in the user directory to be saved, so 341 * that other options can be merged in from a global directory, allowing any options not locally 342 * set to be changed globally. 343 *************************************************************************************************/ 491 344 class OptionChanges : IDataSection 492 345 { … … 554 407 } 555 408 556 /* NOTE: Options sub-classes are expected to use a template to ease inserting contents and 557 * hide some of the "backend" functionality. Use impl as below, or read the documentation for impl. 558 * 559 * Each entry should have a Translation entry with humanized names and descriptions in 560 * data/L10n/ClassName.mtt 561 * 562 * To create a new Options sub-class, just copy, paste and adjust. 563 */ 564 565 /** A home for all miscellaneous options, at least for now. */ 409 /** A home for all miscellaneous options. 410 * 411 * Also a template for deriving Options; comments explain what does what. 412 * 413 * Translation strings for the options are looked for in data/L10n/SectionName.mtt where 414 * this ("SectionName") names the instance. */ 566 415 MiscOptions miscOpts; 567 416 class MiscOptions : Options { 417 /* The key step is to mixin impl. 418 The syntax is just as simple variables are declared, which is how these options used to be 419 stored. Now they're enclosed in ValueContent classes; e.g. "char[] L10n;" is replaced with 420 "TextContent L10n;". The pragma statement can be uncommented to see what code gets injected 421 (note: pragma () gets called each time the module is imported as well as when it's compiled). 422 impl creates a this() function; if you want to include your own CTOR see impl's ddoc. */ 568 423 const A = "bool exitImmediately; int maxThreads, logLevel, logOutput; double pollInterval; char[] L10n;"; 569 424 //pragma (msg, impl!(A)); 570 425 mixin (impl!(A)); 571 426 572 void validate() { 427 // Overriding validate allows limits to be enforced on variables at load time. Currently 428 // there's no infrastructure for enforcing limits when options are set at run-time. 429 override void validate() { 573 430 // Try to enforce sensible values, whilst being reasonably flexible: 574 431 if (maxThreads() < 1 || maxThreads() > 64) { … … 580 437 } 581 438 439 // A static CTOR is a good place to create the instance (it must be created before init runs). 582 440 static this() { 583 miscOpts = new MiscOptions; 584 Options.addOptionsClass (miscOpts,"MiscOptions");441 // Adds instance to Options's tracking; the string is the section name in the config files. 442 miscOpts = new MiscOptions ("MiscOptions"); 585 443 } 586 444 } mde/setup/Screen.d
r98 r104 265 265 logger = Log.getLogger ("mde.setup.Screen"); 266 266 267 videoOpts = new VideoOptions; 268 Options.addOptionsClass (videoOpts, "VideoOptions"); 267 videoOpts = new VideoOptions ("VideoOptions"); 269 268 } 270 269
