mirror of
https://github.com/gnustep/libs-gui.git
synced 2025-05-31 20:40:47 +00:00
Get the fragmentRect used for layout from the text container
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/gui/trunk@8061 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
856b6b4a6b
commit
0ca2537bc8
1 changed files with 98 additions and 82 deletions
|
@ -52,8 +52,8 @@
|
||||||
|
|
||||||
#define HUGE 1e7
|
#define HUGE 1e7
|
||||||
|
|
||||||
static NSCharacterSet *selectionWordGranularitySet;
|
|
||||||
static NSCharacterSet *newlines;
|
static NSCharacterSet *newlines;
|
||||||
|
static NSCharacterSet *selectionWordGranularitySet;
|
||||||
static NSCharacterSet *invSelectionWordGranularitySet;
|
static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
|
|
||||||
@interface _GNULineLayoutInfo: NSObject
|
@interface _GNULineLayoutInfo: NSObject
|
||||||
|
@ -120,7 +120,6 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
- (NSRect) rectForCharacterIndex: (unsigned) index;
|
- (NSRect) rectForCharacterIndex: (unsigned) index;
|
||||||
- (NSRange) lineRangeForRect: (NSRect) aRect;
|
- (NSRange) lineRangeForRect: (NSRect) aRect;
|
||||||
- (NSSize) _sizeOfRange: (NSRange) range;
|
- (NSSize) _sizeOfRange: (NSRange) range;
|
||||||
- (float) width;
|
|
||||||
|
|
||||||
// return value is identical to the real line number
|
// return value is identical to the real line number
|
||||||
- (int) lineLayoutIndexForGlyphIndex: (unsigned) anIndex;
|
- (int) lineLayoutIndexForGlyphIndex: (unsigned) anIndex;
|
||||||
|
@ -154,6 +153,14 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
[self setSelectionWordGranularitySet: whitespace];
|
[self setSelectionWordGranularitySet: whitespace];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (id) init
|
||||||
|
{
|
||||||
|
self = [super init];
|
||||||
|
|
||||||
|
_lineLayoutInformation = [[NSMutableArray alloc] init];
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
- (void) dealloc
|
- (void) dealloc
|
||||||
{
|
{
|
||||||
RELEASE(_lineLayoutInformation);
|
RELEASE(_lineLayoutInformation);
|
||||||
|
@ -163,7 +170,7 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
|
|
||||||
- (void) setTextStorage: (NSTextStorage*)aTextStorage
|
- (void) setTextStorage: (NSTextStorage*)aTextStorage
|
||||||
{
|
{
|
||||||
DESTROY(_lineLayoutInformation);
|
[_lineLayoutInformation removeAllObjects];
|
||||||
|
|
||||||
[super setTextStorage: aTextStorage];
|
[super setTextStorage: aTextStorage];
|
||||||
}
|
}
|
||||||
|
@ -180,7 +187,7 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
for ((lineEnum = [_lineLayoutInformation objectEnumerator]);
|
for ((lineEnum = [_lineLayoutInformation objectEnumerator]);
|
||||||
(currentInfo = [lineEnum nextObject]);)
|
(currentInfo = [lineEnum nextObject]);)
|
||||||
{
|
{
|
||||||
retRect = NSUnionRect (retRect, currentInfo->lineRect);
|
retRect = NSUnionRect (retRect, currentInfo->usedRect);
|
||||||
}
|
}
|
||||||
return retRect;
|
return retRect;
|
||||||
}
|
}
|
||||||
|
@ -392,6 +399,17 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
return NSMakeRange(0, [_textStorage length]);
|
return NSMakeRange(0, [_textStorage length]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)invalidateGlyphsForCharacterRange:(NSRange)charRange
|
||||||
|
changeInLength:(int)delta
|
||||||
|
actualCharacterRange:(NSRange*)actualCharRange
|
||||||
|
{
|
||||||
|
// As we currenty dont have any glyph character mapping, we only have
|
||||||
|
// to ajust the ranges in the line layout infos
|
||||||
|
|
||||||
|
if (actualCharRange)
|
||||||
|
*actualCharRange = charRange;
|
||||||
|
}
|
||||||
|
|
||||||
- (void) invalidateLayoutForCharacterRange: (NSRange)aRange
|
- (void) invalidateLayoutForCharacterRange: (NSRange)aRange
|
||||||
isSoft: (BOOL)flag
|
isSoft: (BOOL)flag
|
||||||
actualCharacterRange: (NSRange*)actualRange
|
actualCharacterRange: (NSRange*)actualRange
|
||||||
|
@ -421,6 +439,10 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
if (!mask)
|
if (!mask)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
[self invalidateGlyphsForCharacterRange: invalidatedCharRange
|
||||||
|
changeInLength: delta
|
||||||
|
actualCharacterRange: NULL];
|
||||||
|
|
||||||
lineRange = [self rebuildForRange: aRange
|
lineRange = [self rebuildForRange: aRange
|
||||||
delta: delta
|
delta: delta
|
||||||
inTextContainer: aTextContainer];
|
inTextContainer: aTextContainer];
|
||||||
|
@ -495,21 +517,6 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
return [_textStorage sizeRange: aRange];
|
return [_textStorage sizeRange: aRange];
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSRect) frame
|
|
||||||
{
|
|
||||||
if ([self firstTextView] == nil)
|
|
||||||
{
|
|
||||||
NSSize size = [[_textContainers objectAtIndex: 0] containerSize];
|
|
||||||
return NSMakeRect(0, 0, size.width, size.height);
|
|
||||||
}
|
|
||||||
return [[self firstTextView] frame];
|
|
||||||
}
|
|
||||||
|
|
||||||
- (float) width
|
|
||||||
{
|
|
||||||
return [[_textContainers objectAtIndex: 0] containerSize].width;
|
|
||||||
}
|
|
||||||
|
|
||||||
- (unsigned) lineLayoutIndexForPoint: (NSPoint)point
|
- (unsigned) lineLayoutIndexForPoint: (NSPoint)point
|
||||||
{
|
{
|
||||||
int i;
|
int i;
|
||||||
|
@ -620,7 +627,7 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
// rect to the end of line
|
// rect to the end of line
|
||||||
- (NSRect) rectForCharacterIndex: (unsigned)index
|
- (NSRect) rectForCharacterIndex: (unsigned)index
|
||||||
{
|
{
|
||||||
float width = [self width];
|
float width = [[_textContainers objectAtIndex: 0] containerSize].width;
|
||||||
_GNULineLayoutInfo *currentInfo;
|
_GNULineLayoutInfo *currentInfo;
|
||||||
unsigned start;
|
unsigned start;
|
||||||
NSRect rect;
|
NSRect rect;
|
||||||
|
@ -700,8 +707,6 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
- (void) setNeedsDisplayForLineRange: (NSRange)redrawLineRange
|
- (void) setNeedsDisplayForLineRange: (NSRange)redrawLineRange
|
||||||
inTextContainer:(NSTextContainer *)aTextContainer
|
inTextContainer:(NSTextContainer *)aTextContainer
|
||||||
{
|
{
|
||||||
float width = [aTextContainer containerSize].width;
|
|
||||||
|
|
||||||
if ([_lineLayoutInformation count]
|
if ([_lineLayoutInformation count]
|
||||||
&& redrawLineRange.location < [_lineLayoutInformation count]
|
&& redrawLineRange.location < [_lineLayoutInformation count]
|
||||||
&& redrawLineRange.length)
|
&& redrawLineRange.length)
|
||||||
|
@ -709,6 +714,7 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
_GNULineLayoutInfo *firstInfo
|
_GNULineLayoutInfo *firstInfo
|
||||||
= [_lineLayoutInformation objectAtIndex: redrawLineRange.location];
|
= [_lineLayoutInformation objectAtIndex: redrawLineRange.location];
|
||||||
NSRect displayRect = firstInfo->lineRect;
|
NSRect displayRect = firstInfo->lineRect;
|
||||||
|
float width = [aTextContainer containerSize].width;
|
||||||
|
|
||||||
if (redrawLineRange.length > 1)
|
if (redrawLineRange.length > 1)
|
||||||
displayRect = NSUnionRect(displayRect,
|
displayRect = NSUnionRect(displayRect,
|
||||||
|
@ -717,13 +723,13 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
(int)NSMaxRange(redrawLineRange) - 1] lineRect]);
|
(int)NSMaxRange(redrawLineRange) - 1] lineRect]);
|
||||||
|
|
||||||
displayRect.size.width = width - displayRect.origin.x;
|
displayRect.size.width = width - displayRect.origin.x;
|
||||||
[[self firstTextView] setNeedsDisplayInRect: displayRect];
|
[[aTextContainer textView] setNeedsDisplayInRect: displayRect];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
- (BOOL) _relocLayoutArray: (NSMutableArray*)ghostArray
|
- (BOOL) _relocLayoutArray: (NSMutableArray*)ghostArray
|
||||||
offset: (int)relocOffset
|
offset: (int)relocOffset
|
||||||
floatTrift: (float*)yDisplacement
|
floatTrift: (float*)yDisplacement
|
||||||
{
|
{
|
||||||
_GNULineLayoutInfo *lastInfo = [_lineLayoutInformation lastObject];
|
_GNULineLayoutInfo *lastInfo = [_lineLayoutInformation lastObject];
|
||||||
// The start character in the ghostArray
|
// The start character in the ghostArray
|
||||||
|
@ -760,6 +766,7 @@ static NSCharacterSet *invSelectionWordGranularitySet;
|
||||||
if (yReloc)
|
if (yReloc)
|
||||||
{
|
{
|
||||||
currReloc->lineRect.origin.y += yReloc;
|
currReloc->lineRect.origin.y += yReloc;
|
||||||
|
currReloc->usedRect.origin.y += yReloc;
|
||||||
}
|
}
|
||||||
[_lineLayoutInformation addObject: currReloc];
|
[_lineLayoutInformation addObject: currReloc];
|
||||||
}
|
}
|
||||||
|
@ -790,42 +797,27 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
delta: (int)insertionDelta
|
delta: (int)insertionDelta
|
||||||
inTextContainer:(NSTextContainer *)aTextContainer
|
inTextContainer:(NSTextContainer *)aTextContainer
|
||||||
{
|
{
|
||||||
int aLine = 0;
|
|
||||||
NSPoint drawingPoint = NSZeroPoint;
|
NSPoint drawingPoint = NSZeroPoint;
|
||||||
float width = [aTextContainer containerSize].width;
|
float padding = [aTextContainer lineFragmentPadding];
|
||||||
unsigned startingIndex = 0;
|
unsigned startingIndex = 0;
|
||||||
unsigned currentLineIndex;
|
unsigned currentLineIndex;
|
||||||
// for optimization detection
|
|
||||||
NSMutableArray *ghostArray;
|
|
||||||
NSString *allText = [_textStorage string];
|
NSString *allText = [_textStorage string];
|
||||||
unsigned length = [allText length];
|
unsigned length = [allText length];
|
||||||
unsigned paraPos;
|
unsigned paraPos;
|
||||||
|
int maxLines = [_lineLayoutInformation count];
|
||||||
|
int aLine = 0;
|
||||||
|
// for optimization detection
|
||||||
|
NSMutableArray *ghostArray = nil;
|
||||||
|
|
||||||
// sanity check that it is possible to do the layout
|
if (maxLines)
|
||||||
if (width == 0.0)
|
|
||||||
{
|
{
|
||||||
NSLog(@"NSText formatting with empty frame");
|
int insertionLineIndex = [self lineLayoutIndexForGlyphIndex:
|
||||||
return NSMakeRange(0,0);
|
aRange.location];
|
||||||
}
|
int nextLine = [self lineLayoutIndexForGlyphIndex:
|
||||||
|
NSMaxRange(aRange) - insertionDelta] + 1;
|
||||||
|
|
||||||
if (_lineLayoutInformation == nil)
|
if (nextLine < maxLines)
|
||||||
{
|
{
|
||||||
_lineLayoutInformation = [[NSMutableArray alloc] init];
|
|
||||||
aLine = 0;
|
|
||||||
ghostArray = nil;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int insertionLineIndex = [self lineLayoutIndexForGlyphIndex:
|
|
||||||
aRange.location];
|
|
||||||
int nextLine = [self lineLayoutIndexForGlyphIndex:
|
|
||||||
NSMaxRange(aRange)] + 1;
|
|
||||||
int maxLines = [_lineLayoutInformation count];
|
|
||||||
|
|
||||||
aLine = MAX(0, insertionLineIndex - 1);
|
|
||||||
|
|
||||||
if (nextLine < maxLines)
|
|
||||||
{
|
|
||||||
// remember old array for optimization purposes
|
// remember old array for optimization purposes
|
||||||
ghostArray = AUTORELEASE([[_lineLayoutInformation
|
ghostArray = AUTORELEASE([[_lineLayoutInformation
|
||||||
subarrayWithRange:
|
subarrayWithRange:
|
||||||
|
@ -833,28 +825,27 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
maxLines - nextLine)]
|
maxLines - nextLine)]
|
||||||
mutableCopy]);
|
mutableCopy]);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
aLine = MAX(0, insertionLineIndex - 1);
|
||||||
ghostArray = nil;
|
if (aLine)
|
||||||
}
|
{
|
||||||
|
_GNULineLayoutInfo *lastValidLineInfo = [_lineLayoutInformation
|
||||||
if (aLine)
|
objectAtIndex: aLine - 1];
|
||||||
{
|
NSRect aRect = lastValidLineInfo->lineRect;
|
||||||
_GNULineLayoutInfo *lastValidLineInfo = [_lineLayoutInformation
|
|
||||||
objectAtIndex: aLine - 1];
|
startingIndex = NSMaxRange(lastValidLineInfo->lineRange);
|
||||||
NSRect aRect = lastValidLineInfo->lineRect;
|
drawingPoint = aRect.origin;
|
||||||
|
drawingPoint.y += aRect.size.height;
|
||||||
startingIndex = NSMaxRange(lastValidLineInfo->lineRange);
|
}
|
||||||
drawingPoint = aRect.origin;
|
|
||||||
drawingPoint.y += aRect.size.height;
|
[_lineLayoutInformation removeObjectsInRange:
|
||||||
}
|
NSMakeRange (aLine, maxLines - aLine)];
|
||||||
|
|
||||||
[_lineLayoutInformation removeObjectsInRange:
|
|
||||||
NSMakeRange (aLine, [_lineLayoutInformation count] - aLine)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!length)
|
if (!length)
|
||||||
{
|
{
|
||||||
|
float width = [aTextContainer containerSize].width;
|
||||||
|
|
||||||
// FIXME: This should be done via extra line fragment
|
// FIXME: This should be done via extra line fragment
|
||||||
// If there is no text add one empty box
|
// If there is no text add one empty box
|
||||||
[_lineLayoutInformation
|
[_lineLayoutInformation
|
||||||
|
@ -877,6 +868,7 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
BOOL isBuckled = NO;
|
BOOL isBuckled = NO;
|
||||||
NSString *paragraph;
|
NSString *paragraph;
|
||||||
unsigned position; // Position in NSString.
|
unsigned position; // Position in NSString.
|
||||||
|
NSRect remainingRect = NSZeroRect;
|
||||||
|
|
||||||
// Determine the range of the next paragraph of text (in 'para') and set
|
// Determine the range of the next paragraph of text (in 'para') and set
|
||||||
// 'paraPos' to point after the terminating newline character (if any).
|
// 'paraPos' to point after the terminating newline character (if any).
|
||||||
|
@ -898,12 +890,34 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
|
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
NSRect currentLineRect = NSMakeRect (0, drawingPoint.y, 0, 0);
|
NSRect fragmentRect;
|
||||||
|
NSRect usedLineRect;
|
||||||
|
float width;
|
||||||
NSRange currentLineRange;
|
NSRange currentLineRange;
|
||||||
// starts with zero, do not confuse with startingLineCharIndex
|
// starts with zero, do not confuse with startingLineCharIndex
|
||||||
unsigned localLineStartIndex = [lScanner scanLocation];
|
unsigned localLineStartIndex = [lScanner scanLocation];
|
||||||
unsigned scannerPosition = localLineStartIndex;
|
unsigned scannerPosition = localLineStartIndex;
|
||||||
|
|
||||||
|
if (NSIsEmptyRect(remainingRect))
|
||||||
|
fragmentRect = NSMakeRect (0, drawingPoint.y, HUGE, HUGE);
|
||||||
|
else
|
||||||
|
fragmentRect = remainingRect;
|
||||||
|
|
||||||
|
fragmentRect = [aTextContainer lineFragmentRectForProposedRect: fragmentRect
|
||||||
|
sweepDirection: NSLineSweepRight
|
||||||
|
movementDirection: NSLineMoveDown
|
||||||
|
remainingRect: &remainingRect];
|
||||||
|
if (NSIsEmptyRect(fragmentRect))
|
||||||
|
{
|
||||||
|
// No more space in the text container, give up doing the layout
|
||||||
|
return NSMakeRange(aLine, MAX(1, [_lineLayoutInformation count] - aLine));
|
||||||
|
}
|
||||||
|
|
||||||
|
width = fragmentRect.size.width - 2 * padding;
|
||||||
|
usedLineRect = fragmentRect;
|
||||||
|
usedLineRect.origin.x += padding;
|
||||||
|
usedLineRect.size = NSZeroSize;
|
||||||
|
|
||||||
// scan the individual words to the end of the line
|
// scan the individual words to the end of the line
|
||||||
while (![lScanner isAtEnd])
|
while (![lScanner isAtEnd])
|
||||||
{
|
{
|
||||||
|
@ -927,14 +941,13 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
currentStringRange = NSUnionRange(trailingSpacesRange,
|
currentStringRange = NSUnionRange(trailingSpacesRange,
|
||||||
currentStringRange);
|
currentStringRange);
|
||||||
|
|
||||||
// evaluate size of current word and line so far
|
// evaluate size of current word
|
||||||
advanceSize = [self _sizeOfRange:
|
advanceSize = [self _sizeOfRange:
|
||||||
NSMakeRange (currentStringRange.location + position,
|
NSMakeRange (currentStringRange.location + position,
|
||||||
currentStringRange.length)];
|
currentStringRange.length)];
|
||||||
|
|
||||||
// handle case where single word is broader than width
|
// handle case where single word is broader than width
|
||||||
// (buckle word) <!> unfinished and untested
|
// (buckle word)
|
||||||
// for richText (absolute position see above)
|
|
||||||
if (advanceSize.width > width)
|
if (advanceSize.width > width)
|
||||||
{
|
{
|
||||||
if (isBuckled)
|
if (isBuckled)
|
||||||
|
@ -950,7 +963,7 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
startingLineCharIndex, lastVisibleCharIndex)];
|
startingLineCharIndex, lastVisibleCharIndex)];
|
||||||
}
|
}
|
||||||
isBuckled = NO;
|
isBuckled = NO;
|
||||||
currentLineRect.size = currentSize;
|
usedLineRect.size = currentSize;
|
||||||
scannerPosition = localLineStartIndex + lastVisibleCharIndex +1;
|
scannerPosition = localLineStartIndex + lastVisibleCharIndex +1;
|
||||||
[lScanner setScanLocation: scannerPosition];
|
[lScanner setScanLocation: scannerPosition];
|
||||||
break;
|
break;
|
||||||
|
@ -966,7 +979,7 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
isBuckled = NO;
|
isBuckled = NO;
|
||||||
|
|
||||||
// line to long
|
// line to long
|
||||||
if (currentLineRect.size.width + advanceSize.width > width ||
|
if (usedLineRect.size.width + advanceSize.width > width ||
|
||||||
isBuckled)
|
isBuckled)
|
||||||
{
|
{
|
||||||
// end of line -> word wrap
|
// end of line -> word wrap
|
||||||
|
@ -976,7 +989,7 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add next word
|
// Add next word
|
||||||
currentLineRect = NSUnionRect (currentLineRect,
|
usedLineRect = NSUnionRect (usedLineRect,
|
||||||
NSMakeRect (drawingPoint.x,
|
NSMakeRect (drawingPoint.x,
|
||||||
drawingPoint.y,
|
drawingPoint.y,
|
||||||
advanceSize.width,
|
advanceSize.width,
|
||||||
|
@ -992,21 +1005,23 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
{
|
{
|
||||||
// Add information for the line break
|
// Add information for the line break
|
||||||
scannerPosition += eol.length;
|
scannerPosition += eol.length;
|
||||||
currentLineRect.size.width += 1;
|
usedLineRect.size.width += 1;
|
||||||
// FIXME: This should use the real font size!!
|
// FIXME: This should use the real font size!!
|
||||||
if (!currentLineRect.size.height)
|
if (!usedLineRect.size.height)
|
||||||
currentLineRect.size.height = 12;
|
usedLineRect.size.height = 12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLineRange = NSMakeRange (startingLineCharIndex,
|
currentLineRange = NSMakeRange (startingLineCharIndex,
|
||||||
scannerPosition - localLineStartIndex);
|
scannerPosition - localLineStartIndex);
|
||||||
[self setLineFragmentRect: currentLineRect
|
// Adjust the height of the line fragment rect, as this will the to big
|
||||||
|
fragmentRect.size.height = usedLineRect.size.height;
|
||||||
|
[self setLineFragmentRect: fragmentRect
|
||||||
forGlyphRange: currentLineRange
|
forGlyphRange: currentLineRange
|
||||||
usedRect: currentLineRect];
|
usedRect: usedLineRect];
|
||||||
currentLineIndex++;
|
currentLineIndex++;
|
||||||
startingLineCharIndex = NSMaxRange(currentLineRange);
|
startingLineCharIndex = NSMaxRange(currentLineRange);
|
||||||
drawingPoint.y += currentLineRect.size.height;
|
drawingPoint.y += usedLineRect.size.height;
|
||||||
drawingPoint.x = 0;
|
drawingPoint.x = 0;
|
||||||
|
|
||||||
if (ghostArray != nil)
|
if (ghostArray != nil)
|
||||||
|
@ -1044,3 +1059,4 @@ scanRange(NSScanner *scanner, NSCharacterSet* aSet)
|
||||||
// end: central line formatting method------------------------------------
|
// end: central line formatting method------------------------------------
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue