% Copyright 2019 by Till Tantau % Copyright 2019 by Saso Zivanovic % % This file may be distributed and/or modified % % 1. under the LaTeX Project Public License and/or % 2. under the GNU Public License. % % See the file doc/generic/pgf/licenses/LICENSE for more details. % % Based on pgfmoduleoo.code.tex v1.8 by Till Tantau. This version adds inheritance. % Support of object-oriented programming in TeX, with inheritance. % The oo support works as follows: % % The main supported concepts are classes, objects, methods, % attributes and signal/slots. A class defines a set of methods, which are, % in the end, just normal TeX macros. Once a class has been created, % it can be instantiated by calling the \pgfoonew command, resulting % in a new object. Objects are local to their group. Given an object, % you can send it a message, resulting in the method code of the % object's method to be executed. While an object exists, it has a set % of attributes whose values can change over time. Attributes values % are not local to TeX groups, rather their life-cycle is the same as % the object's (which, however, is local to the group in which the % object was declared). % % The implementation is as follows: There is an ID counter that is % increased each time an object is created. This counter is local to % the group, which means that when a group ends the counter will % revert to the previous value, allowing objects to the reused in % subsequent groups. % % A method is just a normal \TeX macro, but to call it you write % \objecthandle.methodname(parameters). The \objecthandle is a macro % that is created when you say \pgfoonew. The special object % \pgfoothis is the current object. % % Attributes are stored globally in internal TeX macros whose name is % composed of the object number and the attribute name. % Declares a class % % #1 = class name % #2 = methods % % Description: % % This command declares a class for future use. Inside #2, the macro % \method can be used to declare a method. The \method macro takes a % method name, parameters (methods are normal TeX macros, after all) % and body. % base classes are put in parenthesis before the new class name, but % are optional. \def\pgfooclass{% \pgfutil@ifnextchar ({% \pgfooclass@ }{% \pgfooclass@() }% }% \long\def\pgfooclass@(#1)#2#3{% \def\pgfoo@classname{#2}% \expandafter\ifx\csname pgfooY\pgfoo@classname.@pgfooinit\endcsname\relax\else \pgferror{class #2 is already defined}% \fi \pgfoo@ciii{#2}{#1}% \expandafter\let\csname pgfooY#2@pgfoo@mro\endcsname\pgfoo@mro \let\pgfoo@origmethod=\method% \let\pgfoo@origattribute=\attribute% \let\method=\pgfoo@declaremethod% \let\attribute=\pgfoo@declareattribute% \let\pgfoo@attributes=\pgfutil@empty% \let\pgfoo@methods=\pgfutil@empty% #3% % inherit \def\pgfoo@temp@baseclasses{#1}% \pgfoo@inherit@methods \pgfoo@inherit@attributes % Always present (and never inherited) methods: \expandafter\let\csname pgfooY\pgfoo@classname.get handle\endcsname\pgfoo@obj% \expandafter\let\csname pgfooY\pgfoo@classname.get id\endcsname\pgfoo@id% % for compatibility with previous versions of pgfoo, define method % with the same name as the class as a synonym for init \expandafter\let\expandafter\pgfoo@init\csname pgfooY\pgfoo@classname.init\endcsname \expandafter\let\csname pgfooY\pgfoo@classname.\pgfoo@classname\endcsname\pgfoo@init % Cleanup \let\method=\pgfoo@origmethod% \let\attribute=\pgfoo@origattribute% }% \def\pgfoo@emptyinit(){}% \def\pgfoo@escapeif#1#2\fi{\fi#1}% a little helper %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Compute MRO using C3 algorithm % The sequences are enclosed in brackets; elements of sequences and % the big list are terminated by a comma and a dot, respectively. (Why % comma *and* a dot: TeX's parset---brackets don't form groups! Soooo, % why not braces? Because I don't know how do delete excess "{},"s...) % #1 = current class, #2 = list of base classes \def\pgfoo@ciii#1#2{% % \pgfoo@seq will hold a list of sequences. % (i) the first sequence has just one element: the current class \def\pgfoo@seq{[#1,].}% % (ii) every subclass has a sequence (in the order as they were % given): the class' sequence is its mro. \pgfutil@for\pgfoo@baseclass:={#2}\do{% \edef\pgfoo@baseclass@mro{\csname pgfooY\pgfoo@baseclass @pgfoo@mro\endcsname}% \expandafter\ifx\expandafter\relax\pgfoo@baseclass@mro\relax\else \edef\pgfoo@seq{\pgfoo@seq[\pgfoo@baseclass@mro,].}% \fi }% % The final sequence is a sequence of base classes, in the order as % they were given. \ifx\relax#2\relax\else \edef\pgfoo@seq{\pgfoo@seq[#2,].}% \fi \edef\pgfoo@temp{()(\pgfoo@seq)}% \expandafter\pgfoo@ciii@merge\pgfoo@temp }% % #1 = MRO so far % #2 = remaining sequences % no empty sequences [] should arrive here: they should be removed % before calling this macro \def\pgfoo@ciii@merge(#1)(#2){% \ifx\relax#2\relax \pgfoo@ciii@done(#1)% \else\pgfoo@escapeif{% split #2 \pgfoo@ciii@merge@A(#1)(#2)% }\fi% }% % a splitter: to be only called from \pgfoo@ciii@merge \def\pgfoo@ciii@merge@A(#1)(#2.#3){% \ifx\relax#3\relax % if #3 is empty, we can take the shortcut \pgfoo@ciii@mergeA@done(#1)(#2)% \else\pgfoo@escapeif{% \pgfoo@ciii@merge@checkHead(#1)()(#2)()(#3)% }\fi }% \def\pgfoo@ciii@mergeA@done(#1)([#2]){% \pgfoo@ciii@done(#1#2)% }% % #1 = MRO so far % #2 = sequences with bad heads (dot after each sequence) % [#3,#4] = the sequence whose head we're testing right % now---obviously, it should not be empty! (#3 has no comma, % #4 has a comma after each class name) % #5 = #3 does not occur in the tail of these sequences (dot after % each sequence) % #6 = the remaining sequences (dot after each sequence) \def\pgfoo@ciii@merge@checkHead(#1)(#2)([#3,#4])(#5)(#6){% \pgfutil@in@{[#3,}{#2}% \ifpgfutil@in@\pgfoo@escapeif{% #3 is already blacklisted \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead@P{(#1)(#2[#3,#4].)(#5#6)}% }\else\pgfoo@escapeif{% \ifx\relax#6\relax\pgfoo@escapeif{% no more sequences to test the head against! % so, #3 is a good head, and we can recurse \pgfoo@ciii@goodHeadFound(#1)(#3)(#2[#4].#5)% }\else\pgfoo@escapeif{% split #6 and get to work \pgfoo@ciii@merge@checkHead@A(#1)(#2)([#3,#4])(#5)(#6)% }\fi }\fi }% % splitter: to be only called from \pgfoo@ciii@merge@checkHead % we will check if #3 occurs in the tail of #6 \def\pgfoo@ciii@merge@checkHead@A(#1)(#2)([#3,#4])(#5)(#6.#7){% \pgfoo@ciii@merge@checkHead@B(#1)(#2)([#3,#4])(#5)(#6)(#7)% }% % splitter: to be only called from \pgfoo@ciii@merge@checkHead@A % &worker: we will check if #3 occurs in #7. If it does, #3 is a bad head. \def\pgfoo@ciii@merge@checkHead@B(#1)(#2)([#3,#4])(#5)([#6,#7])(#8){% % \def\pgfoo@ciii@tempA{#3}\def\pgfoo@ciii@tempB{#5}% % \ifx\pgfoo@ciii@tempA\pgfoo@ciii@tempB % \pgferror{An attempt to derive from the same class twice}% % \else\pgfoo@escapeif{% \pgfutil@in@{,#3,}{,#7}% \ifpgfutil@in@\pgfoo@escapeif{% bad head! get next head and restart % but: need to clean up first! \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead@P{(#1)(#2[#3,#4].)(#5[#6,#7].#8)}% }\else\pgfoo@escapeif{% so far, so good \ifx\relax#8\relax\pgfoo@escapeif{% if #8 is empty, we won: % #3 is a good head! we will move it to MRO % but clean-up first! \pgfoo@ciii@goodHeadFound(#1)(#3)(#2[#4].#5[#6,#7].)% }\else\pgfoo@escapeif{% proceed to the first seq in #8 \pgfoo@ciii@clean\pgfoo@ciii@merge@checkHead{(#1)(#2)([#3,#4])(#5[#6,#7].)(#8)}% }\fi }\fi % }\fi }% % #1 = MRO % #2 = good head % #3 = the remaining sequences (in need of removing the good head from % them, and then also some general cleanup) \def\pgfoo@ciii@goodHeadFound(#1)(#2)(#3){% \pgfutil@in@{,#2,}{#3}% \ifpgfutil@in@\pgfoo@escapeif{% \def\pgfoo@ciii@removeGoodHead(##1)(##2)(##3,#2,##4){% \pgfoo@ciii@goodHeadFound(##1)(##2)(##3,##4)}% \pgfoo@ciii@removeGoodHead(#1)(#2)(#3)% }\else\pgfoo@escapeif{% \pgfutil@in@{[#2,}{#3}% \ifpgfutil@in@\pgfoo@escapeif{% \def\pgfoo@ciii@removeGoodHead(##1)(##2)(##3[#2,##4){% \pgfoo@ciii@goodHeadFound(##1)(##2)(##3[##4)}% \pgfoo@ciii@removeGoodHead(#1)(#2)(#3)% }\else\pgfoo@escapeif{% \pgfoo@ciii@clean\pgfoo@ciii@merge{(#1#2,)(#3)}% }\fi }\fi }% % mediates between the bad head exit of \pgfoo@ciii@merge@checkHead@B % and \pgfoo@ciii@merge@checkHead; we need the mediator because #3 % below might have been affected by cleaning. \def\pgfoo@ciii@merge@checkHead@P(#1)(#2)(#3){% \if\relax#3\relax % no more good head candidates! \pgferror{Bad MRO: There is no good (monotonic etc.) Method Resolution Order for your class hierarchy}% \else\pgfoo@escapeif{% \pgfoo@ciii@merge@checkHead@Q(#1)(#2)(#3)% }\fi }% % splitter for \pgfoo@ciii@merge@checkHead@P % #3 = the sequence containing the newest good head candidate \def\pgfoo@ciii@merge@checkHead@Q(#1)(#2)(#3.#4){% \pgfoo@ciii@merge@checkHead(#1)(#2)(#3)()(#4)% }% % cyclically removes all occurrences of "[]," and "[]." (i.e., empty % sequences) in #2. When done, calls #1 with whatever remains. \def\pgfoo@ciii@clean#1#2{% clean commas \pgfutil@in@{[].}{#2}% clean dots \ifpgfutil@in@\pgfoo@escapeif{% \pgfoo@ciii@clean@dot#1#2\pgfoo@ciii@clean@END }\else\pgfoo@escapeif{% #1#2% }\fi }% \def\pgfoo@ciii@clean@dot#1#2[].#3\pgfoo@ciii@clean@END{% \pgfoo@ciii@clean#1{#2#3}% }% \def\pgfoo@ciii@done(#1,){% \def\pgfoo@mro{#1}% }% % end of C3 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % Declare a method % % Description: % % Defines a method. To use/invoke this method for an object \object, % you write \object.methodname(parameters). This will cause the method % body to be invoked with the argument "(parameters)". % % To define the method \method should be directly followed by the % method name and, then, by (, followed by a parameter pattern, % followed by ), followed by the message body. Spaces are allowed only % after "\method" and after the closing ). % % Example: % % \pgfooclass{MyPlot}{ % % \attribute x=0; % \attribute y=0; % % \method MyPlot() { % } % % \method getX(#1) { % \pgfooget{x}{#1} % } % % \method setPoint(#1,#2) { % \pgfooset{x}{#1} % \pgfooset{y}{#2} % } % } % define the method macro and append the method to the method collection \long\def\pgfoo@declaremethod#1(#2)#3{% \def\pgfoo@method{#1}% \ifx\pgfoo@classname\pgfoo@method \def\pgfoo@method{init}% \fi \expandafter\long\expandafter\def\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname(#2){#3}% \edef\pgfoo@methods{\pgfoo@methods,\pgfoo@method}% }% \def\pgfoo@inherit@methods{% % for non-derived classes: \expandafter\ifx\expandafter\relax\pgfoo@temp@baseclasses\relax % if init is not defined, define an empty one \expandafter\ifx\csname pgfooY\pgfoo@classname.init\endcsname\relax \expandafter\let\csname pgfooY\pgfoo@classname.init\endcsname\pgfoo@emptyinit% \edef\pgfoo@methods{,init\pgfoo@methods}% \fi \fi \if\relax\pgfoo@methods\relax\else % gobble the initial comma \edef\pgfoo@methods{\expandafter\pgfutil@gobble\pgfoo@methods}% \fi % remember the list of collected methods \expandafter\let\csname pgfooY\pgfoo@classname @pgfoo@methods\endcsname\pgfoo@methods % get MRO \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname % loop throough base classes in MRO \pgfutil@for\pgfoo@baseclass:=\pgfoo@mro\do{% \ifx\pgfoo@baseclass\pgfoo@classname % skip self \else\pgfoo@escapeif{% % get methods defined in this base class \expandafter\let\expandafter\pgfoo@methods\csname pgfooY\pgfoo@baseclass @pgfoo@methods\endcsname \pgfutil@for\pgfoo@method:=\pgfoo@methods\do{% for each method % check if it is already defined in our class \expandafter\ifx\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname\relax % if not, link the method name from our class to base class \edef\pgfoo@temp{\noexpand\let \expandafter\noexpand\csname pgfooY\pgfoo@classname.\pgfoo@method\endcsname \expandafter\noexpand\csname pgfooY\pgfoo@baseclass.\pgfoo@method\endcsname}% \pgfoo@temp \fi }% }\fi }% }% \def\pgfoosuper(#1,#2).#3({% #2.get id(\pgfoo@temp@id)% \edef\pgfoo@classname{\csname pgfooX\pgfoo@temp@id @class\endcsname}% \def\pgfoo@super@A##1,#1,##2.##3({% \pgfoo@super@B##2.##3(% }% \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname \expandafter\pgfoo@super@A\expandafter,\pgfoo@mro,.#3(% }% \def\pgfoo@super@B#1,#2.#3({% \expandafter\ifx\csname pgfooY#1.#3\endcsname\relax \pgfoo@escapeif{\pgfoo@super@B#2.#3(}% \else \pgfoo@escapeif{\expandafter\pgfoo@caller\pgfoo@temp@id.#1.#3(}% \fi }% % Declare an attribute % % #1 = attribute name % #2 = optional default value % % Description: % % Declares the attribute #1 for the current class. If the attribute % name is followed by =, the text following the equal sign is the % default value. \def\pgfoo@declareattribute#1;{% \pgfutil@in@{ =}{#1}% \ifpgfutil@in@% \pgfoo@declareunpackspace#1;% \else% \pgfutil@in@={#1}% \ifpgfutil@in@% \pgfoo@declareunpack#1;% \else% \pgfoo@declareattribute@\let{#1}\pgfutil@empty \fi% \fi% }% \def\pgfoo@declareunpack#1=#2;{\pgfoo@declareattribute@\def{#1}{{#2}}}% \def\pgfoo@declareunpackspace#1 =#2;{\pgfoo@declareattribute@\def{#1}{{#2}}}% % put the initial value in the class's namespace % put the attr name in a list \def\pgfoo@declareattribute@#1#2#3{% \expandafter#1\csname pgfooY\pgfoo@classname @#2\endcsname#3% \expandafter\def\expandafter\pgfoo@attributes\expandafter{\pgfoo@attributes,#2}% }% % for each base class: % for each attribute declared in that class: % add the attr on a list if it's not there yet % convert the list of (attr,class which it comes from) into an % efficient sequence of triples \op{attrname}\pointer-to-initial-value % (\op is then set differently in init and garbage collection) \def\pgfoo@inherit@attributes{% % store the list of attributes declared in this class for use in % derived classes \if\relax\pgfoo@attributes\relax\else \edef\pgfoo@attributes{\expandafter\pgfutil@gobble\pgfoo@attributes}% gobble comma \fi \expandafter\let\csname pgfooY\pgfoo@classname.@pgfoo@attributes\endcsname\pgfoo@attributes% % get MRO \expandafter\let\expandafter\pgfoo@mro\csname pgfooY\pgfoo@classname @pgfoo@mro\endcsname \def\pgfoo@allattributes{}% here we will store the attrs declared in base classes \pgfutil@for\pgfoo@baseclass:=\pgfoo@mro\do{% for each base class in MRO order % the attributes declared in this base class \expandafter\let\expandafter\pgfoo@attributes\csname pgfooY\pgfoo@baseclass.@pgfoo@attributes\endcsname \pgfutil@for\pgfoo@attribute:=\pgfoo@attributes\do{% for each attribute % check if we have already found it in some previous base class \edef\pgfoo@temp{{,\pgfoo@attribute=}{\pgfoo@allattributes,}}% \expandafter\pgfutil@in@\pgfoo@temp \ifpgfutil@in@\else % don't overwrite % append to the list \edef\pgfoo@allattributes{\pgfoo@allattributes,\pgfoo@attribute=\pgfoo@baseclass}% \fi }% }% \if\relax\pgfoo@allattributes\relax\else \edef\pgfoo@allattributes{\expandafter\pgfutil@gobble\pgfoo@allattributes}% gobble comma \fi % this macro will hold a list of attributes from all base classes % we run this macro at init and gc \def\pgfoo@process@attributes{}% \pgfutil@for\pgfoo@attributeclass:=\pgfoo@allattributes\do{% for each (attribute, class) pair % append to the attribute processor \expandafter\pgfoo@inherit@attributes@appendattribute\pgfoo@attributeclass.% }% \expandafter\let\csname pgfooY\pgfoo@classname .@pgfoo@process@attributes\endcsname\pgfoo@process@attributes }% % #1=attr, #2=class which the attr comes from \def\pgfoo@inherit@attributes@appendattribute#1=#2.{% \def\pgfoo@tempA{\pgfoo@attribute@op{#1}}% \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pgfoo@tempB\expandafter\expandafter\expandafter{\expandafter\pgfoo@tempA\csname pgfooY#2@#1\endcsname}% \expandafter\expandafter\expandafter\def\expandafter\expandafter\expandafter\pgfoo@process@attributes\expandafter\expandafter\expandafter{\expandafter\pgfoo@process@attributes\pgfoo@tempB}% % above is just this: % \def\pgfoo@process@attributes{\pgfoo@process@attributes\pgfoo@attribute@op{#1}% % \csname pgfooY#2@#1\endcsname} % with \pgfoo@process@attributes and \csname expanded exactly once: }% % \pgfoo@attribute@op for garbage collection \def\pgfoo@gc@attribute#1#2{\pgfoolet{#1}\relax}% \newcount\pgfoo@objectcount \newcount\pgfoothis@count % The attribute value method % % #1 = attr % % This method inserts the current value of the given attribute for the % current object. \def\pgfoovalueof#1{% \csname pgfooX\the\pgfoothis@count @#1\endcsname% }% % The attribute get method % % #1 = attr % #2 = macro % % This method makes the macro equal to the current value of the % attribute for the current object. \def\pgfooget#1#2{% \expandafter\let\expandafter#2\csname pgfooX\the\pgfoothis@count @#1\endcsname% }% % The attribute set method % % #1 = attr % #2 = value % % This method sets the given attribute for the current object to the % given value. \long\def\pgfooset#1#2{% \expandafter\gdef\csname pgfooX\the\pgfoothis@count @#1\endcsname{#2}% }% % The attribute set method (expanded version) % % #1 = attr % #2 = value % % This method sets the given attribute for the current object to the % expanded version of the given value. \long\def\pgfooeset#1#2{% \expandafter\xdef\csname pgfooX\the\pgfoothis@count @#1\endcsname{#2}% }% % The attribute let method % % #1 = attr % #2 = value % % This method sets the given attribute for the current object to the % given value using \let. \def\pgfoolet#1#2{% \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @#1\endcsname#2% }% % Add something to an attribute at the end % % #1 = attr % #2 = code % % This method adds the give code to the attr at the end. \def\pgfooappend#1#2{% \expandafter\expandafter\expandafter\def% \expandafter\expandafter\expandafter\pgf@oo@temp\expandafter\expandafter\expandafter{\csname pgfooX\the\pgfoothis@count @#1\endcsname#2}% \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @#1\endcsname\pgf@oo@temp% }% % Add something to an attribute at the beginning % % #1 = attr % #2 = code % % This method adds the give code to the attr at the beginning. \def\pgfooprefix#1#2{% \pgfooget{#1}\pgf@oo@temp% \def\pgf@oo@@temp{#2}% \expandafter\expandafter\expandafter\def% \expandafter\expandafter\expandafter\pgf@oo@temp% \expandafter\expandafter\expandafter{\expandafter\pgf@oo@@temp\pgf@oo@temp}% \pgfoolet{#1}\pgf@oo@temp% }% % Help macro for scanning arguments \newtoks\pgfoo@toks \def\pgfoo@collect@args{% \pgfutil@ifnextchar\bgroup\pgfoo@collect@args@group\pgfoo@collect@args@nogroup% }% \def\pgfoo@collect@args@nogroup#1){% \pgfoo@toks{#1}% \pgfoo@continue% }% \def\pgfoo@collect@args@group#1{% \pgfutil@ifnextchar){\pgfoo@toks{{#1}}\expandafter\pgfoo@continue\pgfutil@gobble}{\pgfoo@collect@args@nogroup{#1}}% }% % Instantiate an object % % Possible syntax: % % 1) \pgfoonew new (=new (( (the constructor) is invoked. If the % \= part is present, the macro is assigned to the newly % created object. \def\pgfoonew{% \pgfutil@ifnextchar n{% \pgfoo@new\pgfoo@dummy=% }{% \pgfutil@ifnextchar\bgroup{% \pgfoo@new@attribute% }{% \pgfoo@new% }% }% }% \def\pgfoo@new#1={% \def\pgfutil@reserved@a{#1}% \let\pgfutil@next\pgfoo@@new \futurelet\pgfutil@let@token\pgfutil@ignorespaces}% \def\pgfoo@@new new #1({% \expandafter\ifx\csname pgfooY#1.get id\endcsname\relax% \pgferror{Unknown class '#1'}% \else% \expandafter\pgfoo@new@create\pgfutil@reserved@a{#1}% {% \pgfoothis@count\pgfoo@objectcount% \let\pgfoo@attribute@op\pgfoolet \csname pgfooY#1.@pgfoo@process@attributes\endcsname% }% \let\pgfoo@continue=\pgf@oo@new@cont% \expandafter\pgfoo@collect@args% \fi% }% \def\pgf@oo@new@cont{% \expandafter\pgfoolastobj\expandafter.\expandafter i\expandafter n\expandafter i\expandafter t\expandafter(\the\pgfoo@toks)% \aftergroup\pgfoogc% cleanup after group }% \def\pgfoo@new@attribute#1={% \def\pgfutil@reserved@a{#1}% \let\pgfutil@next\pgfoo@@new@attribute \futurelet\pgfutil@let@token\pgfutil@ignorespaces}% \def\pgfoo@@new@attribute new #1({% \expandafter\ifx\csname pgfooY#1.get id\endcsname\relax% \pgferror{Unknown class '#1'}% \else% \pgfoo@new@create\pgfoo@temp{#1}% {% \pgfoothis@count\pgfoo@objectcount% \let\pgfoo@attribute@op\pgfoolet% \csname pgfooY#1.@pgfoo@process@attributes\endcsname% }% \expandafter\pgfoolet\expandafter{\pgfutil@reserved@a}\pgfoo@temp% \let\pgfoo@continue=\pgf@oo@new@cont% \expandafter\pgfoo@collect@args% \fi% }% \def\pgfoo@new@create#1#2{% \advance\pgfoo@objectcount by 1\relax% \edef\pgfoolastobj{\noexpand\pgfoo@caller{\the\pgfoo@objectcount}}% \expandafter\gdef\csname pgfooX\the\pgfoo@objectcount @class\endcsname{#2}% \let#1\pgfoolastobj% }% \def\pgfoo@caller#1.#2({% \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY\csname pgfooX#1@class\endcsname.#2\endcsname% \ifx\pgfoo@caller@temp\relax% % assume that #2 is of form "superclass.method" \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY#2\endcsname% \ifx\pgfoo@caller@temp\relax% \pgferror{Object #1 has no method '#2'}% \fi% \fi% \def\pgf@marshal{% \pgfoothis@count=#1\relax% }% \let\pgfoo@continue\pgfoo@caller@cont% \pgfoo@collect@args% }% \def\pgfoo@caller@cont{% \edef\pgf@marshal{% \pgf@marshal% \noexpand\pgfoo@caller@temp(\the\pgfoo@toks)% }% \expandafter\pgf@marshal\expandafter\pgfoothis@count\the\pgfoothis@count\relax% }% \let\pgfoo@orig@caller=\pgfoo@caller % The special "this" object \def\pgfoothis.#1({% \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY\csname pgfooX\the\pgfoothis@count @class\endcsname.#1\endcsname% \ifx\pgfoo@caller@temp\relax% % assume that #1 is of form "superclass.method" \expandafter\let\expandafter\pgfoo@caller@temp\csname pgfooY#1\endcsname% \fi% \let\pgfoo@continue\pgfoothis@cont% \pgfoo@collect@args% }% \def\pgfoothis@cont{% \expandafter\pgfoo@caller@temp\expandafter(\the\pgfoo@toks)% }% % Get the object id % % #1 = macro to store the id % % Description: % % This special method allows you to access the object id. You can then % use \pgfoocall to call a method using this id. This is mainly useful % when you wish to store the id for a longer time. \def\pgfoo@id(#1){% \edef#1{\the\pgfoothis@count}% }% % Yields the object with the given id % % #1 = id % % Description: % % Given an object id, \pgfooobj{} will yield the object having % this id. \def\pgfooobj#1{% \pgfoo@caller{#1}% }% % Get the object % % #1 = macro to store the object % % Description: % % This special method allows you to get a new handle to a given % object. If \obj is an object, you could normally just say % \let#1=\obj. However, if \obj happens to be \pgfoothis, then you may % wish to get a handle to the object itself, not to the special macro % \pgfoothis. In this case you can say \obj.get handle(#1). \def\pgfoo@obj(#1){% \edef#1{\noexpand\pgfoo@caller{\the\pgfoothis@count}}% }% % The garbage collector % % Description: % % This method frees space occupied by unused objects. Garbage are % objects that have been destroyed because of the end of the scope in % which they were created. In this case, however, the memory used by % this object is not immediately reused because the attributes of the % object are actually stored in global variables. When the garbage % collector is called, it will set all these global variables to % \relax, thereby ensuring that no memory is needed for them. \def\pgfoogc{% {% % We do this in a group... \pgfoothis@count\pgfoo@objectcount% this is temporary... \let\pgfoo@next=\pgfoo@dogc% \pgfoo@next% }% }% \def\pgfoo@dogc{% \advance\pgfoothis@count by 1\relax% \expandafter\ifx\csname pgfooX\the\pgfoothis@count @class\endcsname\relax% \let\pgfoo@next=\relax% \else% % Cleanup this object: % The following is the fast version of \pgfooobj{\the\pgfoo@objectcount}.@pgfoogc: \let\pgfoo@attribute@op\pgfoo@gc@attribute \csname pgfooY\csname pgfooX\the\pgfoothis@count @class\endcsname.@pgfoo@process@attributes\endcsname% \expandafter\global\expandafter\let\csname pgfooX\the\pgfoothis@count @class\endcsname\relax% \fi% \pgfoo@next% }% % % % Object class % defines method "copy" % % \pgfooclass{object}{% \method copy(#1) {% % create a new object \edef\pgfoo@temp@classname{\csname pgfooX\the\pgfoothis@count @class\endcsname}% \expandafter\pgfoo@new@create\expandafter#1\expandafter{\pgfoo@temp@classname}% \def\pgfoo@copy@attributes##1##2{% \pgfooget{##1}\pgfoo@attrvalue \expandafter\let\csname pgfooX\the\pgfoo@objectcount @##1\endcsname\pgfoo@attrvalue }% \let\pgfoo@attribute@op\pgfoo@copy@attributes \csname pgfooY\pgfoo@temp@classname .@pgfoo@process@attributes\endcsname \aftergroup\pgfoogc% cleanup after group }% }% % % % Signal class % % \pgfooclass{signal} {% % % This class implements signals. % % After you have created a signal object, you can call % connect to connect a slot. Then, whenever the emit method is % called, all connected methods get called. % Attribute \attribute emitter;% % Collects the objects that should be called. % Constructor \method signal() {}% % Connect a slot (method) #2 of object #1 \method connect(#1,#2) {% {% #1.get id(\pgf@tempid)% % Save in emitter \pgfooget{emitter}\pgf@temp% \let\pgfoo@signal@call=\relax% avoid expansion \edef\pgf@temp{\pgf@temp\pgfoo@signal@call{\pgf@tempid}{#2}}% \pgfoolet{emitter}\pgf@temp% }% }% \def\pgfoo@signal@call#1#2{% \def\pgf@temp{\pgfooobj{#1}.#2}% \expandafter\pgf@temp\expandafter(\pgfoo@signal@args)% }% % Emit a signal \method emit(#1) {% \def\pgfoo@signal@args{#1}% \pgfoovalueof{emitter} }% }% \endinput