1 /*
2     SPDX-FileCopyrightText: 2007 Hamish Rodda <rodda@kde.org>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "statusbar.h"
8 #include "progresswidget/statusbarprogresswidget.h"
9 #include "progresswidget/progressmanager.h"
10 #include "progresswidget/progressdialog.h"
11 
12 #include <QTimer>
13 
14 #include <KColorScheme>
15 #include <KSqueezedTextLabel>
16 
17 #include <interfaces/istatus.h>
18 #include <interfaces/ilanguagecontroller.h>
19 #include <language/backgroundparser/backgroundparser.h>
20 
21 #include <sublime/view.h>
22 
23 #include "plugincontroller.h"
24 #include "core.h"
25 
26 namespace KDevelop
27 {
28 
StatusBar(QWidget * parent)29 StatusBar::StatusBar(QWidget* parent)
30     : QStatusBar(parent)
31     , m_timer(new QTimer(this))
32     , m_currentView(nullptr)
33 {
34 #ifdef Q_OS_MAC
35     /* At time of writing this is only required for OSX and only has effect on OSX.
36        ifdef for robustness to future platform dependent theme/widget changes
37        https://phabricator.kde.org/D656
38     */
39     setStyleSheet(QStringLiteral("QStatusBar{background:transparent;}"));
40 #endif
41 
42     m_timer->setSingleShot(true);
43     connect(m_timer, &QTimer::timeout, this, &StatusBar::slotTimeout);
44     connect(Core::self()->pluginController(), &IPluginController::pluginLoaded, this, &StatusBar::pluginLoaded);
45     const QList<IPlugin*> plugins = Core::self()->pluginControllerInternal()->allPluginsForExtension(QStringLiteral("IStatus"));
46 
47     for (IPlugin* plugin : plugins) {
48         registerStatus(plugin);
49     }
50 
51     registerStatus(Core::self()->languageController()->backgroundParser());
52 
53     m_progressController = Core::self()->progressController();
54     m_progressDialog = new ProgressDialog(this, parent); // construct this first, then progressWidget
55     m_progressDialog->setVisible(false);
56     m_progressWidget = new StatusbarProgressWidget(m_progressDialog, this);
57 
58     addPermanentWidget(m_progressWidget, 0);
59 }
60 
61 StatusBar::~StatusBar() = default;
62 
removeError(QWidget * w)63 void StatusBar::removeError(QWidget* w)
64 {
65     removeWidget(w);
66     w->deleteLater();
67 }
68 
viewChanged(Sublime::View * view)69 void StatusBar::viewChanged(Sublime::View* view)
70 {
71     if (m_currentView)
72         m_currentView->disconnect(this);
73 
74     m_currentView = view;
75 
76     if (view) {
77         connect(view, &Sublime::View::statusChanged, this, &StatusBar::viewStatusChanged);
78         QStatusBar::showMessage(view->viewStatus(), 0);
79 
80     }
81 }
82 
viewStatusChanged(Sublime::View * view)83 void StatusBar::viewStatusChanged(Sublime::View* view)
84 {
85     QStatusBar::showMessage(view->viewStatus(), 0);
86 }
87 
pluginLoaded(IPlugin * plugin)88 void StatusBar::pluginLoaded(IPlugin* plugin)
89 {
90     if (qobject_cast<IStatus*>(plugin))
91         registerStatus(plugin);
92 }
93 
registerStatus(QObject * status)94 void StatusBar::registerStatus(QObject* status)
95 {
96     Q_ASSERT(qobject_cast<IStatus*>(status));
97     // can't convert this to new signal slot syntax, IStatus is not a QObject
98     connect(status, SIGNAL(clearMessage(KDevelop::IStatus*)),
99             SLOT(clearMessage(KDevelop::IStatus*)));
100     connect(status, SIGNAL(showMessage(KDevelop::IStatus*,QString,int)),
101             SLOT(showMessage(KDevelop::IStatus*,QString,int)));
102     connect(status, SIGNAL(hideProgress(KDevelop::IStatus*)),
103             SLOT(hideProgress(KDevelop::IStatus*)));
104     connect(status, SIGNAL(showProgress(KDevelop::IStatus*,int,int,int)),
105             SLOT(showProgress(KDevelop::IStatus*,int,int,int)));
106     connect(status, SIGNAL(showErrorMessage(QString,int)),
107             SLOT(showErrorMessage(QString,int)));
108 }
109 
errorMessage(QWidget * parent,const QString & text)110 QWidget* errorMessage(QWidget* parent, const QString& text)
111 {
112     auto* label = new KSqueezedTextLabel(parent);
113     KStatefulBrush red(KColorScheme::Window, KColorScheme::NegativeText);
114     QPalette pal = label->palette();
115     pal.setBrush(QPalette::WindowText, red.brush(label->palette()));
116     label->setPalette(pal);
117     label->setAlignment(Qt::AlignRight);
118     label->setText(text);
119     label->setToolTip(text);
120     return label;
121 }
122 
errorTimeout(QWidget * error,int timeout)123 QTimer* StatusBar::errorTimeout(QWidget* error, int timeout)
124 {
125     auto* timer = new QTimer(error);
126     timer->setSingleShot(true);
127     timer->setInterval(1000*timeout);
128     connect(timer, &QTimer::timeout, this, [this, error](){ removeError(error); });
129     return timer;
130 }
131 
showErrorMessage(const QString & message,int timeout)132 void StatusBar::showErrorMessage(const QString& message, int timeout)
133 {
134     QWidget* error = errorMessage(this, message);
135     QTimer* timer = errorTimeout(error, timeout);
136     addWidget(error);
137     timer->start(); // triggers removeError()
138 }
139 
slotTimeout()140 void StatusBar::slotTimeout()
141 {
142     QMutableHashIterator<IStatus*, Message> it = m_messages;
143 
144     while (it.hasNext()) {
145         it.next();
146         if (it.value().timeout) {
147             it.value().timeout -= m_timer->interval();
148             if (it.value().timeout == 0)
149                 it.remove();
150         }
151     }
152 
153     updateMessage();
154 }
155 
updateMessage()156 void StatusBar::updateMessage()
157 {
158     if (m_timer->isActive()) {
159         m_timer->stop();
160         m_timer->setInterval(m_time.elapsed());
161         slotTimeout();
162     }
163 
164     int timeout = 0;
165 
166     QStringList messages;
167     messages.reserve(m_messages.size());
168     for (const Message& m : qAsConst(m_messages)) {
169         messages.append(m.text);
170 
171         if (timeout)
172             timeout = qMin(timeout, m.timeout);
173         else
174             timeout = m.timeout;
175     }
176 
177     if (!messages.isEmpty())
178         QStatusBar::showMessage(messages.join(QLatin1String("; ")));
179     else
180         QStatusBar::clearMessage();
181 
182     if (timeout) {
183         m_time.start();
184         m_timer->start(timeout);
185     }
186 }
187 
clearMessage(IStatus * status)188 void StatusBar::clearMessage( IStatus* status )
189 {
190     QTimer::singleShot(0, this, [this, status]() {
191         const auto messageIt = m_messages.find(status);
192         if (messageIt != m_messages.end()) {
193             m_messages.erase(messageIt);
194             updateMessage();
195         }
196     });
197 }
198 
showMessage(IStatus * status,const QString & message,int timeout)199 void StatusBar::showMessage( IStatus* status, const QString & message, int timeout)
200 {
201     QPointer<QObject> context = dynamic_cast<QObject*>(status);
202     QTimer::singleShot(0, this, [this, context, status, message, timeout]() {
203         if (!context)
204             return;
205         const auto progressItemIt = m_progressItems.constFind(status);
206         if (progressItemIt != m_progressItems.constEnd()) {
207             ProgressItem* i = *progressItemIt;
208             i->setStatus(message);
209         } else {
210             Message m;
211             m.text = message;
212             m.timeout = timeout;
213             m_messages.insert(status, m);
214             updateMessage();
215         }
216     });
217 }
218 
hideProgress(IStatus * status)219 void StatusBar::hideProgress( IStatus* status )
220 {
221     QTimer::singleShot(0, this, [this, status]() {
222         const auto progressItemIt = m_progressItems.find(status);
223         if (progressItemIt != m_progressItems.end()) {
224             (*progressItemIt)->setComplete();
225             m_progressItems.erase(progressItemIt);
226         }
227     });
228 }
229 
showProgress(IStatus * status,int minimum,int maximum,int value)230 void StatusBar::showProgress( IStatus* status, int minimum, int maximum, int value)
231 {
232     QPointer<QObject> context = dynamic_cast<QObject*>(status);
233     QTimer::singleShot(0, this, [this, context, status, minimum, maximum, value]() {
234         if (!context)
235             return;
236         auto progressItemIt = m_progressItems.find(status);
237         if (progressItemIt == m_progressItems.end()) {
238             bool canBeCanceled = false;
239             progressItemIt = m_progressItems.insert(status, m_progressController->createProgressItem(
240                 ProgressManager::createUniqueID(), status->statusName(), QString(), canBeCanceled));
241         }
242 
243         ProgressItem* i = *progressItemIt;
244         m_progressWidget->raise();
245         m_progressDialog->raise();
246         if( minimum == 0 && maximum == 0 ) {
247             i->setUsesBusyIndicator( true );
248         } else {
249             i->setUsesBusyIndicator( false );
250             i->setProgress( 100*value/maximum );
251         }
252     });
253 }
254 
255 }
256 
257