Akonadi

conflictresolvedialog.cpp
1 /*
2  Copyright (c) 2010 KDAB
3  Author: Tobias Koenig <[email protected]>
4 
5  This library is free software; you can redistribute it and/or modify it
6  under the terms of the GNU Library General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or (at your
8  option) any later version.
9 
10  This library is distributed in the hope that it will be useful, but WITHOUT
11  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public
13  License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to the
17  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
18  02110-1301, USA.
19 */
20 
21 #include "conflictresolvedialog_p.h"
22 
23 #include "abstractdifferencesreporter.h"
24 #include "differencesalgorithminterface.h"
25 #include "typepluginloader_p.h"
26 
27 #include <shared/akranges.h>
28 
29 #include <QVBoxLayout>
30 #include <QLabel>
31 #include <QPushButton>
32 #include <QTemporaryFile>
33 #include <QDir>
34 #include <QDesktopServices>
35 #include <QWindow>
36 #include <QScreen>
37 
38 #include <KColorScheme>
39 #include <KLocalizedString>
40 #include <QTextBrowser>
41 #include <QDialogButtonBox>
42 #include <KWindowConfig>
43 
44 using namespace Akonadi;
45 using namespace AkRanges;
46 
47 static inline QString textToHTML(const QString &text)
48 {
49  return Qt::convertFromPlainText(text);
50 }
51 
52 class HtmlDifferencesReporter : public AbstractDifferencesReporter
53 {
54 public:
55  HtmlDifferencesReporter() = default;
56 
57  QString toHtml() const
58  {
59  return header() + mContent + footer();
60  }
61 
62  QString plainText() const
63  {
64  return mTextContent;
65  }
66 
67  void setPropertyNameTitle(const QString &title) override {
68  mNameTitle = title;
69  }
70 
71  void setLeftPropertyValueTitle(const QString &title) override {
72  mLeftTitle = title;
73  }
74 
75  void setRightPropertyValueTitle(const QString &title) override {
76  mRightTitle = title;
77  }
78 
79  void addProperty(Mode mode, const QString &name, const QString &leftValue, const QString &rightValue) override {
80  switch (mode)
81  {
82  case NormalMode:
83  mContent.append(QStringLiteral("<tr><td align=\"right\"><b>%1:</b></td><td>%2</td><td></td><td>%3</td></tr>")
84  .arg(name,
85  textToHTML(leftValue),
86  textToHTML(rightValue)));
87  mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue));
88  break;
89  case ConflictMode:
90  mContent.append(QStringLiteral("<tr><td align=\"right\"><b>%1:</b></td><td bgcolor=\"#ff8686\">%2</td><td></td><td bgcolor=\"#ff8686\">%3</td></tr>")
91  .arg(name,
92  textToHTML(leftValue),
93  textToHTML(rightValue)));
94  mTextContent.append(QStringLiteral("%1:\n%2\n%3\n\n").arg(name, leftValue, rightValue));
95  break;
96  case AdditionalLeftMode:
97  mContent.append(QStringLiteral("<tr><td align=\"right\"><b>%1:</b></td><td bgcolor=\"#9cff83\">%2</td><td></td><td></td></tr>")
98  .arg(name,
99  textToHTML(leftValue)));
100  mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, leftValue));
101  break;
102  case AdditionalRightMode:
103  mContent.append(QStringLiteral("<tr><td align=\"right\"><b>%1:</b></td><td></td><td></td><td bgcolor=\"#9cff83\">%2</td></tr>")
104  .arg(name,
105  textToHTML(rightValue)));
106  mTextContent.append(QStringLiteral("%1:\n%2\n\n").arg(name, rightValue));
107  break;
108  }
109  }
110 
111 private:
112  QString header() const
113  {
114  QString header = QStringLiteral("<html>");
115  header += QStringLiteral("<body text=\"%1\" bgcolor=\"%2\">")
116  .arg(KColorScheme(QPalette::Active, KColorScheme::View).foreground().color().name(),
117  KColorScheme(QPalette::Active, KColorScheme::View).background().color().name());
118  header += QLatin1String("<center><table>");
119  header += QStringLiteral("<tr><th align=\"center\">%1</th><th align=\"center\">%2</th><td>&nbsp;</td><th align=\"center\">%3</th></tr>")
120  .arg(mNameTitle, mLeftTitle, mRightTitle);
121 
122  return header;
123  }
124 
125  QString footer() const
126  {
127  return QStringLiteral("</table></center>"
128  "</body>"
129  "</html>");
130  }
131 
132  QString mContent;
133  QString mNameTitle;
134  QString mLeftTitle;
135  QString mRightTitle;
136  QString mTextContent;
137 };
138 
139 static void compareItems(AbstractDifferencesReporter *reporter, const Akonadi::Item &localItem, const Akonadi::Item &otherItem)
140 {
141  if (localItem.modificationTime() != otherItem.modificationTime()) {
142  reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Modification Time"),
143  QLocale().toString(localItem.modificationTime(), QLocale::ShortFormat),
144  QLocale().toString(otherItem.modificationTime(), QLocale::ShortFormat));
145  }
146 
147  if (localItem.flags() != otherItem.flags()) {
148  const auto toQString = [](const QByteArray &s) { return QString::fromUtf8(s); };
149  const auto localFlags = localItem.flags() | Views::transform(toQString) | Actions::toQList;
150  const auto otherFlags = otherItem.flags() | Views::transform(toQString) | Actions::toQList;
152  localFlags.join(QLatin1String(", ")),
153  otherFlags.join(QLatin1String(", ")));
154  }
155 
156  const auto toPair = [](Attribute *attr) { return std::pair{attr->type(), attr->serialized()}; };
157  const auto localAttributes = localItem.attributes() | Views::transform(toPair) | Actions::toQHash;
158  const auto otherAttributes = otherItem.attributes() | Views::transform(toPair) | Actions::toQHash;
159 
160  if (localAttributes != otherAttributes) {
161  for (const QByteArray &localKey : localAttributes) {
162  if (!otherAttributes.contains(localKey)) {
164  QString::fromUtf8(localAttributes.value(localKey)),
165  QString());
166  } else {
167  const QByteArray localValue = localAttributes.value(localKey);
168  const QByteArray otherValue = otherAttributes.value(localKey);
169  if (localValue != otherValue) {
170  reporter->addProperty(AbstractDifferencesReporter::ConflictMode, i18n("Attribute: %1", QString::fromUtf8(localKey)),
171  QString::fromUtf8(localValue),
172  QString::fromUtf8(otherValue));
173  }
174  }
175  }
176 
177  for (const QByteArray &otherKey : otherAttributes) {
178  if (!localAttributes.contains(otherKey)) {
180  QString(),
181  QString::fromUtf8(otherAttributes.value(otherKey)));
182  }
183  }
184  }
185 }
186 
187 ConflictResolveDialog::ConflictResolveDialog(QWidget *parent)
188  : QDialog(parent), mResolveStrategy(ConflictHandler::UseBothItems)
189 {
190  setWindowTitle(i18nc("@title:window", "Conflict Resolution"));
191 
192  QVBoxLayout *mainLayout = new QVBoxLayout(this);
193  // Don't use QDialogButtonBox, order is very important (left on the left, right on the right)
194  QHBoxLayout *buttonLayout = new QHBoxLayout();
195  QPushButton *takeLeftButton = new QPushButton(this);
196  takeLeftButton->setText(i18nc("@action:button", "Take my version"));
197  connect(takeLeftButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseLocalItemChoosen);
198  buttonLayout->addWidget(takeLeftButton);
199  takeLeftButton->setObjectName(QStringLiteral("takeLeftButton"));
200 
201  QPushButton *takeRightButton = new QPushButton(this);
202  takeRightButton->setText(i18nc("@action:button", "Take their version"));
203  takeRightButton->setObjectName(QStringLiteral("takeRightButton"));
204  connect(takeRightButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseOtherItemChoosen);
205  buttonLayout->addWidget(takeRightButton);
206 
207  QPushButton *keepBothButton = new QPushButton(this);
208  keepBothButton->setText(i18nc("@action:button", "Keep both versions"));
209  keepBothButton->setObjectName(QStringLiteral("keepBothButton"));
210  buttonLayout->addWidget(keepBothButton);
211  connect(keepBothButton, &QPushButton::clicked, this, &ConflictResolveDialog::slotUseBothItemsChoosen);
212 
213  keepBothButton->setDefault(true);
214 
215 
216  mView = new QTextBrowser(this);
217  mView->setObjectName(QStringLiteral("view"));
218  mView->setOpenLinks(false);
219 
220  QLabel *docuLabel = new QLabel(i18n("<qt>Your changes conflict with those made by someone else meanwhile.<br>"
221  "Unless one version can just be thrown away, you will have to integrate those changes manually.<br>"
222  "Click on <a href=\"opentexteditor\">\"Open text editor\"</a> to keep a copy of the texts, then select which version is most correct, then re-open it and modify it again to add what's missing."));
223  connect(docuLabel, &QLabel::linkActivated, this, &ConflictResolveDialog::slotOpenEditor);
224  docuLabel->setContextMenuPolicy(Qt::NoContextMenu);
225 
226  docuLabel->setWordWrap(true);
227  docuLabel->setObjectName(QStringLiteral("doculabel"));
228 
229  mainLayout->addWidget(mView);
230  mainLayout->addWidget(docuLabel);
231  mainLayout->addLayout(buttonLayout);
232 
233  // default size is tiny, and there's usually lots of text, so make it much bigger
234  create(); // ensure a window is created
235  const QSize availableSize = windowHandle()->screen()->availableSize();
236  windowHandle()->resize(availableSize.width() * 0.7, availableSize.height() * 0.5);
237  KWindowConfig::restoreWindowSize(windowHandle(), KSharedConfig::openConfig()->group("ConflictResolveDialog"));
238  resize(windowHandle()->size()); // workaround for QTBUG-40584
239 }
240 
241 ConflictResolveDialog::~ConflictResolveDialog()
242 {
243  KConfigGroup group(KSharedConfig::openConfig()->group("ConflictResolveDialog"));
245 }
246 
247 void ConflictResolveDialog::setConflictingItems(const Akonadi::Item &localItem, const Akonadi::Item &otherItem)
248 {
249  mLocalItem = localItem;
250  mOtherItem = otherItem;
251 
252  HtmlDifferencesReporter reporter;
253  compareItems(&reporter, localItem, otherItem);
254 
255  if (mLocalItem.hasPayload() && mOtherItem.hasPayload()) {
256 
257  QObject *object = TypePluginLoader::objectForMimeTypeAndClass(localItem.mimeType(), localItem.availablePayloadMetaTypeIds());
258  if (object) {
260  if (algorithm) {
261  algorithm->compare(&reporter, localItem, otherItem);
262  mView->setHtml(reporter.toHtml());
263  mTextContent = reporter.plainText();
264  return;
265  }
266  }
267 
268  reporter.addProperty(HtmlDifferencesReporter::NormalMode, i18n("Data"),
269  QString::fromUtf8(mLocalItem.payloadData()),
270  QString::fromUtf8(mOtherItem.payloadData()));
271  }
272 
273  mView->setHtml(reporter.toHtml());
274  mTextContent = reporter.plainText();
275 }
276 
277 void ConflictResolveDialog::slotOpenEditor()
278 {
279  QTemporaryFile file(QDir::tempPath() + QStringLiteral("/akonadi-XXXXXX.txt"));
280  if (file.open()) {
281  file.setAutoRemove(false);
282  file.write(mTextContent.toLocal8Bit());
283  const QString fileName = file.fileName();
284  file.close();
286  }
287 }
288 
289 ConflictHandler::ResolveStrategy ConflictResolveDialog::resolveStrategy() const
290 {
291  return mResolveStrategy;
292 }
293 
294 void ConflictResolveDialog::slotUseLocalItemChoosen()
295 {
296  mResolveStrategy = ConflictHandler::UseLocalItem;
297  accept();
298 }
299 
300 void ConflictResolveDialog::slotUseOtherItemChoosen()
301 {
302  mResolveStrategy = ConflictHandler::UseOtherItem;
303  accept();
304 }
305 
306 void ConflictResolveDialog::slotUseBothItemsChoosen()
307 {
308  mResolveStrategy = ConflictHandler::UseBothItems;
309  accept();
310 }
311 
312 #include "moc_conflictresolvedialog_p.cpp"
The left column contains a property value that is not available in the right column.
void resize(int w, int h)
QString & append(QChar ch)
void create(WId window, bool initializeWindow, bool destroyOldWindow)
int width() const const
QString toString(qlonglong i) const const
Provides interface for custom attributes for Entity.
Definition: attribute.h:139
An interface to find out differences between two Akonadi objects.
QSize size() const const
void addWidget(QWidget *widget, int stretch, Qt::Alignment alignment)
QString fromUtf8(const char *str, int size)
QString tempPath()
QString convertFromPlainText(const QString &plain, Qt::WhiteSpaceMode mode)
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void setObjectName(const QString &name)
void clicked(bool checked)
void linkActivated(const QString &link)
The left and right column show conflicting property values.
void setContextMenuPolicy(Qt::ContextMenuPolicy policy)
static KSharedConfig::Ptr openConfig(const QString &fileName=QString(), OpenFlags mode=FullConfig, QStandardPaths::StandardLocation type=QStandardPaths::GenericConfigLocation)
QWindow * windowHandle() const const
An interface to report differences between two arbitrary objects.
QString i18n(const char *text, const TYPE &arg...)
virtual void addProperty(Mode mode, const QString &name, const QString &leftValue, const QString &rightValue)=0
Adds a new property entry to the table.
char * toString(const T &value)
Helper integration between Akonadi and Qt.
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
void setWindowTitle(const QString &)
void resize(const QSize &newSize)
int height() const const
The right column contains a property value that is not available in the left column.
KCONFIGGUI_EXPORT void restoreWindowSize(QWindow *window, const KConfigGroup &config)
void setText(const QString &text)
virtual void compare(AbstractDifferencesReporter *reporter, const Akonadi::Item &leftItem, const Akonadi::Item &rightItem)=0
Calculates the differences between two Akonadi objects and reports them to a reporter object...
bool openUrl(const QUrl &url)
QScreen * screen() const const
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
void setDefault(bool)
KCONFIGGUI_EXPORT void saveWindowSize(const QWindow *window, KConfigGroup &config, KConfigGroup::WriteConfigFlags options=KConfigGroup::Normal)
void setWordWrap(bool on)
QUrl fromLocalFile(const QString &localFile)
void addLayout(QLayout *layout, int stretch)
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Fri Jun 5 2020 23:08:54 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.