OSDN Git Service

kate: always use the KMessageWidget animation feature
[kde/kde-workspace.git] / kate / part / view / katemessagewidget.cpp
1 /* This file is part of the KDE and the Kate project
2  *
3  *   Copyright (C) 2012 Dominik Haumann <dhaumann@kde.org>
4  *
5  *  This library is free software; you can redistribute it and/or
6  *  modify it under the terms of the GNU Library General Public
7  *  License as published by the Free Software Foundation; either
8  *  version 2 of the License, or (at your option) any later version.
9  *
10  *  This library is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  *  Library General Public 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
17  *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  *  Boston, MA 02110-1301, USA.
19  */
20
21 #include "katemessagewidget.h"
22 #include "moc_katemessagewidget.cpp"
23
24 #include <ktexteditor/messageinterface.h>
25 #include <kmessagewidget.h>
26 #include <kateanimation.h>
27
28 #include <kdeversion.h>
29 #include <kdebug.h>
30
31 #include <QtCore/QEvent>
32 #include <QtCore/QTimer>
33 #include <QtGui/QVBoxLayout>
34 #include <QToolTip>
35 #include <QtGui/qevent.h>
36
37 static const int s_defaultAutoHideTime = 6 * 1000;
38
39 KateMessageWidget::KateMessageWidget(QWidget* parent, bool applyFadeEffect)
40   : QWidget(parent)
41   , m_animation(0)
42   , m_autoHideTimer(new QTimer(this))
43   , m_autoHideTime(-1)
44 {
45   QVBoxLayout* l = new QVBoxLayout();
46   l->setMargin(0);
47
48   m_messageWidget = new KMessageWidget(this);
49   m_messageWidget->setCloseButtonVisible(false);
50
51   l->addWidget(m_messageWidget);
52   setLayout(l);
53
54   // tell the widget to always use the minimum size.
55   setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
56
57   // install event filter so we catch the end of the hide animation
58   m_messageWidget->installEventFilter(this);
59
60   // by default, hide widgets
61   m_messageWidget->hide();
62   hide();
63
64   // create animation controller, and connect widgetHidden() to showNextMessage()
65   m_animation = new KateAnimation(m_messageWidget, applyFadeEffect);
66   connect(m_animation, SIGNAL(widgetHidden()), this, SLOT(showNextMessage()));
67
68   // setup autoHide timer details
69   m_autoHideTimer->setSingleShot(true);
70
71   // KMessageWidget::linkHovered() is new in KDE 4.11
72   connect(m_messageWidget, SIGNAL(linkHovered(const QString&)), this, SLOT(linkHovered(const QString&)));
73 }
74
75 void KateMessageWidget::showNextMessage()
76 {
77   // at this point, we should not have a currently shown message
78   Q_ASSERT(m_currentMessage == 0);
79
80   // if not message to show, just stop
81   if (m_messageQueue.size() == 0) {
82     hide();
83     return;
84   }
85
86   // track current message
87   m_currentMessage = m_messageQueue[0];
88
89   // set text etc.
90   m_messageWidget->setText(m_currentMessage->text());
91   // KMessageWidget::setIcon() requires KDE >= 4.11
92   m_messageWidget->setIcon(m_currentMessage->icon());
93
94   // connect textChanged() and iconChanged(), so it's possible to change this on the fly
95   connect(m_currentMessage, SIGNAL(textChanged(const QString&)),
96           m_messageWidget, SLOT(setText(const QString&)), Qt::UniqueConnection);
97   connect(m_currentMessage, SIGNAL(iconChanged(const QIcon&)),
98           m_messageWidget, SLOT(setIcon(const QIcon&)), Qt::UniqueConnection);
99
100   // the enums values do not necessarily match, hence translate with switch
101   switch (m_currentMessage->messageType()) {
102     case KTextEditor::Message::Positive:
103       m_messageWidget->setMessageType(KMessageWidget::Positive);
104       break;
105     case KTextEditor::Message::Information:
106       m_messageWidget->setMessageType(KMessageWidget::Information);
107       break;
108     case KTextEditor::Message::Warning:
109       m_messageWidget->setMessageType(KMessageWidget::Warning);
110       break;
111     case KTextEditor::Message::Error:
112       m_messageWidget->setMessageType(KMessageWidget::Error);
113       break;
114     default:
115       m_messageWidget->setMessageType(KMessageWidget::Information);
116       break;
117   }
118
119   // remove all actions from the message widget
120   foreach (QAction* a, m_messageWidget->actions())
121     m_messageWidget->removeAction(a);
122
123   // add new actions to the message widget
124   foreach (QAction* a, m_currentMessage->actions())
125     m_messageWidget->addAction(a);
126
127   // set word wrap of the message
128   setWordWrap(m_currentMessage);
129
130   // setup auto-hide timer, and start if requested
131   m_autoHideTime = m_currentMessage->autoHide();
132   m_autoHideTimer->stop();
133   if (m_autoHideTime >= 0) {
134     connect(m_autoHideTimer, SIGNAL(timeout()), m_currentMessage, SLOT(deleteLater()), Qt::UniqueConnection);
135     if (m_currentMessage->autoHideMode() == KTextEditor::Message::Immediate) {
136       m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime);
137     }
138   }
139
140   // finally show
141   show();
142   m_animation->show();
143 }
144
145 void KateMessageWidget::setWordWrap(KTextEditor::Message* message)
146 {
147   // want word wrap anyway? -> ok
148   if (message->wordWrap()) {
149     m_messageWidget->setWordWrap(message->wordWrap());
150     return;
151   }
152
153   // word wrap not wanted, that's ok if a parent widget does not exist
154   if (!parentWidget()) {
155     m_messageWidget->setWordWrap(false);
156     return;
157   }
158
159   // word wrap not wanted -> enable word wrap if it breaks the layout otherwise
160   int margin = 0;
161   if (parentWidget()->layout()) {
162     // get left/right margin of the layout, since we need to subtract these
163     int leftMargin = 0, rightMargin = 0;
164     parentWidget()->layout()->getContentsMargins(&leftMargin, 0, &rightMargin, 0);
165     margin = leftMargin + rightMargin;
166   }
167
168   // if word wrap enabled, first disable it
169   if (m_messageWidget->wordWrap())
170     m_messageWidget->setWordWrap(false);
171
172   // make sure the widget's size is up-to-date in its hidden state
173   m_messageWidget->ensurePolished();
174   m_messageWidget->adjustSize();
175
176   // finally enable word wrap, if there is not enough free horizontal space
177   const int freeSpace = (parentWidget()->width() - margin) - m_messageWidget->width();
178   if (freeSpace < 0) {
179 //     kDebug() << "force word wrap to avoid breaking the layout" << freeSpace;
180     m_messageWidget->setWordWrap(true);
181   }
182 }
183
184 void KateMessageWidget::postMessage(KTextEditor::Message* message,
185                            QList<QSharedPointer<QAction> > actions)
186 {
187   Q_ASSERT(!m_messageHash.contains(message));
188   m_messageHash[message] = actions;
189
190   // insert message sorted after priority
191   int i = 0;
192   for (; i < m_messageQueue.count(); ++i) {
193     if (message->priority() > m_messageQueue[i]->priority())
194       break;
195   }
196
197   // queue message
198   m_messageQueue.insert(i, message);
199
200   // catch if the message gets deleted
201   connect(message, SIGNAL(closed(KTextEditor::Message*)), SLOT(messageDestroyed(KTextEditor::Message*)));
202
203   if (i == 0 && !m_animation->hideAnimationActive()) {
204     // if message has higher priority than the one currently shown,
205     // then hide the current one and then show the new one.
206     if (m_currentMessage) {
207
208       // autoHide timer may be running for currently shown message, therefore
209       // simply disconnect autoHide timer to all timeout() receivers
210       disconnect(m_autoHideTimer, SIGNAL(timeout()), 0, 0);
211       m_autoHideTimer->stop();
212
213       // if there is a current message, the message queue must contain 2 messages
214       Q_ASSERT(m_messageQueue.size() > 1);
215       Q_ASSERT(m_currentMessage == m_messageQueue[1]);
216
217       // a bit unnice: disconnect textChanged() and iconChanged() signals of previously visible message
218       disconnect(m_currentMessage, SIGNAL(textChanged(const QString&)),
219                  m_messageWidget, SLOT(setText(const QString&)));
220       disconnect(m_currentMessage, SIGNAL(iconChanged(const QIcon&)),
221                  m_messageWidget, SLOT(setIcon(const QIcon&)));
222
223       m_currentMessage = 0;
224       m_animation->hide();
225     } else {
226       showNextMessage();
227     }
228   }
229 }
230
231 void KateMessageWidget::messageDestroyed(KTextEditor::Message* message)
232 {
233   // last moment when message is valid, since KTE::Message is already in
234   // destructor we have to do the following:
235   // 1. remove message from m_messageQueue, so we don't care about it anymore
236   // 2. activate hide animation or show a new message()
237
238   // remove widget from m_messageQueue
239   int i = 0;
240   for (; i < m_messageQueue.count(); ++i) {
241     if (m_messageQueue[i] == message) {
242       break;
243     }
244   }
245
246   // the message must be in the list
247   Q_ASSERT(i < m_messageQueue.count());
248
249   // remove message
250   m_messageQueue.removeAt(i);
251
252   // remove message from hash -> release QActions
253   Q_ASSERT(m_messageHash.contains(message));
254   m_messageHash.remove(message);
255
256   // if deleted message is the current message, launch hide animation
257   if (message == m_currentMessage) {
258     m_currentMessage = 0;
259     m_animation->hide();
260   }
261 }
262
263 void KateMessageWidget::startAutoHideTimer()
264 {
265   // message does not want autohide, or timer already running
266   if ( !m_currentMessage           // no message, nothing to do
267     || m_autoHideTime < 0          // message does not want auto-hide
268     || m_autoHideTimer->isActive() // auto-hide timer is already active
269     || m_animation->hideAnimationActive() // widget is in hide animation phase
270     || m_animation->showAnimationActive() // widget is in show animation phase
271   ) {
272     return;
273   }
274
275   // safety checks: the message must still be valid
276   Q_ASSERT(m_messageQueue.size());
277   Q_ASSERT(m_currentMessage->autoHide() == m_autoHideTime);
278
279   // start autoHide timer as requrested
280   m_autoHideTimer->start(m_autoHideTime == 0 ? s_defaultAutoHideTime : m_autoHideTime);
281 }
282
283 void KateMessageWidget::linkHovered(const QString& link)
284 {
285   QToolTip::showText(QCursor::pos(), link, m_messageWidget);
286 }
287
288 QString KateMessageWidget::text() const
289 {
290   return m_messageWidget->text();
291 }
292 // kate: space-indent on; indent-width 2; replace-tabs on;