2009-10-24 16:15:28 +00:00
|
|
|
/**
|
|
|
|
* \file GuiCompare.cpp
|
|
|
|
* This file is part of LyX, the document processor.
|
|
|
|
* Licence details can be found in the file COPYING.
|
|
|
|
*
|
|
|
|
* \author Vincent van Ravesteijn
|
|
|
|
*
|
|
|
|
* Full author contact details are available in file CREDITS.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
|
|
|
|
#include "GuiCompare.h"
|
|
|
|
|
2022-11-27 13:16:00 -05:00
|
|
|
#include "GuiApplication.h"
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
#include "Buffer.h"
|
|
|
|
#include "BufferView.h"
|
|
|
|
#include "BufferList.h"
|
|
|
|
#include "buffer_funcs.h"
|
2022-11-27 13:16:00 -05:00
|
|
|
#include "ColorCache.h"
|
2009-10-25 13:41:46 +00:00
|
|
|
#include "Compare.h"
|
2009-10-24 16:15:28 +00:00
|
|
|
#include "FuncRequest.h"
|
|
|
|
#include "GuiView.h"
|
|
|
|
#include "LyXRC.h"
|
|
|
|
#include "qt_helpers.h"
|
|
|
|
|
|
|
|
#include "frontends/alert.h"
|
|
|
|
|
|
|
|
#include "support/debug.h"
|
|
|
|
#include "support/filetools.h"
|
|
|
|
#include "support/FileName.h"
|
|
|
|
#include "support/gettext.h"
|
|
|
|
|
2018-07-08 16:35:38 +02:00
|
|
|
#include <QDialogButtonBox>
|
2009-10-24 16:15:28 +00:00
|
|
|
#include <QThread>
|
|
|
|
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
using namespace lyx::support;
|
|
|
|
|
|
|
|
namespace lyx {
|
|
|
|
namespace frontend {
|
|
|
|
|
|
|
|
|
|
|
|
GuiCompare::GuiCompare(GuiView & lv)
|
|
|
|
: GuiDialog(lv, "compare", qt_("Compare LyX files")),
|
2009-10-25 13:41:46 +00:00
|
|
|
compare_(0), dest_buffer_(0), old_buffer_(0), new_buffer_(0)
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
setupUi(this);
|
|
|
|
setModal(Qt::WindowModal);
|
|
|
|
|
2018-07-08 16:35:38 +02:00
|
|
|
connect(buttonBox, SIGNAL(clicked(QAbstractButton *)),
|
|
|
|
this, SLOT(slotButtonBox(QAbstractButton *)));
|
2009-10-24 16:15:28 +00:00
|
|
|
|
2009-10-24 17:04:09 +00:00
|
|
|
connect(newFilePB, SIGNAL(clicked()), this, SLOT(selectNewFile()));
|
|
|
|
connect(oldFilePB, SIGNAL(clicked()), this, SLOT(selectOldFile()));
|
2009-10-24 16:15:28 +00:00
|
|
|
|
|
|
|
connect(newFileCB, SIGNAL(currentIndexChanged(int)),
|
2009-10-24 17:04:09 +00:00
|
|
|
this, SLOT(changeAdaptor()));
|
2009-10-24 16:15:28 +00:00
|
|
|
connect(newFileCB, SIGNAL(editTextChanged(const QString &)),
|
2009-10-24 17:04:09 +00:00
|
|
|
this, SLOT(changeAdaptor()));
|
2009-10-24 16:15:28 +00:00
|
|
|
connect(oldFileCB, SIGNAL(currentIndexChanged(int)),
|
2009-10-24 17:04:09 +00:00
|
|
|
this, SLOT(changeAdaptor()));
|
2009-10-24 16:15:28 +00:00
|
|
|
connect(oldFileCB, SIGNAL(editTextChanged(const QString &)),
|
2009-10-24 17:04:09 +00:00
|
|
|
this, SLOT(changeAdaptor()));
|
2009-10-24 16:15:28 +00:00
|
|
|
|
|
|
|
newSettingsRB->setChecked(true);
|
2010-09-08 15:09:10 +00:00
|
|
|
trackingCB->setChecked(true);
|
2009-10-24 16:15:28 +00:00
|
|
|
|
2018-07-08 16:35:38 +02:00
|
|
|
buttonBox->button(QDialogButtonBox::Ok)->setCursor(Qt::ArrowCursor);
|
2010-01-14 01:20:14 +00:00
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
bc().setPolicy(ButtonPolicy::OkApplyCancelPolicy);
|
2018-07-08 16:35:38 +02:00
|
|
|
bc().setOK(buttonBox->button(QDialogButtonBox::Ok));
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
GuiCompare::~GuiCompare()
|
|
|
|
{
|
2009-10-25 13:41:46 +00:00
|
|
|
if (compare_)
|
|
|
|
delete compare_;
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void GuiCompare::closeEvent(QCloseEvent *)
|
|
|
|
{
|
2017-07-03 13:53:14 -04:00
|
|
|
slotCancel();
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 17:04:09 +00:00
|
|
|
void GuiCompare::changeAdaptor()
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool GuiCompare::isValid()
|
|
|
|
{
|
|
|
|
bool const valid = !newFileCB->currentText().isEmpty()
|
|
|
|
&& !oldFileCB->currentText().isEmpty();
|
|
|
|
return valid;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GuiCompare::updateContents()
|
|
|
|
{
|
2009-10-25 13:41:46 +00:00
|
|
|
if (compare_ && compare_->isRunning())
|
|
|
|
return;
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
QString restore_filename1 = newFileCB->currentText();
|
|
|
|
QString restore_filename2 = oldFileCB->currentText();
|
|
|
|
newFileCB->clear();
|
|
|
|
oldFileCB->clear();
|
|
|
|
progressBar->setValue(0);
|
2010-01-14 00:41:47 +00:00
|
|
|
statusBar->clearMessage();
|
2009-10-24 16:15:28 +00:00
|
|
|
BufferList::iterator it = theBufferList().begin();
|
|
|
|
BufferList::iterator const end = theBufferList().end();
|
|
|
|
for (; it != end; ++it) {
|
|
|
|
QString filename = toqstr((*it)->absFileName());
|
|
|
|
newFileCB->addItem(filename);
|
|
|
|
oldFileCB->addItem(filename);
|
|
|
|
}
|
2010-09-07 11:29:02 +00:00
|
|
|
if (!restore_filename1.isEmpty())
|
2009-10-24 16:15:28 +00:00
|
|
|
newFileCB->setEditText(restore_filename1);
|
2010-09-07 11:29:02 +00:00
|
|
|
else if (lyxview().documentBufferView())
|
|
|
|
newFileCB->setEditText(toqstr(buffer().absFileName()));
|
2009-10-24 16:15:28 +00:00
|
|
|
|
|
|
|
if (!restore_filename2.isEmpty())
|
|
|
|
oldFileCB->setEditText(restore_filename2);
|
|
|
|
else
|
|
|
|
oldFileCB->clearEditText();
|
|
|
|
|
|
|
|
if (isValid()) {
|
|
|
|
bc().setValid(isValid());
|
|
|
|
bc().apply();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 17:04:09 +00:00
|
|
|
void GuiCompare::selectNewFile()
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
QString name = browse(newFileCB->currentText());
|
|
|
|
if (!name.isEmpty())
|
|
|
|
newFileCB->setEditText(name);
|
|
|
|
changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 17:04:09 +00:00
|
|
|
void GuiCompare::selectOldFile()
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
QString name = browse(oldFileCB->currentText());
|
|
|
|
if (!name.isEmpty())
|
|
|
|
oldFileCB->setEditText(name);
|
|
|
|
changed();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
QString GuiCompare::browse(QString const & in_name) const
|
|
|
|
{
|
|
|
|
QString const title = qt_("Select document");
|
|
|
|
|
|
|
|
QStringList const & filters = fileFilters(qt_("LyX Documents (*.lyx)"));
|
2017-07-03 13:53:14 -04:00
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
QString filename;
|
|
|
|
if (lyxview().documentBufferView()) {
|
2010-04-21 01:19:30 +00:00
|
|
|
QString path = bufferFilePath();
|
2011-06-01 13:18:08 +00:00
|
|
|
filename = browseRelToParent(in_name, path, title, filters, false,
|
2017-12-20 11:36:32 +01:00
|
|
|
qt_("D&ocuments"), toqstr(lyxrc.document_path));
|
2009-10-24 16:15:28 +00:00
|
|
|
} else {
|
|
|
|
QString path = toqstr(lyxrc.document_path);
|
2011-06-01 13:18:08 +00:00
|
|
|
QString rel_filename = browseRelToParent(in_name, path, title, filters, false,
|
2017-12-20 11:36:32 +01:00
|
|
|
qt_("D&ocuments"), toqstr(lyxrc.document_path));
|
2009-10-24 16:15:28 +00:00
|
|
|
filename = makeAbsPath(rel_filename, path);
|
|
|
|
}
|
2017-07-03 13:53:14 -04:00
|
|
|
return filename;
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-01-14 01:20:14 +00:00
|
|
|
void GuiCompare::enableControls(bool enable)
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
2010-01-14 01:20:14 +00:00
|
|
|
// Set the hourglass cursor for the dialog, but
|
|
|
|
// never for the cancel button.
|
|
|
|
setCursor(enable ? Qt::ArrowCursor : Qt::WaitCursor);
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
newFileLA->setEnabled(enable);
|
|
|
|
newFilePB->setEnabled(enable);
|
|
|
|
newFileCB->setEnabled(enable);
|
|
|
|
oldFileLA->setEnabled(enable);
|
|
|
|
oldFilePB->setEnabled(enable);
|
|
|
|
oldFileCB->setEnabled(enable);
|
2018-07-08 16:35:38 +02:00
|
|
|
buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable);
|
2009-10-24 16:15:28 +00:00
|
|
|
groupBox->setEnabled(enable);
|
|
|
|
progressBar->setEnabled(!enable);
|
|
|
|
|
|
|
|
if (enable)
|
2018-07-08 16:35:38 +02:00
|
|
|
buttonBox->button(QDialogButtonBox::Cancel)->setText(qt_("Close"));
|
2009-10-24 16:15:28 +00:00
|
|
|
else
|
2018-07-08 16:35:38 +02:00
|
|
|
buttonBox->button(QDialogButtonBox::Cancel)->setText(qt_("Cancel"));
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
void GuiCompare::error()
|
|
|
|
{
|
|
|
|
Alert::error(_("Error"), _("Error while comparing documents."));
|
|
|
|
finished(true);
|
|
|
|
}
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
void GuiCompare::finished(bool aborted)
|
|
|
|
{
|
|
|
|
enableControls(true);
|
2009-10-25 13:56:44 +00:00
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
if (compare_) {
|
|
|
|
delete compare_;
|
|
|
|
compare_ = 0;
|
|
|
|
}
|
2017-07-03 13:53:14 -04:00
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
if (aborted) {
|
2009-10-25 13:41:46 +00:00
|
|
|
if (dest_buffer_) {
|
|
|
|
dest_buffer_->markClean();
|
|
|
|
theBufferList().release(dest_buffer_);
|
|
|
|
}
|
2009-10-24 16:15:28 +00:00
|
|
|
progressBar->setValue(0);
|
2010-01-14 00:56:13 +00:00
|
|
|
statusBar->showMessage(qt_("Aborted"), 5000);
|
2009-10-24 16:15:28 +00:00
|
|
|
} else {
|
|
|
|
hideView();
|
|
|
|
bc().ok();
|
2009-10-25 13:41:46 +00:00
|
|
|
if (dest_buffer_) {
|
|
|
|
dispatch(FuncRequest(LFUN_BUFFER_SWITCH,
|
|
|
|
dest_buffer_->absFileName()));
|
2010-09-08 15:09:10 +00:00
|
|
|
if (trackingCB->isChecked()) {
|
|
|
|
dispatch(FuncRequest(LFUN_CHANGES_OUTPUT));
|
|
|
|
dispatch(FuncRequest(LFUN_CHANGES_TRACK));
|
|
|
|
}
|
2009-10-25 13:41:46 +00:00
|
|
|
}
|
2010-01-14 00:56:13 +00:00
|
|
|
statusBar->showMessage(qt_("Finished"), 5000);
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
void GuiCompare::progress(int val)
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
progressBar->setValue(progressBar->value() + val);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 17:04:09 +00:00
|
|
|
void GuiCompare::progressMax(int max) const
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
progressBar->setMaximum(max);
|
|
|
|
}
|
|
|
|
|
2010-01-14 00:41:47 +00:00
|
|
|
|
2020-10-31 15:09:46 +02:00
|
|
|
void GuiCompare::setStatusMessage(QString const & msg)
|
2010-01-14 00:41:47 +00:00
|
|
|
{
|
|
|
|
statusBar->showMessage(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
void GuiCompare::slotOK()
|
|
|
|
{
|
|
|
|
enableControls(false);
|
2009-10-25 13:41:46 +00:00
|
|
|
if (!run())
|
|
|
|
error();
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void GuiCompare::slotCancel()
|
|
|
|
{
|
2009-10-25 13:41:46 +00:00
|
|
|
if (compare_ && compare_->isRunning()) {
|
2010-01-14 00:41:47 +00:00
|
|
|
statusBar->showMessage(qt_("Aborting process..."));
|
2009-10-25 13:41:46 +00:00
|
|
|
compare_->abort();
|
|
|
|
} else {
|
|
|
|
GuiDialog::slotClose();
|
|
|
|
progressBar->setValue(0);
|
2010-01-14 00:41:47 +00:00
|
|
|
statusBar->clearMessage();
|
2009-10-25 13:41:46 +00:00
|
|
|
}
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-07-08 16:35:38 +02:00
|
|
|
void GuiCompare::slotButtonBox(QAbstractButton * button)
|
|
|
|
{
|
|
|
|
switch (buttonBox->standardButton(button)) {
|
|
|
|
case QDialogButtonBox::Ok:
|
|
|
|
slotOK();
|
|
|
|
break;
|
|
|
|
case QDialogButtonBox::Cancel:
|
|
|
|
slotCancel();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
Buffer const * GuiCompare::bufferFromFileName(string const & file) const
|
|
|
|
{
|
|
|
|
FileName fname;
|
|
|
|
if (FileName::isAbsolute(file))
|
|
|
|
fname.set(file);
|
|
|
|
else if (lyxview().documentBufferView())
|
2010-04-21 01:19:30 +00:00
|
|
|
fname = support::makeAbsPath(file, fromqstr(bufferFilePath()));
|
2009-10-24 16:15:28 +00:00
|
|
|
|
|
|
|
if (fname.empty()
|
|
|
|
|| (!fname.exists() && !theBufferList().getBuffer(fname))) {
|
|
|
|
LYXERR0( "Unable to read: " << file);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
return loadIfNeeded(fname);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-11-13 20:58:20 +13:00
|
|
|
int GuiCompare::run(bool blocking_mode)
|
2009-10-24 16:15:28 +00:00
|
|
|
{
|
|
|
|
progressBar->setValue(0);
|
|
|
|
|
|
|
|
new_buffer_ = bufferFromFileName(fromqstr(newFileCB->currentText()));
|
|
|
|
old_buffer_ = bufferFromFileName(fromqstr(oldFileCB->currentText()));
|
|
|
|
|
|
|
|
// new buffer that will carry the output
|
|
|
|
FileName initpath(lyxrc.document_path);
|
|
|
|
dest_buffer_ = newUnnamedFile(initpath, to_utf8(_("differences")));
|
2009-10-25 13:41:46 +00:00
|
|
|
|
|
|
|
if (!new_buffer_ || !old_buffer_ || !dest_buffer_)
|
|
|
|
return 0;
|
|
|
|
|
2010-01-08 02:03:54 +00:00
|
|
|
dest_buffer_->changed(true);
|
2021-04-03 13:19:53 +02:00
|
|
|
if (blocking_mode)
|
|
|
|
//blocking mode is infinitive and we don't want diff autosave
|
|
|
|
//if user decides to kill ther running lyx instance
|
|
|
|
dest_buffer_->markClean();
|
|
|
|
else
|
|
|
|
dest_buffer_->markDirty();
|
|
|
|
|
2009-10-24 16:15:28 +00:00
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
// get the options from the dialog
|
|
|
|
CompareOptions options;
|
|
|
|
options.settings_from_new = newSettingsRB->isChecked();
|
2022-11-27 13:16:00 -05:00
|
|
|
options.author = authorCO->currentIndex();
|
2009-10-25 13:41:46 +00:00
|
|
|
|
|
|
|
// init the compare object and start it
|
2020-11-13 20:58:20 +13:00
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
compare_ = new Compare(new_buffer_, old_buffer_, dest_buffer_, options);
|
2020-11-13 20:58:20 +13:00
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
connect(compare_, SIGNAL(error()), this, SLOT(error()));
|
2020-11-13 20:58:20 +13:00
|
|
|
// Only connect the finished() method to the signal if we're *not* in blocking_mode
|
|
|
|
// (i.e. we want to make it possible for caller to block for the results)
|
|
|
|
if (! blocking_mode) {
|
|
|
|
connect(compare_, SIGNAL(finished(bool)), this, SLOT(finished(bool)));
|
|
|
|
}
|
2009-10-25 13:41:46 +00:00
|
|
|
connect(compare_, SIGNAL(progress(int)), this, SLOT(progress(int)));
|
|
|
|
connect(compare_, SIGNAL(progressMax(int)), this, SLOT(progressMax(int)));
|
2010-01-14 00:41:47 +00:00
|
|
|
connect(compare_, SIGNAL(statusMessage(QString)),
|
|
|
|
this, SLOT(setStatusMessage(QString)));
|
2009-10-25 13:41:46 +00:00
|
|
|
compare_->start(QThread::LowPriority);
|
2020-11-13 20:58:20 +13:00
|
|
|
|
2009-10-25 13:41:46 +00:00
|
|
|
return 1;
|
2009-10-24 16:15:28 +00:00
|
|
|
}
|
|
|
|
|
2010-09-07 11:29:02 +00:00
|
|
|
bool GuiCompare::initialiseParams(std::string const &par)
|
|
|
|
{
|
|
|
|
//just for the sake of parsing arguments
|
|
|
|
FuncRequest cmd(LFUN_UNKNOWN_ACTION, par);
|
2020-11-13 20:58:20 +13:00
|
|
|
if (cmd.getArg(0) == "run" || cmd.getArg(0) == "run-blocking") {
|
2010-09-07 11:29:02 +00:00
|
|
|
oldFileCB->setEditText(toqstr(cmd.getArg(1)));
|
|
|
|
newFileCB->setEditText(toqstr(cmd.getArg(2)));
|
2020-11-13 20:58:20 +13:00
|
|
|
|
|
|
|
if (cmd.getArg(0) == "run" ) {
|
|
|
|
// Run asynchronously
|
|
|
|
slotOK();
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
// Run synchronously
|
|
|
|
enableControls(false);
|
|
|
|
|
|
|
|
if (! run(true)) {
|
|
|
|
error();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2021-04-03 13:19:53 +02:00
|
|
|
// Wait for the Compare function to process in a thread
|
|
|
|
compare_->wait();
|
2020-11-13 20:58:20 +13:00
|
|
|
|
|
|
|
finished(false);
|
2021-04-03 13:19:53 +02:00
|
|
|
//Hiding dialog does not work as intended through finished routine, because showView
|
|
|
|
//is triggered after initialiseParams returns true. So we return false, warning will
|
|
|
|
//show on the terminal though.
|
|
|
|
return false;
|
2020-11-13 20:58:20 +13:00
|
|
|
}
|
2010-09-07 11:29:02 +00:00
|
|
|
}
|
2010-09-07 19:26:01 +00:00
|
|
|
|
|
|
|
progressBar->setValue(0);
|
|
|
|
progressBar->setEnabled(false);
|
2010-09-08 15:12:55 +00:00
|
|
|
progressBar->setMaximum(1);
|
2010-09-07 19:26:01 +00:00
|
|
|
|
2022-11-27 13:16:00 -05:00
|
|
|
// If empty fill the author combobox with the current and the comparison
|
|
|
|
// author and their respective colors
|
|
|
|
if (authorCO->count() == 0) {
|
|
|
|
authorCO->clear();
|
|
|
|
QPixmap colorIcon(32, 32);
|
|
|
|
colorIcon.fill(guiApp->colorCache().get(
|
|
|
|
Color(Color_changedtext_workarea_author1)));
|
|
|
|
authorCO->addItem(colorIcon, qt_("Current Author"));
|
|
|
|
colorIcon.fill(guiApp->colorCache().get(
|
|
|
|
Color(Color_changedtext_workarea_comparison)));
|
|
|
|
authorCO->addItem(colorIcon, qt_("Document Comparison"));
|
|
|
|
}
|
|
|
|
|
2010-09-07 11:29:02 +00:00
|
|
|
return true;
|
|
|
|
}
|
2009-10-24 16:15:28 +00:00
|
|
|
|
|
|
|
|
|
|
|
} // namespace frontend
|
|
|
|
} // namespace lyx
|
|
|
|
|
|
|
|
|
|
|
|
#include "moc_GuiCompare.cpp"
|