diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 07ed3941..db80ecd5 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -333,6 +333,8 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
&ChatPage::initializeMentions,
user_mentions_popup_,
&popups::UserMentions::initializeMentions);
+ connect(
+ this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
try {
room_list_->cleanupInvites(cache::invites());
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 0516f87d..917bd785 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -127,7 +127,6 @@ public slots:
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
void decryptDownloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
const SecretsToDecrypt &secrets);
-
signals:
void connectionLost();
void connectionRestored();
@@ -176,6 +175,7 @@ signals:
void retrievedPresence(const QString &statusMsg, mtx::presence::PresenceState state);
void themeChanged();
void decryptSidebarChanged();
+ void chatFocusChanged(const bool focused);
//! Signals for device verificaiton
void receivedDeviceVerificationAccept(
diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp
index ec9b856f..15aeb12a 100644
--- a/src/LoginPage.cpp
+++ b/src/LoginPage.cpp
@@ -120,7 +120,7 @@ LoginPage::LoginPage(QWidget *parent)
password_input_ = new TextField(this);
password_input_->setLabel(tr("Password"));
password_input_->setEchoMode(QLineEdit::Password);
- password_input_->setToolTip("Your password.");
+ password_input_->setToolTip(tr("Your password."));
deviceName_ = new TextField(this);
deviceName_->setLabel(tr("Device name"));
@@ -129,8 +129,8 @@ LoginPage::LoginPage(QWidget *parent)
"If none is provided a default is used."));
serverInput_ = new TextField(this);
- serverInput_->setLabel("Homeserver address");
- serverInput_->setPlaceholderText("matrix.org");
+ serverInput_->setLabel(tr("Homeserver address"));
+ serverInput_->setPlaceholderText(tr("server.my:8787"));
serverInput_->setToolTip(tr("The address that can be used to contact you homeservers "
"client API.\nExample: https://server.my:8787"));
serverInput_->hide();
@@ -217,7 +217,7 @@ LoginPage::onMatrixIdEntered()
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
- "You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText("");
@@ -228,7 +228,7 @@ LoginPage::onMatrixIdEntered()
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
- "You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
@@ -385,7 +385,7 @@ LoginPage::onLoginButtonClicked()
if (!matrixid_input_->isValid()) {
error_matrixid_label_->show();
showError(error_matrixid_label_,
- "You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
} else {
error_matrixid_label_->setText("");
@@ -396,7 +396,7 @@ LoginPage::onLoginButtonClicked()
user = parse<User>(matrixid_input_->text().toStdString());
} catch (const std::exception &) {
showError(error_matrixid_label_,
- "You have entered an invalid Matrix ID e.g @joe:matrix.org");
+ tr("You have entered an invalid Matrix ID e.g @joe:matrix.org"));
return;
}
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 77269008..ab3c2cf2 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -59,6 +59,8 @@ MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, userSettings_{UserSettings::instance()}
{
+ instance_ = this;
+
setWindowTitle(0);
setObjectName("MainWindow");
@@ -130,6 +132,9 @@ MainWindow::MainWindow(QWidget *parent)
SLOT(iconActivated(QSystemTrayIcon::ActivationReason)));
connect(chat_page_, SIGNAL(contentLoaded()), this, SLOT(removeOverlayProgressBar()));
+
+ connect(this, &MainWindow::focusChanged, chat_page_, &ChatPage::chatFocusChanged);
+
connect(
chat_page_, &ChatPage::showUserSettingsPage, this, &MainWindow::showUserSettingsPage);
@@ -204,6 +209,19 @@ MainWindow::resizeEvent(QResizeEvent *event)
QMainWindow::resizeEvent(event);
}
+bool
+MainWindow::event(QEvent *event)
+{
+ auto type = event->type();
+ if (type == QEvent::WindowActivate) {
+ emit focusChanged(true);
+ } else if (type == QEvent::WindowDeactivate) {
+ emit focusChanged(false);
+ }
+
+ return QMainWindow::event(event);
+}
+
void
MainWindow::adjustSideBars()
{
@@ -296,8 +314,6 @@ MainWindow::showChatPage()
&Cache::secretChanged,
userSettingsPage_,
&UserSettingsPage::updateSecretStatus);
-
- instance_ = this;
}
void
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 0915a849..bb219813 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -61,10 +61,15 @@ class MainWindow : public QMainWindow
{
Q_OBJECT
+ Q_PROPERTY(int x READ x CONSTANT)
+ Q_PROPERTY(int y READ y CONSTANT)
+ Q_PROPERTY(int width READ width CONSTANT)
+ Q_PROPERTY(int height READ height CONSTANT)
+
public:
explicit MainWindow(QWidget *parent = nullptr);
- static MainWindow *instance() { return instance_; };
+ static MainWindow *instance() { return instance_; }
void saveCurrentWindowSize();
void openLeaveRoomDialog(const QString &room_id);
@@ -88,6 +93,7 @@ protected:
void closeEvent(QCloseEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
void showEvent(QShowEvent *event) override;
+ bool event(QEvent *event) override;
private slots:
//! Show or hide the sidebars based on window's size.
@@ -115,6 +121,9 @@ private slots:
virtual void setWindowTitle(int notificationCount);
+signals:
+ void focusChanged(const bool focused);
+
private:
bool loadJdenticonPlugin();
diff --git a/src/UserSettingsPage.cpp b/src/UserSettingsPage.cpp
index 0e3bd667..96c07d7c 100644
--- a/src/UserSettingsPage.cpp
+++ b/src/UserSettingsPage.cpp
@@ -88,14 +88,16 @@ UserSettings::load(std::optional<QString> profile)
settings.value("user/timeline/message_hover_highlight", false).toBool();
enlargeEmojiOnlyMessages_ =
settings.value("user/timeline/enlarge_emoji_only_msg", false).toBool();
- markdown_ = settings.value("user/markdown_enabled", true).toBool();
- typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
- sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
- readReceipts_ = settings.value("user/read_receipts", true).toBool();
- theme_ = settings.value("user/theme", defaultTheme_).toString();
- font_ = settings.value("user/font_family", "default").toString();
- avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
- decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool();
+ markdown_ = settings.value("user/markdown_enabled", true).toBool();
+ typingNotifications_ = settings.value("user/typing_notifications", true).toBool();
+ sortByImportance_ = settings.value("user/sort_by_unread", true).toBool();
+ readReceipts_ = settings.value("user/read_receipts", true).toBool();
+ theme_ = settings.value("user/theme", defaultTheme_).toString();
+ font_ = settings.value("user/font_family", "default").toString();
+ avatarCircles_ = settings.value("user/avatar_circles", true).toBool();
+ decryptSidebar_ = settings.value("user/decrypt_sidebar", true).toBool();
+ privacyScreen_ = settings.value("user/privacy_screen", false).toBool();
+ privacyScreenTimeout_ = settings.value("user/privacy_screen_timeout", 0).toInt();
shareKeysWithTrustedUsers_ =
settings.value("user/share_keys_with_trusted_users", true).toBool();
mobileMode_ = settings.value("user/mobile_mode", false).toBool();
@@ -293,6 +295,28 @@ UserSettings::setDecryptSidebar(bool state)
}
void
+UserSettings::setPrivacyScreen(bool state)
+{
+ if (state == privacyScreen_) {
+ return;
+ }
+ privacyScreen_ = state;
+ emit privacyScreenChanged(state);
+ save();
+}
+
+void
+UserSettings::setPrivacyScreenTimeout(int state)
+{
+ if (state == privacyScreenTimeout_) {
+ return;
+ }
+ privacyScreenTimeout_ = state;
+ emit privacyScreenTimeoutChanged(state);
+ save();
+}
+
+void
UserSettings::setFontSize(double size)
{
if (size == baseFontSize_)
@@ -539,6 +563,8 @@ UserSettings::save()
settings.setValue("avatar_circles", avatarCircles_);
settings.setValue("decrypt_sidebar", decryptSidebar_);
+ settings.setValue("privacy_screen", privacyScreen_);
+ settings.setValue("privacy_screen_timeout", privacyScreenTimeout_);
settings.setValue("share_keys_with_trusted_users", shareKeysWithTrustedUsers_);
settings.setValue("mobile_mode", mobileMode_);
settings.setValue("font_size", baseFontSize_);
@@ -628,6 +654,7 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
startInTrayToggle_ = new Toggle{this};
avatarCircles_ = new Toggle{this};
decryptSidebar_ = new Toggle(this);
+ privacyScreen_ = new Toggle{this};
shareKeysWithTrustedUsers_ = new Toggle(this);
groupViewToggle_ = new Toggle{this};
timelineButtonsToggle_ = new Toggle{this};
@@ -651,11 +678,13 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
cameraResolutionCombo_ = new QComboBox{this};
cameraFrameRateCombo_ = new QComboBox{this};
timelineMaxWidthSpin_ = new QSpinBox{this};
+ privacyScreenTimeout_ = new QSpinBox{this};
trayToggle_->setChecked(settings_->tray());
startInTrayToggle_->setChecked(settings_->startInTray());
avatarCircles_->setChecked(settings_->avatarCircles());
decryptSidebar_->setChecked(settings_->decryptSidebar());
+ privacyScreen_->setChecked(settings_->privacyScreen());
shareKeysWithTrustedUsers_->setChecked(settings_->shareKeysWithTrustedUsers());
groupViewToggle_->setChecked(settings_->groupView());
timelineButtonsToggle_->setChecked(settings_->buttonsInTimeline());
@@ -675,6 +704,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
startInTrayToggle_->setDisabled(true);
}
+ if (!settings_->privacyScreen()) {
+ privacyScreenTimeout_->setDisabled(true);
+ }
+
avatarCircles_->setFixedSize(64, 48);
auto uiLabel_ = new QLabel{tr("INTERFACE"), this};
@@ -715,6 +748,10 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
timelineMaxWidthSpin_->setMaximum(100'000'000);
timelineMaxWidthSpin_->setSingleStep(10);
+ privacyScreenTimeout_->setMinimum(0);
+ privacyScreenTimeout_->setMaximum(3600);
+ privacyScreenTimeout_->setSingleStep(10);
+
auto callsLabel = new QLabel{tr("CALLS"), this};
callsLabel->setFixedHeight(callsLabel->minimumHeight() + LayoutTopMargin);
callsLabel->setAlignment(Qt::AlignBottom);
@@ -808,6 +845,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
decryptSidebar_,
tr("Decrypt the messages shown in the sidebar.\nOnly affects messages in "
"encrypted chats."));
+ boxWrap(tr("Privacy Screen"),
+ privacyScreen_,
+ tr("When the window loses focus, the timeline will\nbe blurred."));
+ boxWrap(
+ tr("Privacy screen timeout (in seconds [0 - 3600])"),
+ privacyScreenTimeout_,
+ tr("Set timeout (in seconds) for how long after window loses\nfocus before the screen"
+ " will be blurred.\nSet to 0 to blur immediately after focus loss. Max value of 1 "
+ "hour (3600 seconds)"));
boxWrap(tr("Show buttons in timeline"),
timelineButtonsToggle_,
tr("Show buttons to quickly reply, react or access additional options next to each "
@@ -1066,7 +1112,15 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
connect(decryptSidebar_, &Toggle::toggled, this, [this](bool enabled) {
settings_->setDecryptSidebar(enabled);
- emit decryptSidebarChanged();
+ });
+
+ connect(privacyScreen_, &Toggle::toggled, this, [this](bool enabled) {
+ settings_->setPrivacyScreen(enabled);
+ if (enabled) {
+ privacyScreenTimeout_->setEnabled(true);
+ } else {
+ privacyScreenTimeout_->setDisabled(true);
+ }
});
connect(shareKeysWithTrustedUsers_, &Toggle::toggled, this, [this](bool enabled) {
@@ -1122,6 +1176,11 @@ UserSettingsPage::UserSettingsPage(QSharedPointer<UserSettings> settings, QWidge
this,
[this](int newValue) { settings_->setTimelineMaxWidth(newValue); });
+ connect(privacyScreenTimeout_,
+ qOverload<int>(&QSpinBox::valueChanged),
+ this,
+ [this](int newValue) { settings_->setPrivacyScreenTimeout(newValue); });
+
connect(
sessionKeysImportBtn, &QPushButton::clicked, this, &UserSettingsPage::importSessionKeys);
@@ -1155,6 +1214,7 @@ UserSettingsPage::showEvent(QShowEvent *)
startInTrayToggle_->setState(settings_->startInTray());
groupViewToggle_->setState(settings_->groupView());
decryptSidebar_->setState(settings_->decryptSidebar());
+ privacyScreen_->setState(settings_->privacyScreen());
shareKeysWithTrustedUsers_->setState(settings_->shareKeysWithTrustedUsers());
avatarCircles_->setState(settings_->avatarCircles());
typingNotifications_->setState(settings_->typingNotifications());
@@ -1169,6 +1229,7 @@ UserSettingsPage::showEvent(QShowEvent *)
enlargeEmojiOnlyMessages_->setState(settings_->enlargeEmojiOnlyMessages());
deviceIdValue_->setText(QString::fromStdString(http::client()->device_id()));
timelineMaxWidthSpin_->setValue(settings_->timelineMaxWidth());
+ privacyScreenTimeout_->setValue(settings_->privacyScreenTimeout());
WebRTCSession::instance().refreshDevices();
auto mics =
diff --git a/src/UserSettingsPage.h b/src/UserSettingsPage.h
index 15da235b..b65e1efc 100644
--- a/src/UserSettingsPage.h
+++ b/src/UserSettingsPage.h
@@ -67,6 +67,10 @@ class UserSettings : public QObject
bool avatarCircles READ avatarCircles WRITE setAvatarCircles NOTIFY avatarCirclesChanged)
Q_PROPERTY(bool decryptSidebar READ decryptSidebar WRITE setDecryptSidebar NOTIFY
decryptSidebarChanged)
+ Q_PROPERTY(
+ bool privacyScreen READ privacyScreen WRITE setPrivacyScreen NOTIFY privacyScreenChanged)
+ Q_PROPERTY(int privacyScreenTimeout READ privacyScreenTimeout WRITE setPrivacyScreenTimeout
+ NOTIFY privacyScreenTimeoutChanged)
Q_PROPERTY(int timelineMaxWidth READ timelineMaxWidth WRITE setTimelineMaxWidth NOTIFY
timelineMaxWidthChanged)
Q_PROPERTY(bool mobileMode READ mobileMode WRITE setMobileMode NOTIFY mobileModeChanged)
@@ -131,6 +135,8 @@ public:
void setAlertOnNotification(bool state);
void setAvatarCircles(bool state);
void setDecryptSidebar(bool state);
+ void setPrivacyScreen(bool state);
+ void setPrivacyScreenTimeout(int state);
void setPresence(Presence state);
void setRingtone(QString ringtone);
void setMicrophone(QString microphone);
@@ -154,6 +160,8 @@ public:
bool groupView() const { return groupView_; }
bool avatarCircles() const { return avatarCircles_; }
bool decryptSidebar() const { return decryptSidebar_; }
+ bool privacyScreen() const { return privacyScreen_; }
+ int privacyScreenTimeout() const { return privacyScreenTimeout_; }
bool markdown() const { return markdown_; }
bool typingNotifications() const { return typingNotifications_; }
bool sortByImportance() const { return sortByImportance_; }
@@ -201,6 +209,8 @@ signals:
void alertOnNotificationChanged(bool state);
void avatarCirclesChanged(bool state);
void decryptSidebarChanged(bool state);
+ void privacyScreenChanged(bool state);
+ void privacyScreenTimeoutChanged(int state);
void timelineMaxWidthChanged(int state);
void mobileModeChanged(bool mode);
void fontSizeChanged(double state);
@@ -241,6 +251,8 @@ private:
bool hasAlertOnNotification_;
bool avatarCircles_;
bool decryptSidebar_;
+ bool privacyScreen_;
+ int privacyScreenTimeout_;
bool shareKeysWithTrustedUsers_;
bool mobileMode_;
int timelineMaxWidth_;
@@ -320,6 +332,8 @@ private:
Toggle *avatarCircles_;
Toggle *useStunServer_;
Toggle *decryptSidebar_;
+ Toggle *privacyScreen_;
+ QSpinBox *privacyScreenTimeout_;
Toggle *shareKeysWithTrustedUsers_;
Toggle *mobileMode_;
QLabel *deviceFingerprintValue_;
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 3bb090df..5af5748e 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -55,8 +55,7 @@ utils::codepointIsEmoji(uint code)
{
// TODO: Be more precise here.
return (code >= 0x2600 && code <= 0x27bf) || (code >= 0x2b00 && code <= 0x2bff) ||
- (code >= 0x1f300 && code <= 0x1f3ff) || (code >= 0x1f000 && code <= 0x1faff) ||
- code == 0x200d;
+ (code >= 0x1f000 && code <= 0x1faff) || code == 0x200d || code == 0xfe0f;
}
QString
diff --git a/src/timeline/EventStore.cpp b/src/timeline/EventStore.cpp
index b16bf1d2..be4bc09e 100644
--- a/src/timeline/EventStore.cpp
+++ b/src/timeline/EventStore.cpp
@@ -242,9 +242,12 @@ EventStore::receivedSessionKey(const std::string &session_id)
return;
auto request = pending_key_requests.at(session_id);
- pending_key_requests.erase(session_id);
- olm::send_key_request_for(request.events.front(), request.request_id, true);
+ // Don't request keys again until Nheko is restarted (for now)
+ pending_key_requests[session_id].events.clear();
+
+ if (!request.events.empty())
+ olm::send_key_request_for(request.events.front(), request.request_id, true);
for (const auto &e : request.events) {
auto idx = idToIndex(e.event_id);
@@ -778,7 +781,8 @@ EventStore::fetchMore()
if (cache::client()->previousBatchToken(room_id_) != opts.from) {
nhlog::net()->warn("Cache cleared while fetching more messages, dropping "
"/messages response");
- emit fetchedMore();
+ if (!opts.to.empty())
+ emit fetchedMore();
return;
}
if (err) {
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 97af0065..93451976 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -128,6 +128,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
"UserProfile needs to be instantiated on the C++ side");
static auto self = this;
+ qmlRegisterSingletonType<MainWindow>(
+ "im.nheko", 1, 0, "MainWindow", [](QQmlEngine *, QJSEngine *) -> QObject * {
+ return MainWindow::instance();
+ });
qmlRegisterSingletonType<TimelineViewManager>(
"im.nheko", 1, 0, "TimelineManager", [](QQmlEngine *, QJSEngine *) -> QObject * {
return self;
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 23a960b8..74128865 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -36,6 +36,8 @@ class TimelineViewManager : public QObject
bool isInitialSync MEMBER isInitialSync_ READ isInitialSync NOTIFY initialSyncChanged)
Q_PROPERTY(
bool isNarrowView MEMBER isNarrowView_ READ isNarrowView NOTIFY narrowViewChanged)
+ Q_PROPERTY(
+ bool isWindowFocused MEMBER isWindowFocused_ READ isWindowFocused NOTIFY focusChanged)
public:
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
@@ -54,6 +56,7 @@ public:
Q_INVOKABLE TimelineModel *activeTimeline() const { return timeline_; }
Q_INVOKABLE bool isInitialSync() const { return isInitialSync_; }
bool isNarrowView() const { return isNarrowView_; }
+ bool isWindowFocused() const { return isWindowFocused_; }
Q_INVOKABLE void openImageOverlay(QString mxcUrl, QString eventId) const;
Q_INVOKABLE QColor userColor(QString id, QColor background);
Q_INVOKABLE QString escapeEmoji(QString str) const;
@@ -83,11 +86,17 @@ signals:
void inviteUsers(QStringList users);
void showRoomList();
void narrowViewChanged();
+ void focusChanged();
public slots:
void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
void receivedSessionKey(const std::string &room_id, const std::string &session_id);
void initWithMessages(const std::vector<QString> &roomIds);
+ void chatFocusChanged(bool focused)
+ {
+ isWindowFocused_ = focused;
+ emit focusChanged();
+ }
void setHistoryView(const QString &room_id);
TimelineModel *getHistoryView(const QString &room_id)
@@ -145,8 +154,9 @@ private:
TimelineModel *timeline_ = nullptr;
CallManager *callManager_ = nullptr;
- bool isInitialSync_ = true;
- bool isNarrowView_ = false;
+ bool isInitialSync_ = true;
+ bool isNarrowView_ = false;
+ bool isWindowFocused_ = false;
QHash<QString, QColor> userColors;
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index 715c1c42..274ed927 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -244,7 +244,7 @@ UserProfile::changeUsername(QString username)
if (isGlobalUserProfile()) {
// change global
http::client()->set_displayname(
- username.toStdString(), [this](mtx::http::RequestErr err) {
+ username.toStdString(), [](mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("could not change username");
return;
@@ -408,4 +408,4 @@ UserProfile::getGlobalProfileData()
globalAvatarUrl = QString::fromStdString(res.avatar_url);
emit avatarUrlChanged();
});
-}
\ No newline at end of file
+}
|