JX Application Framework
|
#include <JStyledText.h>
Classes | |
struct | CRMRule |
class | CRMRuleList |
class | DefaultFontChanged |
class | TextChanged |
struct | TextCount |
struct | TextIndex |
struct | TextRange |
class | TextSet |
class | UndoFinished |
class | WillBeBusy |
Public Types | |
enum | PlainTextFormat { kUNIXText , kMacintoshText , kDOSText } |
Public Member Functions | |
JStyledText (const bool useMultipleUndo, const bool pasteStyledText) | |
JStyledText (const JStyledText &source) | |
~JStyledText () override | |
bool | IsEmpty () const |
bool | EndsWithNewline () const |
const JString & | GetText () const |
bool | SetText (const JString &text, const JRunArray< JFont > *style=nullptr) |
const JRunArray< JFont > & | GetStyles () const |
bool | WillPasteStyledText () const |
void | RestyleAll () |
bool | IsCharacterInWord (const JUtf8Character &c) const |
TextIndex | GetWordStart (const TextIndex &index) const |
TextIndex | GetWordEnd (const TextIndex &index) const |
TextIndex | GetPartialWordStart (const TextIndex &index) const |
TextIndex | GetPartialWordEnd (const TextIndex &index) const |
TextIndex | GetParagraphStart (const TextIndex &index) const |
TextIndex | GetParagraphEnd (const TextIndex &index) const |
TextIndex | GetBeyondEnd () const |
JIndex | GetColumnForChar (const TextIndex &lineStart, const TextIndex &location) const |
TextIndex | AdjustTextIndex (const TextIndex &index, const JInteger charDelta) const |
JStringIterator * | GetConstIterator (const JStringIterator::Position pos, const TextIndex &index) const |
void | DisposeConstIterator (JStringIterator *iter) const |
bool | ReadPlainText (const JString &fileName, PlainTextFormat *format, const bool acceptBinaryFile=true) |
void | WritePlainText (const JString &fileName, const PlainTextFormat format) const |
void | WritePlainText (std::ostream &output, const PlainTextFormat format) const |
bool | ReadPrivateFormat (std::istream &input) |
void | WritePrivateFormat (std::ostream &output) const |
JStringMatch | SearchForward (const TextIndex &startIndex, const JRegex ®ex, const bool entireWord, const bool wrapSearch, bool *wrapped) const |
JStringMatch | SearchBackward (const TextIndex &startIndex, const JRegex ®ex, const bool entireWord, const bool wrapSearch, bool *wrapped) const |
TextCount | ReplaceMatch (const JStringMatch &match, const JString &replaceStr, JInterpolate *interpolator, const bool preserveCase, const bool createUndo=true) |
TextRange | ReplaceAllInRange (const TextRange &range, const JRegex ®ex, const bool entireWord, const JString &replaceStr, JInterpolate *interpolator, const bool preserveCase) |
bool | IsEntireWord (const TextRange &range) const |
bool | SearchForward (std::function< bool(const JFont &)> match, const TextIndex &startIndex, const bool wrapSearch, bool *wrapped, TextRange *range) const |
bool | SearchBackward (std::function< bool(const JFont &)> match, const TextIndex &startIndex, const bool wrapSearch, bool *wrapped, TextRange *range) const |
JFont | GetFont (const JIndex charIndex) const |
bool | SetFontName (const TextRange &range, const JString &name, const bool clearUndo) |
bool | SetFontSize (const TextRange &range, const JSize size, const bool clearUndo) |
bool | SetFontBold (const TextRange &range, const bool bold, const bool clearUndo) |
bool | SetFontItalic (const TextRange &range, const bool italic, const bool clearUndo) |
bool | SetFontUnderline (const TextRange &range, const JSize count, const bool clearUndo) |
bool | SetFontStrike (const TextRange &range, const bool strike, const bool clearUndo) |
bool | SetFontColor (const TextRange &range, const JColorID color, const bool clearUndo) |
bool | SetFontStyle (const TextRange &range, const JFontStyle &style, const bool clearUndo) |
void | SetFont (const TextRange &range, const JFont &font, const bool clearUndo) |
void | SetAllFontNameAndSize (const JString &name, const JSize size, const bool clearUndo) |
const JFont & | GetDefaultFont () const |
void | SetDefaultFontName (const JString &name) |
void | SetDefaultFontSize (const JSize size) |
void | SetDefaultFontStyle (const JFontStyle &style) |
void | SetDefaultFont (const JFont &f) |
JFont | CalcInsertionFont (const TextIndex &index) const |
TextRange | Outdent (const TextRange &range, const JSize tabCount=1, const bool force=false) |
TextRange | Indent (const TextRange &range, const JSize tabCount=1) |
TextRange | CleanWhitespace (const TextRange &range, const bool align) |
bool | TabInsertsSpaces () const |
void | TabShouldInsertSpaces (const bool spaces=true) |
bool | WillAutoIndent () const |
void | ShouldAutoIndent (const bool indent=true) |
bool | HasSingleUndo () const |
bool | HasMultipleUndo (bool *canUndo, bool *canRedo) const |
void | Undo () |
void | Redo () |
void | ClearUndo () |
void | DeactivateCurrentUndo () |
JSize | GetUndoDepth () const |
void | SetUndoDepth (const JSize maxUndoCount) |
bool | IsAtLastSaveLocation () const |
void | SetLastSaveLocation () |
void | ClearLastSaveLocation () |
bool | Copy (const TextRange &range, JString *text, JRunArray< JFont > *style=nullptr) const |
TextRange | Paste (const TextRange &range, const JString &text, const JRunArray< JFont > *style=nullptr) |
bool | MoveText (const TextRange &srcRange, const TextIndex &origDestIndex, const bool copy, TextRange *newRange) |
TextIndex | BackwardDelete (const TextIndex &lineStart, const TextIndex &caretIndex, const bool deleteToTabStop, JString *returnText=nullptr, JRunArray< JFont > *returnStyle=nullptr, JUndo **undo=nullptr) |
void | ForwardDelete (const TextIndex &lineStart, const TextIndex &caretIndex, const bool deleteToTabStop, JString *returnText=nullptr, JRunArray< JFont > *returnStyle=nullptr, JUndo **undo=nullptr) |
void | DeleteText (const TextRange &range) |
JUndo * | InsertCharacter (const TextRange &replaceRange, const JUtf8Character &key, const JFont &font, TextCount *count) |
JStyledText::TextRange | InsertSpacesForTab (const TextIndex &lineStart, const TextRange &replaceRange) |
bool | CleanRightMargin (TextIndex *caretIndex, const TextRange &selectionRange, const bool coerce) |
JSize | GetCRMLineWidth () const |
void | SetCRMLineWidth (const JSize charCount) |
JSize | GetCRMTabCharCount () const |
void | SetCRMTabCharCount (const JSize charCount) |
JSize | CRMGetTabWidth (const JIndex textColumn) const |
bool | GetCRMRuleList (const CRMRuleList **ruleList) const |
void | SetCRMRuleList (CRMRuleList *ruleList, const bool teOwnsRuleList) |
void | ClearCRMRuleList () |
void | SetCharacterInWordFunction (const std::function< bool(const JUtf8Character &)> f) |
TextRange | SelectAll () const |
TextRange | CharToTextRange (const TextIndex *lineStart, const JCharacterRange &charRange) const |
void | SetBlockSizes (const JSize textLgSize, const JSize styleBlockSize) |
bool | CRMGetPrefix (TextIndex *startIndex, const TextIndex &beyondEndIndex, JString *linePrefix, JSize *columnCount, JIndex *ruleIndex) const |
bool | CRMLineMatchesRest (const TextRange &range) const |
![]() | |
JBroadcaster () | |
virtual | ~JBroadcaster () |
JBroadcaster & | operator= (const JBroadcaster &source) |
bool | HasSenders () const |
JSize | GetSenderCount () const |
bool | HasRecipients () const |
JSize | GetRecipientCount () const |
virtual JString | ToString () const |
template<class T > | |
void | ListenTo (const JBroadcaster *sender, const std::function< void(const T &)> &f) |
Static Public Member Functions | |
static bool | ReadPrivateFormat (std::istream &input, JString *text, JRunArray< JFont > *style) |
static void | WritePrivateFormat (std::ostream &output, const JFileVersion vers, const JString &text, const JRunArray< JFont > &style, const JCharacterRange &charRange) |
static bool | ContainsIllegalChars (const JString &text) |
static bool | RemoveIllegalChars (JString *text, JRunArray< JFont > *style=nullptr) |
static std::weak_ordering | CompareCharacterIndices (const TextIndex &i, const TextIndex &j) |
static std::weak_ordering | CompareByteIndices (const TextIndex &i, const TextIndex &j) |
Static Public Attributes | |
static const JUtf8Byte * | kTextSet = "TextSet::JStyledText" |
static const JUtf8Byte * | kTextChanged = "TextChanged::JStyledText" |
static const JUtf8Byte * | kDefaultFontChanged = "DefaultFontChanged::JStyledText" |
static const JUtf8Byte * | kUndoFinished = "UndoFinished::JStyledText" |
static const JUtf8Byte * | kWillBeBusy = "WillBeBusy::JStyledText" |
Protected Member Functions | |
bool | IsEntireWord (const JString &text, const TextRange &range) const |
void | SetFont (const TextRange &range, const JRunArray< JFont > &f) |
void | BroadcastTextChanged (const TextRange &range, const JInteger charDelta, const JInteger byteDelta, const bool deletion, const bool adjustStyles=true) |
void | BroadcastUndoFinished (const TextRange &range) |
virtual bool | NeedsToFilterText (const JString &text) const |
virtual bool | FilterText (JString *text, JRunArray< JFont > *style) |
virtual bool | NeedsToAdjustFontToDisplayGlyphs (const JString &text, const JRunArray< JFont > &style) const |
virtual bool | AdjustFontToDisplayGlyphs (const TextRange &range, const JString &text, JRunArray< JFont > *style) |
virtual void | AdjustStylesBeforeBroadcast (const JString &text, JRunArray< JFont > *styles, TextRange *recalcRange, TextRange *redrawRange, const bool deletion) |
![]() | |
JBroadcaster (const JBroadcaster &source) | |
void | ListenTo (const JBroadcaster *sender) |
void | StopListening (const JBroadcaster *sender) |
void | ClearWhenGoingAway (const JBroadcaster *sender, void *pointerToMember) |
void | StopListening (const JBroadcaster *sender, const std::type_info &messageType) |
template<class T > | |
void | Send (JBroadcaster *recipient, const T &message) |
template<class T > | |
void | Broadcast (const T &message) |
virtual void | Receive (JBroadcaster *sender, const Message &message) |
void | SendWithFeedback (JBroadcaster *recipient, Message *message) |
void | BroadcastWithFeedback (Message *message) |
virtual void | ReceiveWithFeedback (JBroadcaster *sender, Message *message) |
virtual void | ReceiveGoingAway (JBroadcaster *sender) |
Friends | |
class | JSTUndoTextBase |
class | JSTUndoStyle |
class | JSTUndoMove |
Class to manage styled text. Only public functions are required to call NewUndo(), and only if the action to be performed changes the text or styles. Private functions must *not* call NewUndo(), because several manipulations may be required to perform one user command, and only the user command as a whole is undoable. (Otherwise, the user may get confused.) Because of this convention, public functions that affect undo should only be a public interface to a private function. The public function calls NewUndo() and then calls the private function. The private function does all the work, but doesn't modify the undo information. This allows other private functions to use the routine (private version) without modifying the undo information. In order to guarantee that the TextChanged message means "text has already changed", NewUndo() must be called -after- making the modification. (even though the Undo object has to be created before the modifications) TextSet is different from TextChanged because documents will typically use only the latter for setting their "unsaved" flag. Functionality yet to be implemented: Search & replace for text and styles combined
JStyledText::JStyledText | ( | const JStyledText & | source | ) |
|
override |
|
protectedvirtual |
Font substitution to hopefully make all characters render. Returns true if font was modified.
Reimplemented in JXStyledText.
|
protectedvirtual |
Called before broadcasting changes. Derived classes can override this to adjust the style of the affected range of text. Ranges are in/out variables because the changes often slop out beyond the initial range.
The endpoints of the ranges are only allowed to move outward. The contents of *styles can change, but the length must remain the same. Ranges must be expanded to include all the changed elements.
Reimplemented in JXFSInputBase::StyledText.
JStyledText::TextIndex JStyledText::AdjustTextIndex | ( | const TextIndex & | index, |
const JInteger | charDelta | ||
) | const |
Optimized by starting JStringIterator at known TextIndex.
JStyledText::TextIndex JStyledText::BackwardDelete | ( | const TextIndex & | lineStart, |
const TextIndex & | caretIndex, | ||
const bool | deleteToTabStop, | ||
JString * | returnText = nullptr , |
||
JRunArray< JFont > * | returnStyle = nullptr , |
||
JUndo ** | undo = nullptr |
||
) |
Delete characters preceding the insertion caret.
Returns index of character after caret.
|
protected |
|
protected |
Separate from BroadcastTextChanged() because that function may modify the range before sending the message.
Returns the font to use when inserting at the specified point.
JStyledText::TextRange JStyledText::CharToTextRange | ( | const TextIndex * | lineStart, |
const JCharacterRange & | charRange | ||
) | const |
Optimized by starting JStringIterator at start of line, computed by using binary search.
lineStart can be nullptr
bool JStyledText::CleanRightMargin | ( | TextIndex * | caretIndex, |
const TextRange & | selectionRange, | ||
const bool | coerce | ||
) |
If coerce, paragraphs are detected by looking only for blank lines. Otherwise, they are detected by blank lines or a change in the line prefix.
The work done by this function can be changed by calling SetCRMRuleList().
JStyledText::TextRange JStyledText::CleanWhitespace | ( | const TextRange & | range, |
const bool | align | ||
) |
void JStyledText::ClearCRMRuleList | ( | ) |
With no other rules, the default is to match [ \t]*
|
inline |
void JStyledText::ClearUndo | ( | ) |
Avoid calling this whenever possible since it throws away -all- undo information.
|
static |
|
static |
bool JStyledText::Copy | ( | const TextRange & | range, |
JString * | text, | ||
JRunArray< JFont > * | style = nullptr |
||
) | const |
Returns true if there was anything to copy. style can be nullptr. If function returns false, text and style are not modified.
bool JStyledText::CRMGetPrefix | ( | TextIndex * | startIndex, |
const TextIndex & | beyondEndIndex, | ||
JString * | linePrefix, | ||
JSize * | columnCount, | ||
JIndex * | ruleIndex | ||
) | const |
Returns the prefix to be used for each line and updates *startIndex to point to the first character after the prefix.
columnCount can be greater than linePrefix->GetCharacterCount() because of tabs.
Returns false if the entire range qualifies as a prefix.
Returns the number of spaces to which the tab is equivalent by rounding up to the nearest multiple of GetCRMTabCharCount(). The default value for this is 8 since this is what all UNIX programs use.
textColumn starts at 1 at the left margin.
Returns true if the given range is matched by any "rest" pattern.
void JStyledText::DeactivateCurrentUndo | ( | ) |
void JStyledText::DeleteText | ( | const TextRange & | range | ) |
We create JSTUndoTyping so keys pressed after the delete key count as part of the undo task.
void JStyledText::DisposeConstIterator | ( | JStringIterator * | iter | ) | const |
|
inline |
Derived classes can override this to enforce restrictions on the text. Return false if the text cannot be used at all.
Reimplemented in JXInputField::StyledText.
void JStyledText::ForwardDelete | ( | const TextIndex & | lineStart, |
const TextIndex & | caretIndex, | ||
const bool | deleteToTabStop, | ||
JString * | returnText = nullptr , |
||
JRunArray< JFont > * | returnStyle = nullptr , |
||
JUndo ** | undo = nullptr |
||
) |
Delete characters following the insertion caret.
|
inline |
JIndex JStyledText::GetColumnForChar | ( | const TextIndex & | lineStart, |
const TextIndex & | location | ||
) | const |
Returns the column that the specified character is in, given the start of the line. If the caret is at the far left, it is column 1.
Since this is only useful with monospace fonts, the CRM tab width is used to calculate the column when tabs are encountered, by calling CRMGetTabWidth().
JStringIterator * JStyledText::GetConstIterator | ( | const JStringIterator::Position | pos, |
const TextIndex & | index | ||
) | const |
|
inline |
|
inline |
|
inline |
|
inline |
JStyledText::TextIndex JStyledText::GetParagraphEnd | ( | const TextIndex & | index | ) | const |
Return the index of the newline that ends the paragraph that contains the character at the given location. This function is required to work for charIndex > text length.
JStyledText::TextIndex JStyledText::GetParagraphStart | ( | const TextIndex & | index | ) | const |
Return the index of the first character in the paragraph that contains the character at the given location. This function is required to work for charIndex == 0.
JStyledText::TextIndex JStyledText::GetPartialWordEnd | ( | const TextIndex & | index | ) | const |
Return the index of the last character of the partial word at the given location. This function is required to work for charIndex > text length.
Example: get_word Get142TheWordABCGood ^ ^ ^ ^ ^ ^ ^ ^
JStyledText::TextIndex JStyledText::GetPartialWordStart | ( | const TextIndex & | index | ) | const |
Return the index of the first character of the partial word at the given location. This function is required to work for charIndex == 0.
Example: get_word Get142TheWordABCGood ABCDe ^ ^ ^ ^ ^ ^ ^ ^ ^ ^
|
inline |
|
inline |
JStyledText::TextIndex JStyledText::GetWordEnd | ( | const TextIndex & | index | ) | const |
Return the index of the last character of the word at the given location. This function is required to work for charIndex > text length.
JStyledText::TextIndex JStyledText::GetWordStart | ( | const TextIndex & | index | ) | const |
Return the index of the first character of the word at the given location. This function is required to work for charIndex == 0.
|
inline |
JStyledText::TextRange JStyledText::Indent | ( | const TextRange & | origRange, |
const JSize | tabCount = 1 |
||
) |
Insert tabs at the beginning of every line within the given range. The first line is assumed to start at the beginning of the range.
JUndo * JStyledText::InsertCharacter | ( | const TextRange & | replaceRange, |
const JUtf8Character & | key, | ||
const JFont & | font, | ||
TextCount * | count | ||
) |
JStyledText::TextRange JStyledText::InsertSpacesForTab | ( | const TextIndex & | lineStart, |
const TextRange & | replaceRange | ||
) |
Insert spaces to use up the same amount of space that a tab would use.
|
inline |
bool JStyledText::IsCharacterInWord | ( | const JUtf8Character & | c | ) | const |
Returns true if the given character should be considered part of a word.
|
inline |
Return true if the given character range is a single, complete word.
Return true if the given character range is a single, complete word.
bool JStyledText::MoveText | ( | const TextRange & | srcRange, |
const TextIndex & | origDestIndex, | ||
const bool | copy, | ||
TextRange * | newRange | ||
) |
|
protectedvirtual |
Reimplemented in JXStyledText.
Derived classes should return true if FilterText() needs to be called. This is an optimization, to avoid copying the data if nothing needs to be done to it.
Reimplemented in JXInputField::StyledText.
JStyledText::TextRange JStyledText::Outdent | ( | const TextRange & | origRange, |
const JSize | tabCount = 1 , |
||
const bool | force = false |
||
) |
Remove tabs from the beginning of every line within the given range. The first line is assumed to start at the beginning of the range.
JStyledText::TextRange JStyledText::Paste | ( | const TextRange & | range, |
const JString & | text, | ||
const JRunArray< JFont > * | style = nullptr |
||
) |
style can be nullptr
bool JStyledText::ReadPlainText | ( | const JString & | fileName, |
PlainTextFormat * | format, | ||
const bool | acceptBinaryFile = true |
||
) |
bool JStyledText::ReadPrivateFormat | ( | std::istream & | input | ) |
See WritePrivateFormat() for version information.
Clears undo history.
|
static |
See WritePrivateFormat() for version information.
void JStyledText::Redo | ( | ) |
|
static |
Returns true if we had to remove any characters that ContainsIllegalChars() would flag.
style can be nullptr or empty.
JStyledText::TextRange JStyledText::ReplaceAllInRange | ( | const TextRange & | range, |
const JRegex & | regex, | ||
const bool | entireWord, | ||
const JString & | replaceStr, | ||
JInterpolate * | interpolator, | ||
const bool | preserveCase | ||
) |
Replace every occurrence of the search pattern with the replace string. Returns the resulting text range.
JStyledText::TextCount JStyledText::ReplaceMatch | ( | const JStringMatch & | match, |
const JString & | replaceStr, | ||
JInterpolate * | interpolator, | ||
const bool | preserveCase, | ||
const bool | createUndo = true |
||
) |
Replace the specified range with the given replace text.
void JStyledText::RestyleAll | ( | ) |
JStringMatch JStyledText::SearchBackward | ( | const TextIndex & | startIndex, |
const JRegex & | regex, | ||
const bool | entireWord, | ||
const bool | wrapSearch, | ||
bool * | wrapped | ||
) | const |
Look for the match before the given position.
bool JStyledText::SearchBackward | ( | std::function< bool(const JFont &)> | match, |
const TextIndex & | startIndex, | ||
const bool | wrapSearch, | ||
bool * | wrapped, | ||
TextRange * | range | ||
) | const |
JStringMatch JStyledText::SearchForward | ( | const TextIndex & | startIndex, |
const JRegex & | regex, | ||
const bool | entireWord, | ||
const bool | wrapSearch, | ||
bool * | wrapped | ||
) | const |
We only support regular expressions because there is no advantage in optimizing for literal strings. Look for the next match beyond the given position.
bool JStyledText::SearchForward | ( | std::function< bool(const JFont &)> | match, |
const TextIndex & | startIndex, | ||
const bool | wrapSearch, | ||
bool * | wrapped, | ||
TextRange * | range | ||
) | const |
|
inline |
void JStyledText::SetAllFontNameAndSize | ( | const JString & | name, |
const JSize | size, | ||
const bool | clearUndo | ||
) |
This function is useful for unstyled text editors that allow the user to change the font and size.
It preserves the styles, in case they are controlled by the program. (e.g. context sensitive hilighting)
You can choose whether or not to throw out all Undo information. Unstyled text editors can usually preserve Undo, since they will not allow the user to modify styles. (We explicitly ask for this because it is too easy to forget about the issue.)
Modifies existing undo's. Does not create a new undo.
void JStyledText::SetCharacterInWordFunction | ( | const std::function< bool(const JUtf8Character &)> | f | ) |
Set the function that determines if a character is part of a word. The default is JIsAlnum(), which uses [A-Za-z0-9].
void JStyledText::SetCRMLineWidth | ( | const JSize | charCount | ) |
void JStyledText::SetCRMRuleList | ( | CRMRuleList * | ruleList, |
const bool | teOwnsRuleList | ||
) |
To find the first line of a paragraph, one searches backward for a blank line: one that matches any CRMRule::first. The following line is the beginning of the paragraph.
Each following line is part of the paragraph if it is not blank: no CRMRule::first matches the entire line, and neither does the CRMRule::rest corresponding to the CRMRule::first that matched the first line of the paragraph.
The prefix of the first line is unchanged. The prefix of the following lines is calculated from using CRMRule::replace on the prefix of the first line.
void JStyledText::SetCRMTabCharCount | ( | const JSize | charCount | ) |
|
inline |
|
inline |
|
inline |
|
inline |
bool JStyledText::SetFontColor | ( | const TextRange & | range, |
const JColorID | color, | ||
const bool | clearUndo | ||
) |
bool JStyledText::SetFontItalic | ( | const TextRange & | range, |
const bool | italic, | ||
const bool | clearUndo | ||
) |
bool JStyledText::SetFontName | ( | const TextRange & | range, |
const JString & | name, | ||
const bool | clearUndo | ||
) |
Returns true if anything actually changed
Handles undo if !clearUndo
bool JStyledText::SetFontStrike | ( | const TextRange & | range, |
const bool | strike, | ||
const bool | clearUndo | ||
) |
bool JStyledText::SetFontStyle | ( | const TextRange & | range, |
const JFontStyle & | style, | ||
const bool | clearUndo | ||
) |
bool JStyledText::SetFontUnderline | ( | const TextRange & | range, |
const JSize | count, | ||
const bool | clearUndo | ||
) |
|
inline |
Returns false if illegal characters had to be removed.
Clears undo history.
style can safely be nullptr or itsStyles.
void JStyledText::SetUndoDepth | ( | const JSize | maxUndoCount | ) |
|
inline |
|
inline |
|
inline |
void JStyledText::Undo | ( | ) |
|
inline |
|
inline |
void JStyledText::WritePlainText | ( | const JString & | fileName, |
const PlainTextFormat | format | ||
) | const |
void JStyledText::WritePlainText | ( | std::ostream & | output, |
const PlainTextFormat | format | ||
) | const |
void JStyledText::WritePrivateFormat | ( | std::ostream & | output | ) | const |
|
static |
We have to be able to write each version because this is what we put on the clipboard.
version 1: initial version.
|
friend |
|
friend |
|
friend |
|
static |
|
static |
|
static |
|
static |
|
static |