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.
685 lines
20 KiB
685 lines
20 KiB
/* |
|
* This file is part of KMail. |
|
* Copyright (c) 2009 Constantin Berzan <exit3219@gmail.com> |
|
* |
|
* Based on KMail code by: |
|
* Copyright (c) 1997 Markus Wuebben <markus.wuebben@kde.org> |
|
* |
|
* This program is free software; you can redistribute it and/or modify |
|
* it under the terms of the GNU General Public License as published by |
|
* the Free Software Foundation; either version 2 of the License, or |
|
* (at your option) any later version. |
|
* |
|
* This program is distributed in the hope that it will be useful, |
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
* GNU General Public License for more details. |
|
* |
|
* You should have received a copy of the GNU General Public License along |
|
* with this program; if not, write to the Free Software Foundation, Inc., |
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
|
*/ |
|
|
|
#ifndef __KMComposeWin |
|
#define __KMComposeWin |
|
|
|
// KMail includes |
|
#include "composer.h" |
|
#include "messagecomposer/recipient/recipientseditor.h" |
|
|
|
// Qt includes |
|
#include <QFont> |
|
#include <QList> |
|
|
|
// KDE includes |
|
#include <kglobalsettings.h> |
|
|
|
// LIBKDEPIM includes |
|
#include <messagecomposer/kmeditor.h> |
|
|
|
#include "messagecomposer/messagesender.h" |
|
|
|
// KDEPIMLIBS includes |
|
#include <kmime/kmime_message.h> |
|
#include <kmime/kmime_headers.h> |
|
|
|
// Other includes |
|
#include "kleo/enum.h" |
|
#include <composer/composerviewbase.h> |
|
|
|
class QByteArray; |
|
class QCheckBox; |
|
class QGridLayout; |
|
class QLabel; |
|
class QPushButton; |
|
class QSplitter; |
|
|
|
class CodecAction; |
|
class KMComposeWin; |
|
class KMComposerEditor; |
|
class KSelectAction; |
|
class KAction; |
|
class KJob; |
|
class KToggleAction; |
|
class KUrl; |
|
class KRecentFilesAction; |
|
class SnippetWidget; |
|
class SelectSpecialChar; |
|
class AttachmentMissingWarning; |
|
class ExternalEditorWarning; |
|
|
|
namespace boost { |
|
template <typename T> class shared_ptr; |
|
} |
|
|
|
namespace Sonnet { |
|
class DictionaryComboBox; |
|
} |
|
|
|
namespace KPIMIdentities { |
|
class Identity; |
|
} |
|
|
|
namespace KPIMTextEdit { |
|
class SelectSpecialChar; |
|
} |
|
|
|
namespace KIO { |
|
class Job; |
|
} |
|
|
|
namespace MessageComposer { |
|
class Composer; |
|
} |
|
|
|
namespace KPIM { |
|
class SpellCheckLineEdit; |
|
} |
|
|
|
namespace MessageComposer { |
|
class ComposerLineEdit; |
|
} |
|
namespace MailCommon { |
|
class FolderRequester; |
|
} |
|
|
|
namespace PimCommon { |
|
class TranslatorWidget; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
class KMComposeWin : public KMail::Composer |
|
{ |
|
Q_OBJECT |
|
Q_CLASSINFO("D-Bus Interface", "org.kde.kmail.mailcomposer") |
|
|
|
friend class ::KMComposerEditor; |
|
|
|
private: // mailserviceimpl, kmkernel, kmcommands, callback, kmmainwidget |
|
explicit KMComposeWin(const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, TemplateContext context = NoTemplate, |
|
uint identity = 0, const QString & textSelection = QString(), |
|
const QString & customTemplate = QString() ); |
|
~KMComposeWin(); |
|
|
|
public: |
|
static Composer *create( const KMime::Message::Ptr &msg, bool lastSignState, bool lastEncryptState, TemplateContext context = NoTemplate, |
|
uint identity = 0, const QString & textSelection = QString(), |
|
const QString & customTemplate = QString() ); |
|
|
|
QString dbusObjectPath() const; |
|
QString smartQuote( const QString & msg ); |
|
|
|
/** |
|
* Start of D-Bus callable stuff. The D-Bus methods need to be public slots, |
|
* otherwise they can't be accessed. |
|
*/ |
|
// TODO clean-up dbus stuff; make the adaptor a friend; etc. |
|
public slots: |
|
|
|
Q_SCRIPTABLE void send( int how ); |
|
|
|
Q_SCRIPTABLE void addAttachmentsAndSend( const KUrl::List & urls, |
|
const QString & comment, |
|
int how ); |
|
|
|
Q_SCRIPTABLE void addAttachment( const KUrl & url, |
|
const QString & comment ); |
|
|
|
Q_SCRIPTABLE void addAttachment( const QString & name, |
|
KMime::Headers::contentEncoding cte, |
|
const QString& charset, |
|
const QByteArray & data, |
|
const QByteArray & mimeType ); |
|
|
|
/** |
|
* End of D-Bus callable stuff |
|
*/ |
|
|
|
signals: |
|
void identityChanged( const KPIMIdentities::Identity &identity ); |
|
|
|
|
|
public: // kmkernel, kmcommands, callback |
|
/** |
|
* Set the message the composer shall work with. This discards |
|
* previous messages without calling applyChanges() on them before. |
|
*/ |
|
void setMessage( const KMime::Message::Ptr &newMsg, bool lastSignState = false, bool lastEncryptState = false, |
|
bool mayAutoSign=true, bool allowDecryption=false, bool isModified=false ); |
|
|
|
void setCurrentTransport( int transportId ); |
|
|
|
/** |
|
* Use the given folder as sent-mail folder if the given folder exists. |
|
* Else show an error message and use the default sent-mail folder as |
|
* sent-mail folder. |
|
*/ |
|
void setFcc( const QString &idString ); |
|
|
|
/** |
|
* Disables word wrap completely. No wrapping at all will occur, not even |
|
* at the right end of the editor. |
|
* This is useful when sending invitations. |
|
*/ |
|
void disableWordWrap(); |
|
|
|
/** |
|
* Disables HTML completely. It disables HTML at the point of calling this and disables it |
|
* again when sending the message, to be sure. Useful when sending invitations. |
|
* This will <b>not</b> remove the actions for activating HTML mode again, it is only |
|
* meant for automatic invitation sending. |
|
* Also calls @sa disableHtml() internally. |
|
*/ |
|
void forceDisableHtml(); |
|
|
|
/** |
|
* Returns @c true while the message composing is in progress. |
|
*/ |
|
bool isComposing() const { return mComposerBase && mComposerBase->isComposing(); } |
|
|
|
/** |
|
* Set the text selection the message is a response to. |
|
*/ |
|
void setTextSelection( const QString& selection ); |
|
|
|
/** |
|
* Set custom template to be used for the message. |
|
*/ |
|
void setCustomTemplate( const QString& customTemplate ); |
|
|
|
/** Disabled signing and encryption completely for this composer window. */ |
|
void setSigningAndEncryptionDisabled( bool v ) |
|
{ |
|
mSigningAndEncryptionExplicitlyDisabled = v; |
|
} |
|
/** |
|
* If this folder is set, the original message is inserted back after |
|
* canceling |
|
*/ |
|
void setFolder(const Akonadi::Collection &aFolder ) { mFolder = aFolder; } |
|
/** |
|
* Sets the focus to the edit-widget. |
|
*/ |
|
void setReplyFocus( bool hasMessage = true ); |
|
|
|
/** |
|
* Sets the focus to the subject line edit. For use when creating a |
|
* message to a known recipient. |
|
*/ |
|
void setFocusToSubject(); |
|
|
|
bool insertFromMimeData( const QMimeData *source, bool forceAttachment = false ); |
|
|
|
void setCurrentReplyTo(const QString&); |
|
void setCollectionForNewMessage( const Akonadi::Collection& folder); |
|
|
|
void addExtraCustomHeaders( const QMap<QByteArray, QString> &header); |
|
|
|
private: |
|
/** |
|
* Write settings to app's config file. |
|
*/ |
|
void writeConfig( void ); |
|
|
|
|
|
/** |
|
* Returns message of the composer. To apply the user changes to the |
|
* message, call applyChanges() first. |
|
*/ |
|
KMime::Message::Ptr msg() const { return mMsg; } |
|
/** |
|
* Returns true if the message was modified by the user. |
|
*/ |
|
bool isModified() const; |
|
bool isComposerModified() const; |
|
void changeModifiedState( bool modified ); |
|
|
|
|
|
|
|
/** |
|
* determines whether inline signing/encryption is selected |
|
*/ |
|
bool inlineSigningEncryptionSelected(); |
|
|
|
|
|
/** |
|
* Tries to find the given mimetype @p type in the KDE Mimetype registry. |
|
* If found, returns its localized description, otherwise the @p type |
|
* in lowercase. |
|
*/ |
|
static QString prettyMimeType( const QString &type ); |
|
|
|
public slots: // kmkernel, callback |
|
void slotSendNow(); |
|
/** |
|
* Switch wordWrap on/off |
|
*/ |
|
void slotWordWrapToggled( bool ); |
|
|
|
void slotToggleMarkup(); |
|
void slotTextModeChanged( KRichTextEdit::Mode ); |
|
void htmlToolBarVisibilityChanged( bool visible ); |
|
void slotSpellcheckDoneClearStatus(); |
|
void autoSaveMessage(bool force = false); |
|
/** |
|
* Set whether the message should be treated as modified or not. |
|
*/ |
|
void setModified( bool modified ); |
|
void slotFetchJob(KJob*); |
|
|
|
private slots: |
|
/** |
|
* Disables the HTML mode, by hiding the HTML toolbar and unchecking the |
|
* "Formatting" action. Also, removes all rich-text formatting. |
|
*/ |
|
void disableHtml( MessageComposer::ComposerViewBase::Confirmation confirmation ); |
|
/** |
|
* Enables HTML mode, by showing the HTML toolbar and checking the |
|
* "Formatting" action |
|
*/ |
|
void enableHtml(); |
|
|
|
/** |
|
* Actions: |
|
*/ |
|
void slotPrint(); |
|
void slotPrintPreview(); |
|
|
|
void slotInsertRecentFile( const KUrl & ); |
|
void slotRecentListFileClear(); |
|
|
|
void slotSendNowVia( QAction * ); |
|
void slotSendLater(); |
|
void slotSendLaterVia( QAction * ); |
|
void getTransportMenu(); |
|
|
|
/** |
|
* Returns true when saving was successful. |
|
*/ |
|
void slotSaveDraft(); |
|
void slotSaveTemplate(); |
|
void slotNewComposer(); |
|
void slotNewMailReader(); |
|
void slotClose(); |
|
void slotHelp(); |
|
void slotUndo(); |
|
void slotRedo(); |
|
void slotCut(); |
|
void slotCopy(); |
|
void slotPaste(); |
|
void slotPasteAsAttachment(); |
|
void slotMarkAll(); |
|
void slotTranslatorWasClosed(); |
|
|
|
void slotFolderRemoved( const Akonadi::Collection& ); |
|
void slotLanguageChanged( const QString &language ); |
|
void slotFccFolderChanged(const Akonadi::Collection&); |
|
void slotEditorTextChanged(); |
|
void slotOverwriteModeChanged(); |
|
/** |
|
* toggle fixed width font. |
|
*/ |
|
void slotUpdateFont(); |
|
|
|
/** |
|
* Open addressbook editor dialog. |
|
*/ |
|
void slotAddrBook(); |
|
|
|
/** |
|
* Insert a file to the end of the text in the editor. |
|
*/ |
|
void slotInsertFile(); |
|
|
|
/** |
|
* Check spelling of text. |
|
*/ |
|
void slotSpellcheckConfig(); |
|
|
|
/** |
|
* Change crypto plugin to be used for signing/encrypting messages, |
|
* or switch to built-in OpenPGP code. |
|
*/ |
|
void slotSelectCryptoModule( bool init = false ); |
|
|
|
/** |
|
* XML-GUI stuff |
|
*/ |
|
void slotEditToolbars(); |
|
void slotUpdateToolbars(); |
|
void slotEditKeys(); |
|
|
|
/** |
|
* Read settings from app's config file. |
|
*/ |
|
void readConfig( bool reload = false ); |
|
|
|
/** |
|
* Change window title to given string. |
|
*/ |
|
void slotUpdWinTitle(); |
|
|
|
/** |
|
* Switch the icon to lock or unlock respectivly. |
|
* Change states of all encrypt check boxes in the attachments listview |
|
*/ |
|
void slotEncryptToggled( bool ); |
|
|
|
/** |
|
* Change states of all sign check boxes in the attachments listview |
|
*/ |
|
void slotSignToggled( bool ); |
|
|
|
void slotView(); |
|
|
|
/** |
|
* Update composer field to reflect new identity |
|
* @param initalChange if true, don't apply the template. This is useful when calling |
|
* this function from setMsg(), because there, the message already has the |
|
* template, and we want to avoid calling the template parser unnecessarily. |
|
*/ |
|
void slotIdentityChanged( uint uoid, bool initalChange = false ); |
|
|
|
void slotCursorPositionChanged(); |
|
|
|
void slotSpellCheckingStatus( const QString & status ); |
|
|
|
void slotDelayedApplyTemplate( KJob* ); |
|
|
|
void recipientEditorSizeHintChanged(); |
|
void setMaximumHeaderSize(); |
|
void slotDoDelayedSend( KJob* ); |
|
|
|
void slotCompletionModeChanged( KGlobalSettings::Completion ); |
|
void slotConfigChanged(); |
|
|
|
void slotPrintComposeResult( KJob *job ); |
|
|
|
//void slotEncryptChiasmusToggled( bool ); |
|
|
|
void slotSendFailed(const QString& msg , MessageComposer::ComposerViewBase::FailedType type); |
|
void slotSendSuccessful(); |
|
|
|
/** |
|
* toggle automatic spellchecking |
|
*/ |
|
void slotAutoSpellCheckingToggled( bool ); |
|
|
|
/** |
|
* Updates the visibility and text of the signature and encryption state indicators. |
|
*/ |
|
void slotUpdateSignatureAndEncrypionStateIndicators(); |
|
|
|
virtual void setAutoSaveFileName( const QString& fileName ); |
|
void slotSpellCheckingLanguage(const QString& language); |
|
void forceAutoSaveMessage(); |
|
void insertSpecialCharacter(); |
|
void charSelected(const QChar& c); |
|
void slotSaveAsFile(); |
|
void slotCreateAddressBookContact(); |
|
|
|
void slotAttachMissingFile(); |
|
void slotCloseAttachMissingFile(); |
|
void slotExplicitClosedMissingAttachment(); |
|
void slotVerifyMissingAttachmentTimeout(); |
|
void slotCheckSendNow(); |
|
|
|
void slotUpperCase(); |
|
void slotLowerCase(); |
|
|
|
void slotExternalEditorStarted(); |
|
void slotExternalEditorClosed(); |
|
|
|
|
|
public: // kmcommand |
|
// FIXME we need to remove these, but they're pure virtual in Composer. |
|
void addAttach( KMime::Content *msgPart ); |
|
|
|
const KPIMIdentities::Identity &identity() const; |
|
|
|
/** Don't check for forgotten attachments for a mail, eg. when sending out invitations. */ |
|
void disableForgottenAttachmentsCheck(); |
|
|
|
/** |
|
* Ignore the "sticky" setting of the transport combo box and prefer the X-KMail-Transport |
|
* header field of the message instead. |
|
* Do the same for the identity combo box, don't obey the "sticky" setting but use the |
|
* X-KMail-Identity header field instead. |
|
* |
|
* This is useful when sending out invitations, since you don't see the GUI and want the |
|
* identity and transport to be set to the values stored in the messages. |
|
*/ |
|
void ignoreStickyFields(); |
|
|
|
private: |
|
void updateSignature(uint uoid, uint uOldId); |
|
Kleo::CryptoMessageFormat cryptoMessageFormat() const; |
|
QString overwriteModeStr() const; |
|
void printComposeResult( KJob *job, bool preview ); |
|
void printComposer(bool preview); |
|
/** |
|
* Install grid management and header fields. If fields exist that |
|
* should not be there they are removed. Those that are needed are |
|
* created if necessary. |
|
*/ |
|
void rethinkFields( bool fromslot=false ); |
|
|
|
/** |
|
Connect signals for moving focus by arrow keys. Returns next edit. |
|
*/ |
|
QWidget *connectFocusMoving( QWidget *prev, QWidget *next ); |
|
|
|
/** |
|
* Show or hide header lines |
|
*/ |
|
void rethinkHeaderLine( int aValue, int aMask, int &aRow, |
|
QLabel *aLbl, QWidget *aEdt, |
|
QPushButton *aBtn = 0 ); |
|
|
|
void rethinkHeaderLine( int value, int mask, int &row, |
|
QLabel *lbl, QWidget *cbx, QCheckBox *chk ); // krazy:exclude=qclasses |
|
|
|
|
|
/** |
|
* Apply template to new or unmodified message. |
|
*/ |
|
void applyTemplate(uint uoid , uint uOldId); |
|
|
|
/** |
|
* Set the quote prefix according to identity. |
|
*/ |
|
void setQuotePrefix( uint uoid ); |
|
|
|
/** |
|
* Checks how many recipients are and warns if there are too many. |
|
* @return true, if the user accepted the warning and the message should be sent |
|
*/ |
|
bool checkRecipientNumber() const; |
|
|
|
bool checkTransport() const; |
|
/** |
|
* Initialization methods |
|
*/ |
|
void setupActions(); |
|
void setupStatusBar(QWidget *w); |
|
void setupEditor(); |
|
|
|
/** |
|
* Header fields. |
|
*/ |
|
QString subject() const; |
|
QString from() const; |
|
QString replyTo() const; |
|
|
|
void setCharset( const QByteArray &charset ); |
|
void setAutoCharset(); |
|
|
|
/** |
|
* Ask for confirmation if the message was changed before close. |
|
*/ |
|
virtual bool queryClose(); |
|
|
|
/** |
|
* Turn encryption on/off. If setByUser is true then a message box is shown |
|
* in case encryption isn't possible. |
|
*/ |
|
void setEncryption( bool encrypt, bool setByUser = false ); |
|
|
|
/** |
|
* Turn signing on/off. If setByUser is true then a message box is shown |
|
* in case signing isn't possible. |
|
*/ |
|
void setSigning( bool sign, bool setByUser = false ); |
|
|
|
MessageComposer::ComposerViewBase::MissingAttachment userForgotAttachment(); |
|
|
|
/** |
|
* Decrypt an OpenPGP block or strip off the OpenPGP envelope of a text |
|
* block with a clear text signature. This is only done if the given |
|
* string contains exactly one OpenPGP block. |
|
* This function is for example used to restore the unencrypted/unsigned |
|
* message text for editting. |
|
*/ |
|
static void decryptOrStripOffCleartextSignature( QByteArray & ); |
|
|
|
/** |
|
* Send the message. |
|
*/ |
|
void doSend( MessageComposer::MessageSender::SendMethod method=MessageComposer::MessageSender::SendDefault, |
|
MessageComposer::MessageSender::SaveIn saveIn = MessageComposer::MessageSender::SaveInNone ); |
|
|
|
void doDelayedSend( MessageComposer::MessageSender::SendMethod method, MessageComposer::MessageSender::SaveIn saveIn ); |
|
|
|
void changeCryptoAction(); |
|
void applyComposerSetting( MessageComposer::ComposerViewBase* mComposerBase ); |
|
/** |
|
* Creates a simple composer that creates a KMime::Message out of the composer content. |
|
* Crypto handling is not done, therefore the name "simple". |
|
* This is used when autosaving or printing a message. |
|
* |
|
* The caller takes ownership of the composer. |
|
*/ |
|
MessageComposer::Composer* createSimpleComposer(); |
|
|
|
bool canSignEncryptAttachments() const { |
|
return cryptoMessageFormat() != Kleo::InlineOpenPGPFormat; |
|
} |
|
|
|
QString addQuotesToText( const QString &inputText ) const; |
|
// helper method for rethinkFields |
|
int calcColumnWidth( int which, long allShowing, int width ) const; |
|
|
|
|
|
/** Initialize header fields. Should be called on new messages |
|
if they are not set manually. E.g. before composing. Calling |
|
of setAutomaticFields(), see below, is still required. */ |
|
void initHeader( KMime::Message *message, uint identity=0 ); |
|
|
|
inline bool encryptToSelf(); |
|
|
|
private: |
|
QWidget *mMainWidget; |
|
Sonnet::DictionaryComboBox *mDictionaryCombo; |
|
MessageComposer::ComposerLineEdit *mEdtFrom, *mEdtReplyTo; |
|
KPIM::SpellCheckLineEdit *mEdtSubject; |
|
QLabel *mLblIdentity, *mLblTransport, *mLblFcc; |
|
QLabel *mLblFrom, *mLblReplyTo; |
|
QLabel *mLblSubject; |
|
QLabel *mDictionaryLabel; |
|
QCheckBox *mBtnIdentity, *mBtnDictionary, *mBtnTransport, *mBtnFcc; |
|
bool mDone; |
|
|
|
KMime::Message::Ptr mMsg; |
|
QGridLayout *mGrid; |
|
QString mTextSelection; |
|
QString mCustomTemplate; |
|
bool mLastSignActionState, mLastEncryptActionState, mSigningAndEncryptionExplicitlyDisabled; |
|
bool mLastIdentityHasSigningKey, mLastIdentityHasEncryptionKey; |
|
Akonadi::Collection mFolder; |
|
long mShowHeaders; |
|
bool mForceDisableHtml; // Completely disable any HTML. Useful when sending invitations in the |
|
// mail body. |
|
int mNumHeaders; |
|
QFont mBodyFont, mFixedFont; |
|
uint mId; |
|
TemplateContext mContext; |
|
|
|
KAction *mCleanSpace; |
|
KRecentFilesAction *mRecentAction; |
|
|
|
KToggleAction *mSignAction, *mEncryptAction, *mRequestMDNAction; |
|
KToggleAction *mUrgentAction, *mAllFieldsAction, *mFromAction; |
|
KToggleAction *mReplyToAction; |
|
KToggleAction *mSubjectAction; |
|
KToggleAction *mIdentityAction, *mTransportAction, *mFccAction; |
|
KToggleAction *mWordWrapAction, *mFixedFontAction, *mAutoSpellCheckingAction; |
|
KToggleAction *mDictionaryAction, *mSnippetAction, *mTranslateAction; |
|
|
|
KToggleAction *markupAction; |
|
|
|
CodecAction *mCodecAction; |
|
KSelectAction *mCryptoModuleAction; |
|
|
|
KAction *mFindText, *mFindNextText, *mReplaceText, *mSelectAll; |
|
|
|
QSplitter *mHeadersToEditorSplitter; |
|
QWidget* mHeadersArea; |
|
QSplitter *mSplitter; |
|
QSplitter *mSnippetSplitter; |
|
QByteArray mOriginalPreferredCharset; |
|
|
|
//KToggleAction *mEncryptChiasmusAction; |
|
|
|
MessageComposer::Composer *mDummyComposer; |
|
// used for auto saving, printing, etc. Not for sending, which happens in ComposerViewBase |
|
QList< MessageComposer::Composer* > mMiscComposers; |
|
|
|
int mLabelWidth; |
|
|
|
QMenu *mActNowMenu; |
|
QMenu *mActLaterMenu; |
|
|
|
QString mdbusObjectPath; |
|
static int s_composerNumber; |
|
|
|
MessageComposer::ComposerViewBase* mComposerBase; |
|
|
|
|
|
SnippetWidget *mSnippetWidget; |
|
PimCommon::TranslatorWidget *mTranslatorWidget; |
|
QPointer<KPIMTextEdit::SelectSpecialChar> mSelectSpecialChar; |
|
AttachmentMissingWarning *mAttachmentMissing; |
|
ExternalEditorWarning *mExternalEditorWarning; |
|
QTimer *m_verifyMissingAttachment; |
|
QLabel *mSignatureStateIndicator; |
|
QLabel *mEncryptionStateIndicator; |
|
MailCommon::FolderRequester *mFccFolder; |
|
bool mPreventFccOverwrite; |
|
bool mCheckForForgottenAttachments; |
|
bool mIgnoreStickyFields; |
|
bool mWasModified; |
|
Akonadi::Collection mCollectionForNewMessage; |
|
QMap<QByteArray, QString> mExtraHeaders; |
|
}; |
|
|
|
#endif
|
|
|