You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
564 lines
20 KiB
564 lines
20 KiB
/***************************************************************************
|
|
khexedit.h - description
|
|
-------------------
|
|
begin : Die Mai 13 2003
|
|
copyright : (C) 2003 by Friedrich W. H. Kossebau
|
|
email : Friedrich.W.H@Kossebau.de
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This library is free software; you can redistribute it and/or *
|
|
* modify it under the terms of the GNU Library General Public *
|
|
* License version 2 as published by the Free Software Foundation. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#ifndef KHE_KHEXEDIT_H
|
|
#define KHE_KHEXEDIT_H
|
|
|
|
// qt specific
|
|
#include <tqclipboard.h>
|
|
// lib specific
|
|
// #include "khe.h"
|
|
#include "khexedit_export.h"
|
|
#include "kcolumnsview.h"
|
|
|
|
class TQTimer;
|
|
|
|
namespace KHE
|
|
{
|
|
|
|
class KCoordRange;
|
|
|
|
class KDataBuffer;
|
|
|
|
class KCharColumn;
|
|
class KValueColumn;
|
|
class KBufferColumn;
|
|
class KOffsetColumn;
|
|
class KBorderColumn;
|
|
|
|
class KBufferCursor;
|
|
class KBufferLayout;
|
|
class KBufferRanges;
|
|
|
|
class KController;
|
|
class KTabController;
|
|
class KNavigator;
|
|
class KValueEditor;
|
|
class KCharEditor;
|
|
|
|
class KBufferDrag;
|
|
|
|
class KCursor;
|
|
class KCharCodec;
|
|
|
|
class KHexEditPrivate;
|
|
|
|
|
|
/** the main widget
|
|
*
|
|
* The functions split up in helper functions and those that are complete.
|
|
*
|
|
* Complete functions can be called from the outside and leave the widget in
|
|
* a consistent state. They care for exceptions so one can safely call them in all
|
|
* situations (like empty buffer, cursor behind end etc.)
|
|
*
|
|
* Helper functions do only partial tasks and need to be completed. They often do not
|
|
* check for exceptions so one has to care for this.
|
|
*
|
|
*@author Friedrich W. H. Kossebau
|
|
*/
|
|
|
|
class KHEXEDIT_EXPORT KHexEdit : public KColumnsView
|
|
{
|
|
friend class KTabController;
|
|
friend class KNavigator;
|
|
friend class KEditor;
|
|
friend class KValueEditor;
|
|
friend class KCharEditor;
|
|
|
|
Q_OBJECT
|
|
TQ_OBJECT
|
|
Q_ENUMS( KResizeStyle KCoding )
|
|
TQ_PROPERTY( bool OverwriteMode READ isOverwriteMode WRITE setOverwriteMode )
|
|
TQ_PROPERTY( bool OverwriteOnly READ isOverwriteOnly WRITE setOverwriteOnly )
|
|
TQ_PROPERTY( bool Modified READ isModified WRITE setModified DESIGNABLE false )
|
|
TQ_PROPERTY( bool ReadOnly READ isReadOnly WRITE setReadOnly )
|
|
|
|
TQ_PROPERTY( int NoOfBytesPerLine READ noOfBytesPerLine WRITE setNoOfBytesPerLine )
|
|
TQ_PROPERTY( bool TabChangesFocus READ tabChangesFocus WRITE setTabChangesFocus )
|
|
|
|
//TQ_PROPERTY( bool hasSelectedData READ hasSelectedData )
|
|
//TQ_PROPERTY( TQByteArray SelectedData READ selectedData )
|
|
TQ_PROPERTY( KResizeStyle ResizeStyle READ resizeStyle WRITE setResizeStyle )
|
|
TQ_PROPERTY( int StartOffset READ startOffset WRITE setStartOffset )
|
|
TQ_PROPERTY( int FirstLineOffset READ firstLineOffset WRITE setFirstLineOffset )
|
|
//_PROPERTY( int undoDepth READ undoDepth WRITE setUndoDepth )
|
|
//_PROPERTY( bool undoRedoEnabled READ isUndoRedoEnabled WRITE setUndoRedoEnabled )
|
|
// value column
|
|
TQ_PROPERTY( KCoding Coding READ coding WRITE setCoding )
|
|
TQ_PROPERTY( int ByteSpacingWidth READ byteSpacingWidth WRITE setByteSpacingWidth )
|
|
TQ_PROPERTY( int NoOfGroupedBytes READ noOfGroupedBytes WRITE setNoOfGroupedBytes )
|
|
TQ_PROPERTY( int GroupSpacingWidth READ groupSpacingWidth WRITE setGroupSpacingWidth )
|
|
TQ_PROPERTY( int BinaryGapWidth READ binaryGapWidth WRITE setBinaryGapWidth )
|
|
// char column
|
|
TQ_PROPERTY( bool ShowUnprintable READ showUnprintable WRITE setShowUnprintable )
|
|
TQ_PROPERTY( TQChar SubstituteChar READ substituteChar WRITE setSubstituteChar )
|
|
|
|
public:
|
|
enum KResizeStyle { NoResize=0, LockGrouping=1, FullSizeUsage=2, MaxResizeStyleId=0xFF };
|
|
enum KCoding { HexadecimalCoding=0, DecimalCoding=1, OctalCoding=2, BinaryCoding=3, MaxCodingId=0xFFFF };
|
|
enum KEncoding { LocalEncoding=0, ISO8859_1Encoding=1, EBCDIC1047Encoding=2,
|
|
StartOfOwnEncoding=0x8000, MaxEncodingId=0xFFFF };
|
|
|
|
enum KBufferColumnId { ValueColumnId=1, CharColumnId=2 };
|
|
|
|
|
|
public:
|
|
KHexEdit( KDataBuffer *Buffer = 0, TQWidget *Parent = 0, const char *Name = 0, WFlags F = 0 );
|
|
virtual ~KHexEdit();
|
|
|
|
|
|
public: // KColumnsView API
|
|
virtual void drawContents( TQPainter *p, int cx, int cy, int cw, int ch );
|
|
|
|
public: // TQWidget API
|
|
// void focusInEvent( TQFocusEvent *FocusEvent ); // TODO: why don't these work?
|
|
// void focusOutEvent( TQFocusEvent *FocusEvent );
|
|
virtual bool eventFilter( TQObject *O, TQEvent *E );
|
|
|
|
virtual TQSize sizeHint() const;
|
|
virtual TQSize minimumSizeHint() const;
|
|
|
|
|
|
public: // value access
|
|
/** returns the index of the cursor position */
|
|
int cursorPosition() const;
|
|
/***/
|
|
bool isCursorBehind() const;
|
|
KBufferColumnId cursorColumn() const;
|
|
|
|
bool isOverwriteMode() const;
|
|
bool isOverwriteOnly() const;
|
|
bool isReadOnly() const;
|
|
bool isModified() const;
|
|
|
|
bool tabChangesFocus() const;
|
|
|
|
KResizeStyle resizeStyle() const;
|
|
int noOfBytesPerLine() const;
|
|
int startOffset() const;
|
|
int firstLineOffset() const;
|
|
|
|
bool offsetColumnVisible() const;
|
|
int visibleBufferColumns() const;
|
|
|
|
// value column
|
|
KCoding coding() const;
|
|
int/*KPixelX*/ byteSpacingWidth() const;
|
|
int noOfGroupedBytes() const;
|
|
int/*KPixelX*/ groupSpacingWidth() const;
|
|
int/*KPixelX*/ binaryGapWidth() const;
|
|
|
|
// char column
|
|
/** reports if "unprintable" chars (value<32) are displayed in the char column
|
|
* with their original character. Default is false
|
|
* @return @c true if original chars are displayed, otherwise @c false
|
|
*/
|
|
bool showUnprintable() const;
|
|
/** gives the used substitute character for "unprintable" chars, default is '.'
|
|
* @return substitute character
|
|
*/
|
|
TQChar substituteChar() const;
|
|
/** returns the actually used undefined character for "undefined" chars, default is '?' */
|
|
TQChar undefinedChar() const;
|
|
/**
|
|
* @return encoding used in the char column
|
|
*/
|
|
KEncoding encoding() const;
|
|
/**
|
|
* @return name of the encoding used in the char column
|
|
*/
|
|
const TQString &encodingName() const;
|
|
|
|
public: // logic value service
|
|
/** calculates the number of bytes per line that fit into a widget with the given size
|
|
* tests whether a vertical scroll bar is needed at all or not due to the given width
|
|
* takes the frame width into account
|
|
* @param TestSize Size the widget might have
|
|
* @return number of bytes per line that fit into a widget with the given size
|
|
*/
|
|
int fittingBytesPerLine( const TQSize &TestSize ) const;
|
|
/** detects the index of the byte at the given point
|
|
* @param Point in viewport coordinate system
|
|
* @return index of the byte that covers the point
|
|
*/
|
|
int indexByPoint(const TQPoint &Point ) const;
|
|
|
|
public:
|
|
/** returns true if there is a selected range in the array */
|
|
bool hasSelectedData() const;
|
|
/**
|
|
* @return deep copy of the selected data
|
|
*/
|
|
TQByteArray selectedData() const;
|
|
KSection selection() const;
|
|
|
|
public: // modification access
|
|
/** puts the cursor to the position of index, handles all drawing
|
|
* @param Index
|
|
*/
|
|
void setCursorPosition( int Index, bool Behind=false );
|
|
/** puts the cursor in the column at the pos of Point (in absolute coord), does not handle the drawing */
|
|
void placeCursor( const TQPoint &Point );
|
|
/***/
|
|
void setCursorColumn( KBufferColumnId );
|
|
// void repaintByte( int row, int column, bool Erase = true );
|
|
// void updateByte( int row, int column );
|
|
// void ensureByteVisible( int row, int column );
|
|
|
|
public slots:
|
|
/** */
|
|
void setDataBuffer( KDataBuffer *B );
|
|
|
|
/** switches the Offset column on/off */
|
|
void toggleOffsetColumn( bool Visible );
|
|
/** */
|
|
void showBufferColumns( int Columns );
|
|
/** scrolls the view as much as needed to have the cursor fully visible */
|
|
void ensureCursorVisible();
|
|
|
|
// setting parameters
|
|
/** sets the resizestyle for the value column. Default is KHE::FullSizeUsage */
|
|
void setResizeStyle( KResizeStyle Style );
|
|
/** sets whether the widget is readonly or not, Default is true.
|
|
* If the databuffer which is worked on can't be written the widget stays readonly
|
|
*/
|
|
virtual void setReadOnly( bool b );
|
|
/** sets whether the widget is overwriteonly or not. Default is false. */
|
|
virtual void setOverwriteOnly( bool b );
|
|
/** sets whether the widget is in overwrite mode or not. Default is true. */
|
|
virtual void setOverwriteMode( bool b );
|
|
/** sets whether the data should be treated modified or not */
|
|
virtual void setModified( bool b );
|
|
/** sets whether on a tab key there should be switched from the char column back to the value column
|
|
* or be switched to the next focusable widget. Default is false
|
|
*/
|
|
virtual void setTabChangesFocus( bool b = true );
|
|
//
|
|
/** sets the number of bytes per line, switching the resize style to KHE::NoResize */
|
|
virtual void setNoOfBytesPerLine( int NoCpL );
|
|
/** sets absolut offset of the data */
|
|
void setStartOffset( int SO );
|
|
/** sets offset of the char in the upper left corner */
|
|
void setFirstLineOffset( int FLO );
|
|
// value column parameters
|
|
/** sets the spacing between the bytes in the value column
|
|
* @param BSW spacing between the bytes in pixels
|
|
* default is 3
|
|
*/
|
|
void setByteSpacingWidth( int/*KPixelX*/ BSW ) ;
|
|
/** sets the number of grouped bytes in the value column
|
|
* @param NoGB numbers of grouped bytes, 0 means no grouping
|
|
* default is 4
|
|
*/
|
|
void setNoOfGroupedBytes( int NoGB );
|
|
/** sets the spacing between the groups of bytes in the value column
|
|
* @param GSW spacing between the groups in pixels
|
|
* default is 9
|
|
*/
|
|
void setGroupSpacingWidth( int/*KPixelX*/ GSW );
|
|
/** sets the spacing in the middle of a binary byte in the value column
|
|
* @param BinaryGapW spacing in the middle of a binary in pixels
|
|
* returns true if there was a change
|
|
*/
|
|
void setBinaryGapWidth( int BGW );
|
|
/** sets the spacing in the value column
|
|
* @param ByteSpacingWidth spacing between the bytes in pixels
|
|
* @param NoOfGroupedBytes numbers of grouped bytes, 0 means no grouping
|
|
* @param GroupSpacingWidth spacing between the groups in pixels
|
|
* Default is 4 for NoOfGroupedBytes
|
|
*/
|
|
void setBufferSpacing( KPixelX ByteSpacingWidth, int NoOfGroupedBytes = 0, KPixelX GroupSpacingWidth = 0 );
|
|
/** sets the format of the value column. Default is KHE::HexadecimalCoding */
|
|
void setCoding( KCoding C );
|
|
// char column parameters
|
|
/** sets whether "unprintable" chars (>32) should be displayed in the char column
|
|
* with their corresponding character.
|
|
* @param SU
|
|
* returns true if there was a change
|
|
*/
|
|
void setShowUnprintable( bool SU = true );
|
|
/** sets the substitute character for "unprintable" chars
|
|
* returns true if there was a change
|
|
*/
|
|
void setSubstituteChar( TQChar SC );
|
|
/** sets the undefined character for "undefined" chars
|
|
* returns true if there was a change
|
|
*/
|
|
void setUndefinedChar( TQChar UC );
|
|
/** sets the encoding of the char column. Default is KHE::LocalEncoding.
|
|
* If the encoding is not available the format will not be changed. */
|
|
void setEncoding( KEncoding C );
|
|
/** sets the encoding of the char column. Default is KHE::LocalEncoding.
|
|
* If the encoding is not available the format will not be changed.
|
|
* @param Encoding name of the encoding
|
|
*/
|
|
void setEncoding( const TQString& Encoding );
|
|
|
|
// interaction
|
|
/** de-/selects all data */
|
|
void selectAll( bool select );
|
|
/** de-/selects all data */
|
|
void select( KSection S );
|
|
/** selects word at index, returns true if there is one */
|
|
bool selectWord( /*unsigned*/ int Index /*, Chartype*/ );
|
|
/** removes the selected data, takes care of the cursor */
|
|
virtual void removeSelectedData();
|
|
/** inserts */
|
|
virtual void insert( const TQByteArray &D );
|
|
|
|
// clipboard interaction
|
|
virtual void copy();
|
|
virtual void cut();
|
|
virtual void paste();
|
|
|
|
// zooming
|
|
virtual void zoomIn( int PointInc );
|
|
virtual void zoomIn();
|
|
virtual void zoomOut( int PointInc );
|
|
virtual void zoomOut();
|
|
virtual void zoomTo( int PointSize );
|
|
virtual void unZoom();
|
|
|
|
// cursor control
|
|
/** we have focus again, start the timer */
|
|
virtual void startCursor();
|
|
/** we lost focus, stop the timer */
|
|
virtual void stopCursor();
|
|
/** simply pauses any blinking, i.e. ignores any calls to blinkCursor */
|
|
virtual void pauseCursor( bool LeaveEdit = false );
|
|
/** undoes pauseCursor */
|
|
virtual void unpauseCursor();
|
|
|
|
|
|
signals:
|
|
/** Index of the byte that was clicked */
|
|
void clicked( int Index );
|
|
/** Index of the byte that was double clicked */
|
|
void doubleClicked( int Index );
|
|
|
|
void cursorPositionChanged( int Index );
|
|
/** selection has changed */
|
|
void selectionChanged( int StartIndex, int EndIndex );
|
|
/** there is a cut available or not */
|
|
void cutAvailable( bool Really );
|
|
/** there is a copy available or not */
|
|
void copyAvailable( bool Really );
|
|
/** there has been a change to the buffer */
|
|
void bufferChanged( int StartIndex, int EndIndex );
|
|
|
|
|
|
protected: // TQWidget API
|
|
virtual void keyPressEvent( TQKeyEvent *KeyEvent );
|
|
virtual void resizeEvent( TQResizeEvent *ResizeEvent );
|
|
virtual void showEvent( TQShowEvent *e );
|
|
|
|
protected: // TQScrollView API
|
|
virtual void contentsMousePressEvent( TQMouseEvent *e );
|
|
virtual void contentsMouseReleaseEvent( TQMouseEvent * e );
|
|
virtual void contentsMouseMoveEvent( TQMouseEvent *e );
|
|
virtual void contentsMouseDoubleClickEvent( TQMouseEvent * e );
|
|
virtual void contentsDragEnterEvent( TQDragEnterEvent *e );
|
|
virtual void contentsDragMoveEvent( TQDragMoveEvent *e );
|
|
virtual void contentsDragLeaveEvent( TQDragLeaveEvent * );
|
|
virtual void contentsDropEvent( TQDropEvent *e );
|
|
virtual void contentsWheelEvent( TQWheelEvent *e );
|
|
// virtual void contentsContextMenuEvent( TQContextMenuEvent *e );
|
|
|
|
protected: // KColumnsView API
|
|
virtual void setNoOfLines( int NewNoOfLines );
|
|
|
|
|
|
protected: // element accessor functions
|
|
KValueColumn& valueColumn();
|
|
KCharColumn& charColumn();
|
|
KBufferColumn& activeColumn();
|
|
KBufferColumn& inactiveColumn();
|
|
const KValueColumn& valueColumn() const;
|
|
const KCharColumn& charColumn() const;
|
|
const KBufferColumn& activeColumn() const;
|
|
const KBufferColumn& inactiveColumn() const;
|
|
|
|
protected: // atomic ui operations
|
|
/** handles screen update in case of a change to any of the width sizes
|
|
*/
|
|
void updateViewByWidth();
|
|
/** repaints all the parts that are signed as changed */
|
|
void repaintChanged();
|
|
|
|
protected: // drawing related operations
|
|
/** recreates the cursor pixmaps and paints active and inactive cursors if doable */
|
|
void updateCursor();
|
|
void createCursorPixmaps();
|
|
void pointPainterToCursor( TQPainter &Painter, const KBufferColumn &Column ) const;
|
|
/** draws the blinking cursor or removes it */
|
|
void paintActiveCursor( bool CursorOn );
|
|
void paintInactiveCursor( bool CursorOn );
|
|
void paintLine( KBufferColumn *C, int Line, KSection Positions );
|
|
|
|
protected: // partial operations
|
|
void handleMouseMove( const TQPoint& Point );
|
|
KBufferDrag *dragObject( TQWidget *Parent = 0 ) const;
|
|
void pasteFromSource( TQMimeSource *Source );
|
|
/** removes the section from the databuffer and updates all affected values */
|
|
KSection removeData( KSection Indizes );
|
|
/** sets ChangedRange to the range of VisibleRange that is actually changed
|
|
* @return true if there was a change within the visible range
|
|
*/
|
|
bool hasChanged( const KCoordRange &VisibleRange, KCoordRange *ChangedRange ) const;
|
|
void handleInternalDrag( TQDropEvent *e );
|
|
|
|
protected:
|
|
/** recalcs all dependant values with the actual NoOfBytesPerLine */
|
|
void adjustToLayoutNoOfBytesPerLine();
|
|
/** recalcs a tqlayout due to the resize style that fits into the view size
|
|
* and updates the dependant values
|
|
*/
|
|
void adjustLayoutToSize();
|
|
/** */
|
|
void updateLength();
|
|
/** calls updateContent for the Column */
|
|
void updateColumn( KColumn &Column );
|
|
|
|
protected slots:
|
|
/** gets called by the cursor blink timer */
|
|
void blinkCursor();
|
|
/** gets called by the scroll timer (for mouse selection) */
|
|
void autoScrollTimerDone();
|
|
/** */
|
|
void clipboardChanged();
|
|
/** */
|
|
void startDrag();
|
|
|
|
protected slots: // TQWidget API
|
|
virtual void fontChange( const TQFont &OldFont );
|
|
|
|
|
|
protected:
|
|
/** Buffer with the data */
|
|
KDataBuffer *DataBuffer;
|
|
|
|
/** holds the logical tqlayout */
|
|
KBufferLayout *BufferLayout;
|
|
/** */
|
|
KBufferCursor *BufferCursor;
|
|
/** */
|
|
KBufferRanges *BufferRanges;
|
|
|
|
|
|
protected:
|
|
KOffsetColumn *OffsetColumn;
|
|
KBorderColumn *FirstBorderColumn;
|
|
KValueColumn *ValueColumn;
|
|
KBorderColumn *SecondBorderColumn;
|
|
KCharColumn *CharColumn;
|
|
|
|
/** points to the column with keyboard focus */
|
|
KBufferColumn *ActiveColumn;
|
|
/** points to the column without keyboard focus (if there is) */
|
|
KBufferColumn *InactiveColumn;
|
|
|
|
/** the actual input controller */
|
|
KController *Controller;
|
|
/** */
|
|
KTabController *TabController;
|
|
/** */
|
|
KNavigator *Navigator;
|
|
/** */
|
|
KValueEditor *ValueEditor;
|
|
/** */
|
|
KCharEditor *CharEditor;
|
|
|
|
protected:
|
|
/** Timer that controls the blinking of the cursor */
|
|
TQTimer *CursorBlinkTimer;
|
|
/** Timer that triggers ensureCursorVisible function calls */
|
|
TQTimer *ScrollTimer;
|
|
/* TQTimer *ChangeIntervalTimer, */
|
|
/** Timer to start a drag */
|
|
TQTimer *DragStartTimer;
|
|
/** timer to measure whether the time between a double click and the following counts for a tripleclick */
|
|
TQTimer *TrippleClickTimer;
|
|
|
|
/** object to store the blinking cursor pixmaps */
|
|
KCursor *CursorPixmaps;
|
|
/** */
|
|
KCharCodec *Codec;
|
|
|
|
protected:
|
|
/** point at which the current double click happended (used by TrippleClick) */
|
|
TQPoint DoubleClickPoint;
|
|
/** line in which the current double click happended (used by TrippleClick) */
|
|
int DoubleClickLine;
|
|
/** point at which the current dragging started */
|
|
TQPoint DragStartPoint;
|
|
/** */
|
|
TQClipboard::Mode ClipboardMode;
|
|
/** font size as set by user (used for zooming) */
|
|
int DefaultFontSize;
|
|
|
|
protected: // parameters
|
|
/** style of resizing */
|
|
KResizeStyle ResizeStyle;
|
|
/** */
|
|
KEncoding Encoding;
|
|
|
|
/** flag whether the widget is set to readonly. Cannot override the databuffer's setting, of course. */
|
|
bool ReadOnly:1;
|
|
/** flag if only overwrite is allowed */
|
|
bool OverWriteOnly:1;
|
|
/** flag if overwrite mode is active */
|
|
bool OverWrite:1;
|
|
/** flag if a mouse button is pressed */
|
|
bool MousePressed:1;
|
|
/** flag if a double click is happening */
|
|
bool InDoubleClick:1;
|
|
/** flag if a Drag'n'Drop is happening */
|
|
bool InDnD:1;
|
|
/** flag if a drag might have started */
|
|
bool DragStartPossible:1;
|
|
/** flag if the cursor should be invisible */
|
|
bool CursorPaused:1;
|
|
/** flag if the cursor is visible */
|
|
bool BlinkCursorVisible:1;
|
|
/** flag whether the font is changed due to a zooming */
|
|
bool InZooming:1;
|
|
|
|
private:
|
|
/** the binary compatibility saving helper */
|
|
KHexEditPrivate* d;
|
|
|
|
private: // Disabling copy constructor and operator= - not useful
|
|
KHexEdit( const KHexEdit & );
|
|
KHexEdit &operator=( const KHexEdit & );
|
|
};
|
|
|
|
|
|
inline const KValueColumn& KHexEdit::valueColumn() const { return *ValueColumn; }
|
|
inline const KCharColumn& KHexEdit::charColumn() const { return *CharColumn; }
|
|
inline const KBufferColumn& KHexEdit::activeColumn() const { return *ActiveColumn; }
|
|
inline const KBufferColumn& KHexEdit::inactiveColumn() const { return *InactiveColumn; }
|
|
|
|
inline KValueColumn& KHexEdit::valueColumn() { return *ValueColumn; }
|
|
inline KCharColumn& KHexEdit::charColumn() { return *CharColumn; }
|
|
inline KBufferColumn& KHexEdit::activeColumn() { return *ActiveColumn; }
|
|
inline KBufferColumn& KHexEdit::inactiveColumn() { return *InactiveColumn; }
|
|
|
|
}
|
|
|
|
#endif
|