1 /*
2 * %kadu copyright begin%
3 * Copyright 2012 Wojciech Treter (juzefwt@gmail.com)
4 * Copyright 2013, 2014 Bartosz Brachaczek (b.brachaczek@gmail.com)
5 * Copyright 2013, 2014, 2015 Rafał Przemysław Malinowski (rafal.przemyslaw.malinowski@gmail.com)
6 * %kadu copyright end%
7 *
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) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 */
21
22 #include <QtCore/QPoint>
23 #include <QtCore/QVariant>
24 #include <QtGui/QDrag>
25 #include <QtWidgets/QHBoxLayout>
26 #include <QtWidgets/QMenu>
27
28 #include "chat/chat.h"
29 #include "chat/model/chat-data-extractor.h"
30 #include "configuration/configuration.h"
31 #include "configuration/deprecated-configuration-api.h"
32 #include "core/application.h"
33 #include "gui/hot-key.h"
34 #include "gui/widgets/chat-widget/chat-widget-manager.h"
35 #include "gui/widgets/chat-widget/chat-widget.h"
36 #include "gui/widgets/filtered-tree-view.h"
37 #include "gui/widgets/recent-chats-menu.h"
38 #include "gui/windows/message-dialog.h"
39 #include "gui/windows/open-chat-with/open-chat-with-service.h"
40 #include "gui/windows/open-chat-with/open-chat-with.h"
41 #include "icons/icons-manager.h"
42 #include "icons/kadu-icon.h"
43 #include "message/unread-message-repository.h"
44 #include "misc/misc.h"
45 #include "plugin/plugin-injected-factory.h"
46 #include "activate.h"
47
48 #include "tab-bar.h"
49 #include "tabs.h"
50
51 #include "tab-widget.h"
52
TabWidget(TabsManager * manager)53 TabWidget::TabWidget(TabsManager *manager) : Manager(manager)
54 {
55 }
56
~TabWidget()57 TabWidget::~TabWidget()
58 {
59 }
60
setApplication(Application * application)61 void TabWidget::setApplication(Application *application)
62 {
63 m_application = application;
64 }
65
setChatWidgetManager(ChatWidgetManager * chatWidgetManager)66 void TabWidget::setChatWidgetManager(ChatWidgetManager *chatWidgetManager)
67 {
68 m_chatWidgetManager = chatWidgetManager;
69 }
70
setConfiguration(Configuration * configuration)71 void TabWidget::setConfiguration(Configuration *configuration)
72 {
73 m_configuration = configuration;
74 }
75
setIconsManager(IconsManager * iconsManager)76 void TabWidget::setIconsManager(IconsManager *iconsManager)
77 {
78 m_iconsManager = iconsManager;
79 }
80
setPluginInjectedFactory(PluginInjectedFactory * pluginInjectedFactory)81 void TabWidget::setPluginInjectedFactory(PluginInjectedFactory *pluginInjectedFactory)
82 {
83 m_pluginInjectedFactory = pluginInjectedFactory;
84 }
85
setOpenChatWithService(OpenChatWithService * openChatWithService)86 void TabWidget::setOpenChatWithService(OpenChatWithService *openChatWithService)
87 {
88 m_openChatWithService = openChatWithService;
89 }
90
init()91 void TabWidget::init()
92 {
93 setWindowRole("kadu-tabs");
94
95 TabBar *tabbar = new TabBar(this);
96 setTabBar(tabbar);
97
98 setAcceptDrops(true);
99 setMovable(true);
100
101 setDocumentMode(true);
102
103 connect(tabbar, SIGNAL(contextMenu(int, const QPoint &)),
104 SLOT(onContextMenu(int, const QPoint &)));
105 connect(tabbar, SIGNAL(tabCloseRequested(int)),
106 SLOT(onDeleteTab(int)));
107 connect(tabbar,SIGNAL(mouseDoubleClickEventSignal(QMouseEvent *)),
108 SLOT(mouseDoubleClickEvent(QMouseEvent *)));
109 connect(tabbar, SIGNAL(currentChanged(int)),
110 SLOT(currentTabChanged(int)));
111
112 //widget (container) for buttons with opening conversations
113 //both buttons are displayed when checking Show "New Tab" button in configurations
114 OpenChatButtonsWidget = new QWidget(this);
115 QHBoxLayout *horizontalLayout = new QHBoxLayout;
116
117 horizontalLayout->setSpacing(2);
118 horizontalLayout->setContentsMargins(3, 0, 2, 3);
119
120 //button for new chat from last conversations
121 OpenRecentChatButton = new QToolButton(OpenChatButtonsWidget);
122 OpenRecentChatButton->setIcon(m_iconsManager->iconByPath(KaduIcon("internet-group-chat")));
123 OpenRecentChatButton->setToolTip(tr("Recent Chats"));
124 OpenRecentChatButton->setAutoRaise(true);
125 connect(OpenRecentChatButton, SIGNAL(clicked()), SLOT(openRecentChatsMenu()));
126
127 //menu for recent chats
128 RecentChatsMenuWidget = m_pluginInjectedFactory->makeInjected<RecentChatsMenu>(OpenRecentChatButton);
129 connect(RecentChatsMenuWidget, SIGNAL(triggered(QAction *)), this, SLOT(openRecentChat(QAction *)));
130 connect(RecentChatsMenuWidget, SIGNAL(chatsListAvailable(bool)), OpenRecentChatButton, SLOT(setEnabled(bool)));
131
132 //button for opening chat
133 QToolButton *openChatButton = new QToolButton(OpenChatButtonsWidget);
134 openChatButton->setIcon(m_iconsManager->iconByPath(KaduIcon("mail-message-new")));
135 openChatButton->setToolTip(tr("Open Chat with..."));
136 openChatButton->setAutoRaise(true);
137 connect(openChatButton, SIGNAL(clicked()), SLOT(newChat()));
138
139 horizontalLayout->addWidget(OpenRecentChatButton);
140 horizontalLayout->addWidget(openChatButton);
141
142 OpenChatButtonsWidget->setLayout(horizontalLayout);
143 OpenChatButtonsWidget->setVisible(false);
144
145 RightCornerWidget = new QWidget(this);
146 QHBoxLayout *rightCornerWidgetLayout = new QHBoxLayout;
147
148 rightCornerWidgetLayout->setSpacing(2);
149 rightCornerWidgetLayout->setContentsMargins(3, 0, 2, 3);
150
151 TabsMenu = new QMenu(this);
152 connect(TabsMenu, SIGNAL(triggered(QAction *)), this, SLOT(tabsMenuSelected(QAction *)));
153 TabsListButton = new QToolButton(RightCornerWidget);
154 TabsListButton->setIcon(m_iconsManager->iconByPath(KaduIcon("internet-group-chat")));
155 TabsListButton->setToolTip(tr("Tabs"));
156 TabsListButton->setAutoRaise(true);
157 TabsListButton->setVisible(false);
158 TabsListButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
159 TabsListButton->setMenu(TabsMenu);
160 connect(TabsListButton, SIGNAL(clicked()), SLOT(openTabsList()));
161 rightCornerWidgetLayout->addWidget(TabsListButton);
162
163 //przycisk zamkniecia aktywnej karty znajdujacy sie w prawym gornym rogu
164 CloseChatButton = new QToolButton(this);
165 CloseChatButton->setIcon(m_iconsManager->iconByPath(KaduIcon("kadu_icons/tab-remove")));
166 CloseChatButton->setToolTip(tr("Close Tab"));
167 CloseChatButton->setAutoRaise(true);
168 CloseChatButton->setVisible(false);
169 connect(CloseChatButton, SIGNAL(clicked()), SLOT(deleteTab()));
170 rightCornerWidgetLayout->addWidget(CloseChatButton);
171
172 RightCornerWidget->setLayout(rightCornerWidgetLayout);
173 setCornerWidget(RightCornerWidget, Qt::TopRightCorner);
174
175 configurationUpdated();
176 }
177
isTabVisible(int index)178 bool TabWidget::isTabVisible(int index)
179 {
180 QRect visibleTabRect = tabBar()->rect().intersected(tabBar()->tabRect(index));
181
182 return visibleTabRect.width() > 20;
183 }
184
updateTabsMenu()185 void TabWidget::updateTabsMenu()
186 {
187 TabsMenu->clear();
188
189 for (int i = 0; i < count(); i++)
190 {
191 QAction *action = new QAction(QIcon(), tabText(i), this);
192 action->setData(QVariant(i));
193
194 if (i == tabBar()->currentIndex())
195 {
196 QFont font = action->font();
197 font.setBold(true);
198 action->setFont(font);
199 }
200
201 TabsMenu->addAction(action);
202 }
203 }
204
currentTabChanged(int index)205 void TabWidget::currentTabChanged(int index)
206 {
207 Q_UNUSED(index);
208
209 updateTabsMenu();
210 }
211
tabsMenuSelected(QAction * action)212 void TabWidget::tabsMenuSelected(QAction *action)
213 {
214 setCurrentIndex(action->data().toInt());
215 tabBar()->setCurrentIndex(action->data().toInt());
216 }
217
tryActivateChatWidget(ChatWidget * chatWidget)218 void TabWidget::tryActivateChatWidget(ChatWidget *chatWidget)
219 {
220 int index = indexOf(chatWidget);
221 if (index < 0)
222 return;
223
224 _activateWindow(m_configuration, this);
225
226 setCurrentIndex(index);
227 chatWidget->edit()->setFocus();
228 }
229
tryMinimizeChatWidget(ChatWidget * chatWidget)230 void TabWidget::tryMinimizeChatWidget(ChatWidget *chatWidget)
231 {
232 int index = indexOf(chatWidget);
233 if (index < 0)
234 return;
235
236 if (count() == 1)
237 window()->showMinimized();
238 }
239
closeTab(ChatWidget * chatWidget)240 void TabWidget::closeTab(ChatWidget *chatWidget)
241 {
242 if (!chatWidget)
243 return;
244
245 if (m_configuration->deprecatedApi()->readBoolEntry("Chat", "ChatCloseTimer"))
246 {
247 unsigned int period = m_configuration->deprecatedApi()->readUnsignedNumEntry("Chat",
248 "ChatCloseTimerPeriod", 2);
249
250 if (QDateTime::currentDateTime() < chatWidget->lastReceivedMessageTime().addSecs(period))
251 {
252 MessageDialog *dialog = MessageDialog::create(m_iconsManager->iconByPath(KaduIcon("dialog-question")), tr("Kadu"), tr("New message received, close window anyway?"));
253 dialog->addButton(QMessageBox::Yes, tr("Close window"));
254 dialog->addButton(QMessageBox::No, tr("Cancel"));
255
256 if (!dialog->ask())
257 return;
258 }
259 }
260
261 delete chatWidget;
262 }
263
isChatWidgetActive(const ChatWidget * chatWidget)264 bool TabWidget::isChatWidgetActive(const ChatWidget *chatWidget)
265 {
266 return currentWidget() == chatWidget && _isWindowActiveOrFullyVisible(this);
267 }
268
closeEvent(QCloseEvent * e)269 void TabWidget::closeEvent(QCloseEvent *e)
270 {
271 // do not block window closing when session is about to close
272 if (m_application->isSavingSession())
273 {
274 QTabWidget::closeEvent(e);
275 return;
276 }
277
278 //w zaleznosci od opcji w konfiguracji zamykamy wszystkie karty, lub tylko aktywna
279 if (config_oldStyleClosing)
280 closeTab(static_cast<ChatWidget *>(currentWidget()));
281 else
282 for (int i = count() - 1; i >= 0; i--)
283 closeTab(static_cast<ChatWidget *>(widget(i)));
284
285 if (count() > 0)
286 e->ignore();
287 else
288 e->accept();
289 }
290
chatKeyPressed(QKeyEvent * e,CustomInput * k,bool & handled)291 void TabWidget::chatKeyPressed(QKeyEvent *e, CustomInput *k, bool &handled)
292 {
293 Q_UNUSED(k)
294
295 if (handled)
296 return;
297
298 handled = true;
299 // obsluga skrotow klawiszowych
300 if (HotKey::shortCut(m_configuration, e, "ShortCuts", "MoveTabLeft"))
301 moveTabLeft();
302 else if (HotKey::shortCut(m_configuration, e, "ShortCuts", "MoveTabRight"))
303 moveTabRight();
304 else if (HotKey::shortCut(m_configuration, e, "ShortCuts", "SwitchTabLeft"))
305 switchTabLeft();
306 else if (HotKey::shortCut(m_configuration, e, "ShortCuts", "SwitchTabRight"))
307 switchTabRight();
308 else if (HotKey::shortCut(m_configuration, e, "ShortCuts", "ReopenClosedTab"))
309 Manager->reopenClosedChat();
310 #if defined(Q_OS_WIN)
311 #define TAB_SWITCH_MODIFIER "Ctrl"
312 #else
313 #define TAB_SWITCH_MODIFIER "Alt"
314 #endif
315 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+0")
316 setCurrentIndex(count() - 1);
317 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+1")
318 setCurrentIndex(0);
319 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+2")
320 setCurrentIndex(1);
321 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+3")
322 setCurrentIndex(2);
323 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+4")
324 setCurrentIndex(3);
325 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+5")
326 setCurrentIndex(4);
327 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+6")
328 setCurrentIndex(5);
329 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+7")
330 setCurrentIndex(6);
331 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+8")
332 setCurrentIndex(7);
333 else if (HotKey::keyEventToString(e, QKeySequence::PortableText) == TAB_SWITCH_MODIFIER "+9")
334 setCurrentIndex(8);
335 else
336 // skrot nie zostal znaleziony i wykonany. Przekazujemy zdarzenie dalej
337 handled = false;
338 }
339
onContextMenu(int id,const QPoint & pos)340 void TabWidget::onContextMenu(int id, const QPoint &pos)
341 {
342 emit contextMenu(widget(id), pos);
343 }
344
moveTab(int from,int to)345 void TabWidget::moveTab(int from, int to)
346 {
347 kdebugf();
348 QString tablabel = tabText(from);
349 QWidget *w = widget(from);
350 QIcon tabiconset = tabIcon(from);
351 QString tabtooltip = tabToolTip(from);
352 bool current = (w == currentWidget());
353 blockSignals(true);
354 removeTab(from);
355
356 insertTab(to, w, tabiconset, tablabel);
357 setTabToolTip(to, tabtooltip);
358
359 if (current)
360 setCurrentIndex(to);
361
362 blockSignals(false);
363 }
364
onDeleteTab(int id)365 void TabWidget::onDeleteTab(int id)
366 {
367 closeTab(static_cast<ChatWidget *>(widget(id)));
368 }
369
switchTabLeft()370 void TabWidget::switchTabLeft()
371 {
372 if (currentIndex() == 0)
373 setCurrentIndex(count() - 1);
374 else
375 setCurrentIndex(currentIndex() - 1);
376 }
377
switchTabRight()378 void TabWidget::switchTabRight()
379 {
380 if (currentIndex() == (count() - 1))
381 setCurrentIndex(0);
382 else
383 setCurrentIndex(currentIndex() + 1);
384 }
385
moveTabLeft()386 void TabWidget::moveTabLeft()
387 {
388 if (count() == 1)
389 return;
390
391 if (currentIndex() == 0)
392 moveTab(0, count() - 1);
393 else
394 moveTab(currentIndex(), currentIndex() - 1);
395 }
396
moveTabRight()397 void TabWidget::moveTabRight()
398 {
399 if (count() == 1)
400 return;
401
402 if (currentIndex() == (count() - 1))
403 moveTab(count() - 1, 0);
404 else
405 moveTab(currentIndex(), currentIndex() + 1);
406 }
407
dragEnterEvent(QDragEnterEvent * e)408 void TabWidget::dragEnterEvent(QDragEnterEvent* e)
409 {
410 kdebugf();
411 // Akceptujemu dnd jezeli pochodzi on z UserBox'a lub paska kart
412 // if ((UlesDrag::canDecode(e) && (qobject_cast<ContactsListWidget *>(e->source()))))
413 // e->acceptProposedAction();
414 // else
415 e->ignore();
416 //
417 kdebugf2();
418 }
419
dropEvent(QDropEvent * e)420 void TabWidget::dropEvent(QDropEvent* e)
421 {
422 kdebugf();
423 QStringList ules;
424
425 // Jezeli dnd pochodzil z userboxa probujemy dodac nowa karte
426 if (qobject_cast<FilteredTreeView *>(e->source()) && false)/*UlesDrag::decode(e, ules))*/
427 {
428 if (tabBar()->tabAt(e->pos()) != -1)
429 // Jezeli w miejscu upuszczenia jest karta, dodajemy na jej pozycji
430 emit openTab(ules, tabBar()->tabAt(e->pos()));
431 else
432 // Jezeli nie na koncu tabbara
433 emit openTab(ules, -1);
434 }
435
436 kdebugf2();
437 }
438
changeEvent(QEvent * event)439 void TabWidget::changeEvent(QEvent *event)
440 {
441 QTabWidget::changeEvent(event);
442 if (event->type() == QEvent::ActivationChange)
443 {
444 kdebugf();
445 ChatWidget *chatWidget = static_cast<ChatWidget *>(currentWidget());
446 if (chatWidget && _isActiveWindow(this))
447 emit chatWidgetActivated(chatWidget);
448 kdebugf2();
449 }
450 }
451
mouseDoubleClickEvent(QMouseEvent * e)452 void TabWidget::mouseDoubleClickEvent(QMouseEvent *e)
453 {
454 kdebugf();
455 // jezeli dwuklik nastapil lewym przyciskiem myszy pokazujemy okno openchatwith
456 if (e->button() == Qt::LeftButton)
457 newChat();
458 kdebugf2();
459 }
460
newChat()461 void TabWidget::newChat()
462 {
463 m_openChatWithService->show();
464 }
465
openRecentChatsMenu()466 void TabWidget::openRecentChatsMenu()
467 {
468 //show last conversations menu under widget with buttons for opening chats
469 RecentChatsMenuWidget->popup(OpenChatButtonsWidget->mapToGlobal(QPoint(0, OpenChatButtonsWidget->height())));
470 }
471
openTabsList()472 void TabWidget::openTabsList()
473 {
474 //show last conversations menu under widget with buttons for opening chats
475 TabsMenu->popup(RightCornerWidget->mapToGlobal(QPoint(0, RightCornerWidget->height())));
476 }
477
openRecentChat(QAction * action)478 void TabWidget::openRecentChat(QAction *action)
479 {
480 m_chatWidgetManager->openChat(action->data().value<Chat>(), OpenChatActivation::Activate);
481 }
482
deleteTab()483 void TabWidget::deleteTab()
484 {
485 closeTab(static_cast<ChatWidget *>(currentWidget()));
486 }
487
tabInserted(int index)488 void TabWidget::tabInserted(int index)
489 {
490 Q_UNUSED(index)
491
492 auto chatWidget = static_cast<ChatWidget *>(widget(index));
493 connect(chatWidget, SIGNAL(closeRequested(ChatWidget*)), this, SLOT(closeTab(ChatWidget*)));
494
495 updateTabsListButton();
496 updateTabsMenu();
497 }
498
tabRemoved(int index)499 void TabWidget::tabRemoved(int index)
500 {
501 Q_UNUSED(index)
502
503 updateTabsListButton();
504 updateTabsMenu();
505
506 if (count() == 0)
507 hide();
508 }
509
compositingEnabled()510 void TabWidget::compositingEnabled()
511 {
512 if (m_configuration->deprecatedApi()->readBoolEntry("Chat", "UseTransparency", false))
513 {
514 setAutoFillBackground(false);
515 setAttribute(Qt::WA_TranslucentBackground, true);
516 }
517 else
518 compositingDisabled();
519 }
520
compositingDisabled()521 void TabWidget::compositingDisabled()
522 {
523 setAttribute(Qt::WA_TranslucentBackground, false);
524 setAttribute(Qt::WA_NoSystemBackground, false);
525 setAutoFillBackground(true);
526 }
527
configurationUpdated()528 void TabWidget::configurationUpdated()
529 {
530 triggerCompositingStateChanged();
531
532 CloseChatButton->setIcon(m_iconsManager->iconByPath(KaduIcon("kadu_icons/tab-remove")));
533
534 setTabsClosable(m_configuration->deprecatedApi()->readBoolEntry("Tabs", "CloseButtonOnTab"));
535 config_oldStyleClosing = m_configuration->deprecatedApi()->readBoolEntry("Tabs", "OldStyleClosing");
536
537 bool isOpenChatButtonEnabled = (cornerWidget(Qt::TopLeftCorner) == OpenChatButtonsWidget);
538 bool shouldEnableOpenChatButton = m_configuration->deprecatedApi()->readBoolEntry("Tabs", "OpenChatButton");
539 bool isCloseButtonEnabled = CloseChatButton->isVisible();
540 bool shouldEnableCloseButton = m_configuration->deprecatedApi()->readBoolEntry("Tabs", "CloseButton");
541
542 if (isOpenChatButtonEnabled != shouldEnableOpenChatButton)
543 {
544 OpenChatButtonsWidget->setVisible(true);
545 setCornerWidget(shouldEnableOpenChatButton ? OpenChatButtonsWidget : 0, Qt::TopLeftCorner);
546 }
547
548 if (isCloseButtonEnabled != shouldEnableCloseButton)
549 {
550 CloseChatButton->setVisible(shouldEnableCloseButton);
551 }
552 }
553
showEvent(QShowEvent * e)554 void TabWidget::showEvent(QShowEvent* e)
555 {
556 QTabWidget::showEvent(e);
557
558 updateTabsListButton();
559 updateTabsMenu();
560 }
561
resizeEvent(QResizeEvent * e)562 void TabWidget::resizeEvent(QResizeEvent *e)
563 {
564 QTabWidget::resizeEvent(e);
565
566 updateTabsListButton();
567 updateTabsMenu();
568 }
569
updateTabsListButton()570 void TabWidget::updateTabsListButton()
571 {
572 bool allTabsVisible = true;
573
574 for (int i = 0; i < tabBar()->count(); i++)
575 {
576 if (!isTabVisible(i))
577 {
578 allTabsVisible = false;
579 break;
580 }
581 }
582
583 TabsListButton->setVisible(!allTabsVisible);
584 TabsListButton->setText(QString::number(count()));
585 }
586
587 #include "moc_tab-widget.cpp"
588