2 This file is part of Blogilo, A KDE Blogging Client
4 Copyright (C) 2008-2010 Mehrdad Momeny <mehrdad.momeny@gmail.com>
5 Copyright (C) 2008-2010 Golnaz Nilieh <g382nilieh@gmail.com>
6 Copyright (C) 2013-2016 Laurent Montel <montel@kde.org>
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of
11 the License or (at your option) version 3 or any later version
12 accepted by the membership of KDE e.V. (or its successor approved
13 by the membership of KDE e.V.), which shall act as a proxy
14 defined in Section 14 of version 3 of the license.
16 This program is distributed in the hope that it will be useful,
17 but WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 GNU General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, see http://www.gnu.org/licenses/
25 #include "postentry.h"
26 #include "bilbomedia.h"
30 #include "sendtoblogdialog.h"
32 #include "bilboblog.h"
33 #include "syncuploader.h"
35 #include "composer/webengine/blogilocomposerwebengineeditor.h"
36 #include "composer/webengine/blogilocomposerwebengineview.h"
37 #include "composer/webengine/blogilocomposerwebenginewidget.h"
38 #include "composer/bilbobrowser.h"
39 #include "composer/htmleditor.h"
41 #include <PimCommon/SpellCheckLineEdit>
43 #include "blogilo_debug.h"
44 #include <klocalizedstring.h>
45 #include <klineedit.h>
46 #include <KMessageBox>
47 #include <ktexteditor/view.h>
48 #include <ktexteditor/document.h>
52 #include <QProgressBar>
56 #include <QHBoxLayout>
60 class PostEntry::Private
63 QPointer
<QProgressBar
> progress
;
64 QGridLayout
*gridLayout
;
65 QHBoxLayout
*horizontalLayout
;
67 PimCommon::SpellCheckLineEdit
*txtTitle
;
69 BilboPost mCurrentPost
;
70 int mCurrentPostBlogId
;
71 QMap
<QString
, BilboMedia
*> mMediaList
;
73 int mNumOfFilesToBeUploaded
;
74 bool isUploadingMediaFilesFailed
;
76 bool isPostContentModified
;
78 QTabWidget
*tabWidget
;
82 BlogiloComposerWebEngineWidget
*wysiwygEditor
;
83 KTextEditor::View
*htmlEditor
;
84 BilboBrowser
*previewer
;
89 PostEntry::PostEntry(QWidget
*parent
)
90 : QFrame(parent
), d(new Private
)
93 //FIXME connect(d->wysiwygEditor->editor(), &ComposerEditorNG::ComposerEditor::textChanged, this, &PostEntry::textChanged);
94 connect(d
->htmlEditor
->document(), &KTextEditor::Document::textChanged
,
95 this, &PostEntry::textChanged
);
96 layout()->addWidget(d
->tabWidget
);
97 d
->mTimer
= new QTimer(this);
98 d
->mTimer
->start(Settings::autosaveInterval() * MINUTE
);
99 connect(d
->mTimer
, &QTimer::timeout
, this, &PostEntry::saveTemporary
);
100 d
->progress
= Q_NULLPTR
;
101 d
->mCurrentPostBlogId
= -1;
102 d
->mNumOfFilesToBeUploaded
= 0;
103 d
->isPostContentModified
= false;
104 connect(this, &PostEntry::textChanged
, this, &PostEntry::slotPostModified
);
107 PostEntry::~PostEntry()
109 disconnect(d
->txtTitle
, &PimCommon::SpellCheckLineEdit::textChanged
, this, &PostEntry::slotTitleChanged
);
110 disconnect(d
->txtTitle
, &PimCommon::SpellCheckLineEdit::focusDown
, this, &PostEntry::slotFocusEditor
);
115 void PostEntry::settingsChanged()
118 d
->mTimer
->setInterval(Settings::autosaveInterval() * MINUTE
);
119 if (Settings::autosaveInterval()) {
126 void PostEntry::createUi()
128 d
->tabWidget
= new QTabWidget(this);
129 d
->tabVisual
= new QWidget(d
->tabWidget
);
130 d
->tabHtml
= new QWidget(d
->tabWidget
);
131 d
->tabPreview
= new QWidget(d
->tabWidget
);
132 d
->tabWidget
->addTab(d
->tabVisual
, i18nc("Software", "Visual Editor"));
133 d
->tabWidget
->addTab(d
->tabHtml
, i18nc("Software", "HTML Editor"));
134 d
->tabWidget
->addTab(d
->tabPreview
, i18nc("preview of the edited post", "Post Preview"));
135 connect(d
->tabWidget
, &QTabWidget::currentChanged
, this, &PostEntry::slotSyncEditors
);
140 BlogiloComposerWebEngineView
*view
= new BlogiloComposerWebEngineView(this);
141 d
->wysiwygEditor
= new BlogiloComposerWebEngineWidget(view
/*,d->tabVisual*/);
142 QVBoxLayout
*vLayout
= new QVBoxLayout(d
->tabVisual
);
143 vLayout
->addWidget(d
->wysiwygEditor
);
146 d
->htmlEditor
= HtmlEditor::self()->createView(d
->tabHtml
);
147 QGridLayout
*hLayout
= new QGridLayout(d
->tabHtml
);
148 hLayout
->addWidget(d
->htmlEditor
);
151 d
->previewer
= new BilboBrowser(d
->tabPreview
);
152 QGridLayout
*gLayout
= new QGridLayout(d
->tabPreview
);
153 gLayout
->addWidget(d
->previewer
);
155 connect(d
->previewer
, &BilboBrowser::sigSetBlogStyle
, this, &PostEntry::slotSetPostPreview
);
157 d
->tabWidget
->setCurrentIndex(0);
159 d
->gridLayout
= new QGridLayout(this);
161 d
->horizontalLayout
= new QHBoxLayout();
162 d
->horizontalLayout
->setSizeConstraint(QLayout::SetDefaultConstraint
);
164 d
->labelTitle
= new QLabel(this);
165 d
->labelTitle
->setText(i18nc("noun, the post title", "Title:"));
166 d
->horizontalLayout
->addWidget(d
->labelTitle
);
168 d
->txtTitle
= new PimCommon::SpellCheckLineEdit(this, QStringLiteral("blogilorc"));
169 d
->horizontalLayout
->addWidget(d
->txtTitle
);
170 d
->labelTitle
->setBuddy(d
->txtTitle
);
171 connect(d
->txtTitle
, &PimCommon::SpellCheckLineEdit::textChanged
, this, &PostEntry::slotTitleChanged
);
172 connect(d
->txtTitle
, &PimCommon::SpellCheckLineEdit::focusDown
, this, &PostEntry::slotFocusEditor
);
174 d
->gridLayout
->addLayout(d
->horizontalLayout
, 0, 0, 1, 1);
177 void PostEntry::slotFocusEditor()
179 switch (d
->tabWidget
->currentIndex()) {
181 d
->wysiwygEditor
->editor()->startEditing();
184 d
->htmlEditor
->setFocus();
189 void PostEntry::slotSyncEditors(int index
)
192 if (d
->prev_index
== 2) {
193 d
->previewer
->stop();
194 d
->prev_index
= index
;
196 }//An else clause can do the job of goto, No? -Mehrdad :D
197 d
->wysiwygEditor
->editor()->setHtmlContent(d
->htmlEditor
->document()->text());
198 d
->wysiwygEditor
->editor()->setFocus();
199 d
->wysiwygEditor
->editor()->startEditing();
200 } else if (index
== 1) {
201 if (d
->prev_index
== 2) {
202 d
->previewer
->stop();
203 d
->prev_index
= index
;
206 d
->htmlEditor
->document()->setText(d
->wysiwygEditor
->editor()->htmlContent());
207 d
->htmlEditor
->setFocus();
209 if (d
->prev_index
== 1) {
210 d
->wysiwygEditor
->editor()->setHtmlContent(d
->htmlEditor
->document()->text());
212 d
->htmlEditor
->document()->setText(d
->wysiwygEditor
->editor()->htmlContent());
214 d
->previewer
->setHtml(d
->txtTitle
->toPlainText(), d
->htmlEditor
->document()->text());
216 d
->prev_index
= index
;
219 void PostEntry::slotSetPostPreview()
221 if (d
->tabWidget
->currentIndex() == 2) {
222 d
->previewer
->setHtml(d
->txtTitle
->toPlainText(), d
->htmlEditor
->document()->text());
226 QString
PostEntry::htmlContent() const
228 if (d
->tabWidget
->currentIndex() == 1) {
229 d
->wysiwygEditor
->editor()->setHtmlContent(d
->htmlEditor
->document()->text());
232 d
->htmlEditor
->document()->setText(d
->wysiwygEditor
->editor()->htmlContent());
235 return d
->htmlEditor
->document()->text();
238 void PostEntry::setHtmlContent(const QString
&content
)
240 d
->wysiwygEditor
->editor()->setHtmlContent(content
);
241 d
->htmlEditor
->document()->setText(content
);
244 void PostEntry::slotTitleChanged()
246 const QString
titleText(d
->txtTitle
->toPlainText());
247 d
->mCurrentPost
.setTitle(titleText
);
248 Q_EMIT
postTitleChanged(titleText
);
251 QString
PostEntry::postTitle() const
253 return d
->mCurrentPost
.title();
256 void PostEntry::setPostTitle(const QString
&title
)
258 d
->txtTitle
->setPlainText(title
);
259 d
->mCurrentPost
.setTitle(title
);
262 void PostEntry::setPostBody(const QString
&content
, const QString
&additionalContent
)
265 if (additionalContent
.isEmpty()) {
268 body
= content
+ QLatin1String("<hr/><!--split-->") + additionalContent
;
269 d
->mCurrentPost
.setAdditionalContent(QString());
271 // if (body.isEmpty()){
272 // body = "<p></p>";//This is because of Bug #387578
274 d
->mCurrentPost
.setContent(body
);
275 setHtmlContent(body
);
276 d
->isPostContentModified
= false;
277 connect(this, &PostEntry::textChanged
, this, &PostEntry::slotPostModified
);
278 // connect( txtTitle, SIGNAL(textChanged(QString)), this, SLOT(slotPostModified()) );
281 int PostEntry::currentPostBlogId() const
283 return d
->mCurrentPostBlogId
;
286 void PostEntry::setCurrentPostBlogId(int blog_id
)
288 d
->mCurrentPostBlogId
= blog_id
;
289 if (blog_id
!= -1 && DBMan::self()->blogList().contains(blog_id
)) {
290 setDefaultLayoutDirection(DBMan::self()->blogList().value(blog_id
)->direction());
294 void PostEntry::setCurrentPostFromEditor()
296 if (d
->isPostContentModified
) {
298 const QString
&str
= htmlContent();
299 d
->mCurrentPost
.setContent(str
);
300 d
->isPostContentModified
= false;
301 connect(this, &PostEntry::textChanged
, this, &PostEntry::slotPostModified
);
305 BilboPost
*PostEntry::currentPost()
307 setCurrentPostFromEditor();
308 return &d
->mCurrentPost
;
311 void PostEntry::setCurrentPost(const BilboPost
&post
)
313 d
->mCurrentPost
= post
;
314 qCDebug(BLOGILO_LOG
) << "local_id: " << d
->mCurrentPost
.localId();
315 this->setPostBody(d
->mCurrentPost
.content(), d
->mCurrentPost
.additionalContent());
316 this->setPostTitle(d
->mCurrentPost
.title());
319 Qt::LayoutDirection
PostEntry::defaultLayoutDirection() const
321 return d
->txtTitle
->layoutDirection();
324 void PostEntry::setDefaultLayoutDirection(Qt::LayoutDirection direction
)
327 d
->tabWidget
->setLayoutDirection(direction
);
328 d
->txtTitle
->setLayoutDirection(direction
);
331 QList
< BilboMedia
* > PostEntry::localImages() const
333 return d
->wysiwygEditor
->editor()->getLocalImages();
336 void PostEntry::replaceImageSrc(const QString
&src
, const QString
&dest
)
338 d
->wysiwygEditor
->editor()->replaceImageSrc(src
, dest
);
341 bool PostEntry::uploadMediaFiles(Backend
*backend
)
343 bool localBackend
= false;
347 backend
= new Backend(d
->mCurrentPostBlogId
, this);
349 QList
<BilboMedia
*> lImages
= localImages();
350 if (!lImages
.isEmpty()) {
352 QList
<BilboMedia
*>::iterator it
= lImages
.begin();
353 QList
<BilboMedia
*>::iterator endIt
= lImages
.end();
354 for (; it
!= endIt
; ++it
) {
355 BilboMedia
*media
= (*it
);
356 SyncUploader
*uploader
= new SyncUploader(this);
357 if (uploader
->uploadMedia(backend
, media
)) {
358 replaceImageSrc(media
->localUrl().url(),
359 media
->remoteUrl().url());
361 const QString err
= i18n("Uploading the media file %1 failed.\n%2",
362 media
->name(), uploader
->errorMessage());
363 Q_EMIT
postPublishingDone(true, err
);
367 uploader
->deleteLater();
369 d
->mCurrentPost
.setContent(htmlContent());
372 backend
->deleteLater();
377 void PostEntry::slotError(const QString
&errMsg
)
379 const QString err
= i18n("An error occurred in the last transaction.\n%1", errMsg
);
380 Q_EMIT
postPublishingDone(true, err
);
382 sender()->deleteLater();
385 void PostEntry::submitPost(int blogId
, const BilboPost
&postData
)
387 setCurrentPostFromEditor();
388 if (d
->mCurrentPost
.content().isEmpty() || d
->mCurrentPost
.title().isEmpty()) {
389 if (KMessageBox::warningContinueCancel(this,
390 i18n("Your post title or body is empty.\nAre you sure you want to submit this post?")
391 ) == KMessageBox::Cancel
) {
396 if (d
->mCurrentPost
.status() == BilboPost::New
) {
399 QPointer
<SendToBlogDialog
> dia
= new SendToBlogDialog(isNew
, d
->mCurrentPost
.isPrivate(), this);
400 dia
->setAttribute(Qt::WA_DeleteOnClose
, false);
401 if (dia
->exec() == QDialog::Accepted
) {
402 this->setCursor(Qt::BusyCursor
);
403 d
->mCurrentPost
.setProperties(postData
);
404 d
->mCurrentPostBlogId
= blogId
;
407 if (dia
->isPrivate()) {
408 msgType
= i18nc("Post status, e.g Draft or Published Post", "draft");
409 d
->mCurrentPost
.setPrivate(true);
411 msgType
= i18nc("Post status, e.g Draft or Published Post", "post");
412 d
->mCurrentPost
.setPrivate(false);
417 statusMsg
= i18n("Submitting new %1...", msgType
);
420 statusMsg
= i18n("Modifying %1...", msgType
);
421 d
->isNewPost
= false;
424 Q_EMIT
showStatusMessage(statusMsg
, true);
425 Backend
*b
= new Backend(d
->mCurrentPostBlogId
, this);
426 connect(b
, &Backend::sigError
, this, &PostEntry::slotError
);
427 if (uploadMediaFiles(b
)) {
428 qCDebug(BLOGILO_LOG
) << "Uploading";
430 connect(b
, &Backend::sigPostPublished
, this, &PostEntry::slotPostPublished
);
432 b
->publishPost(&d
->mCurrentPost
);
434 b
->modifyPost(&d
->mCurrentPost
);
443 void PostEntry::slotPostPublished(int blog_id
, BilboPost
*post
)
445 qCDebug(BLOGILO_LOG
) << "BlogId: " << blog_id
<< "Post Id on server: " << post
->postId();
446 DBMan::self()->removeTempEntry(d
->mCurrentPost
);
448 setCurrentPost(*post
);
449 if (d
->mCurrentPost
.isPrivate()) {
450 msg
= i18n("Draft with title \"%1\" saved successfully.", post
->title());
451 } else if (d
->mCurrentPost
.status() == BilboPost::Modified
) {
452 msg
= i18n("Post with title \"%1\" modified successfully.", post
->title());
454 msg
= i18n("Post with title \"%1\" published successfully.", post
->title());
456 // KMessageBox::information( this, msg, "Successful" );
459 Q_EMIT
postPublishingDone(false, msg
);
460 sender()->deleteLater(); //FIXME Check if this command needed or NOT -Mehrdad
463 void PostEntry::showProgressBar()
466 d
->progress
= new QProgressBar(this);
467 layout()->addWidget(d
->progress
);
468 d
->progress
->setRange(0, 0);
472 void PostEntry::deleteProgressBar()
475 this->layout()->removeWidget(d
->progress
);
476 d
->progress
->deleteLater();
477 d
->progress
= Q_NULLPTR
;
481 void PostEntry::saveLocally()
483 if (currentPost()->content().isEmpty()) {
484 if (KMessageBox::warningYesNo(this, i18n("The current post content is empty, are you sure you want to save an empty post?")) == KMessageBox::No
) {
488 const int resId
= DBMan::self()->saveLocalEntry(*currentPost(), d
->mCurrentPostBlogId
);
490 KMessageBox::detailedSorry(this, i18n("Saving post locally failed."), DBMan::self()->lastErrorText());
493 d
->mCurrentPost
.setLocalId(resId
);
494 Q_EMIT
postSavedLocally();
495 Q_EMIT
showStatusMessage(i18n("Post saved locally."), false);
496 qCDebug(BLOGILO_LOG
) << "Locally saved";
499 void PostEntry::saveTemporary()
501 if (d
->isPostContentModified
) {
502 const int res
= DBMan::self()->saveTempEntry(*currentPost(), d
->mCurrentPostBlogId
);
504 d
->mCurrentPost
.setLocalId(res
);
505 Q_EMIT
postSavedTemporary();
506 qCDebug(BLOGILO_LOG
) << "Temporary saved";
508 qCDebug(BLOGILO_LOG
) << "Saving temporary failed: " << DBMan::self()->lastErrorText();
513 void PostEntry::slotPostModified()
516 disconnect(this, &PostEntry::textChanged
, this, &PostEntry::slotPostModified
);
517 // disconnect( txtTitle, SIGNAL(textChanged(QString)), this, SLOT(slotPostModified()) );
518 // Q_EMIT postModified();
519 d
->isPostContentModified
= true;