1 /*
2 * wbdlg.cpp - dialog for whiteboarding
3 * Copyright (C) 2006 Joonas Govenius
4 *
5 * Originally developed from:
6 * chatdlg.cpp - dialog for handling chats
7 * Copyright (C) 2001, 2002 Justin Karneges
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this library; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 *
23 */
24
25 #include "wbdlg.h"
26
27 #include <QMessageBox>
28 #include <QHBoxLayout>
29 #include <QVBoxLayout>
30 #include <QColorDialog>
31 #include <QToolButton>
32
33 #include "accountlabel.h"
34 #include "stretchwidget.h"
35 #include "iconset.h"
36
37 static const QString geometryOption = "options.ui.chat.wb-size";
38
39 //----------------------------------------------------------------------------
40 // WbDlg
41 //----------------------------------------------------------------------------
42
WbDlg(SxeSession * session,PsiAccount * pa)43 WbDlg::WbDlg(SxeSession* session, PsiAccount* pa) {
44 groupChat_ = session->groupChat();
45 pending_ = 0;
46 keepOpen_ = false;
47 allowEdits_ = true;
48
49 selfDestruct_ = 0;
50 setAttribute(Qt::WA_DeleteOnClose, false); // we want deferred endSession call and delete from manager
51
52 setWindowTitle(CAP(tr("Whiteboard (%1)").arg(pa->jid().bare())));
53 QVBoxLayout *vb1 = new QVBoxLayout(this);
54
55 // first row
56 le_jid_ = new QLineEdit(this);
57 le_jid_->setReadOnly(true);
58 le_jid_->setFocusPolicy(Qt::NoFocus);
59 le_jid_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
60 lb_ident_ = new AccountLabel(this);
61 lb_ident_->setAccount(pa);
62 lb_ident_->setShowJid(false);
63 lb_ident_->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum));
64 QHBoxLayout *hb1 = new QHBoxLayout();
65 hb1->addWidget(le_jid_);
66 hb1->addWidget(lb_ident_);
67 vb1->addLayout(hb1);
68
69 // mid area
70 wbWidget_ = new WbWidget(session, this);
71 wbWidget_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
72 vb1->addWidget(wbWidget_);
73
74 // Bottom (tool) area
75 act_save_ = new IconAction(tr("Save session"), "psi/saveBoard", tr("Save the contents of the whiteboard"), 0, this );
76 act_geometry_ = new IconAction(tr("Change the geometry"), "psi/whiteboard", tr("Change the geometry"), 0, this );
77 act_clear_ = new IconAction(tr("End session"), "psi/clearChat", tr("Clear the whiteboard"), 0, this );
78 act_end_ = new IconAction(tr("End session"), "psi/closetab", tr("End session"), 0, this );
79
80 // Black is the default color
81 QPixmap pixmap(16, 16);
82 pixmap.fill(QColor(Qt::black));
83 act_color_ = new QAction(QIcon(pixmap), tr("Stroke color"), this);
84 pixmap.fill(QColor(Qt::lightGray));
85 act_fill_ = new IconAction(tr("Fill color"), "psi/select", tr("Fill color"),0, this, 0, true);
86 act_fill_->setIcon(QIcon(pixmap));
87 act_fill_->setChecked(false);
88
89 act_widths_ = new IconAction(tr("Stroke width" ), "psi/drawPaths", tr("Stroke width"), 0, this );
90 act_modes_ = new IconAction(tr("Edit mode" ), "psi/select", tr("Edit mode"), 0, this );
91 group_widths_ = new QActionGroup(this);
92 group_modes_ = new QActionGroup(this);
93
94 connect(act_color_, SIGNAL(triggered()), SLOT(setStrokeColor()));
95 connect(act_fill_, SIGNAL(triggered(bool)), SLOT(setFillColor(bool)));
96 connect(group_widths_, SIGNAL(triggered(QAction *)), SLOT(setStrokeWidth(QAction *)));
97 connect(group_modes_, SIGNAL(triggered(QAction *)), SLOT(setMode(QAction *)));
98 connect(act_save_, SIGNAL(triggered()), SLOT(save()));
99 connect(act_geometry_, SIGNAL(triggered()), SLOT(setGeometry()));
100 connect(act_clear_, SIGNAL(triggered()), wbWidget_, SLOT(clear()));
101 connect(act_end_, SIGNAL(triggered()), SLOT(endSession()));
102
103 pixmap = QPixmap(2, 2);
104 pixmap.fill(QColor(Qt::black));
105 QAction* widthaction = new QAction(QIcon(pixmap), tr("Thin stroke"), group_widths_);
106 widthaction->setData(QVariant(1));
107 widthaction->setCheckable(true);
108 widthaction->trigger();
109 pixmap = QPixmap(6, 6);
110 pixmap.fill(QColor(Qt::black));
111 widthaction = new QAction(QIcon(pixmap), tr("Medium stroke"), group_widths_);
112 widthaction->setData(QVariant(3));
113 widthaction->setCheckable(true);
114 pixmap = QPixmap(12, 12);
115 pixmap.fill(QColor(Qt::black));
116 widthaction = new QAction(QIcon(pixmap), tr("Thick stroke"), group_widths_);
117 widthaction->setData(QVariant(6));
118 widthaction->setCheckable(true);
119
120 IconAction* action;
121 action = new IconAction(tr("Select"), "psi/select", tr("Select"), 0, group_modes_ );
122 action->setData(QVariant(WbWidget::Select));
123 action->setCheckable(true);
124 action = new IconAction(tr( "Translate"), "psi/translate", tr("Translate"), 0, group_modes_ );
125 action->setData(QVariant(WbWidget::Translate));
126 action->setCheckable(true);
127 action = new IconAction(tr( "Rotate"), "psi/rotate", tr("Rotate"), 0, group_modes_ );
128 action->setData(QVariant(WbWidget::Rotate));
129 action->setCheckable(true);
130 action = new IconAction(tr( "Scale"), "psi/scale", tr("Scale"), 0, group_modes_ );
131 action->setData(QVariant(WbWidget::Scale));
132 action->setCheckable(true);
133 action = new IconAction(tr( "Erase"), "psi/erase", tr("Erase"), 0, group_modes_ );
134 action->setData(QVariant(WbWidget::Erase));
135 action->setCheckable(true);
136 QAction *separator = new QAction(group_modes_);
137 separator->setSeparator(true);
138 action = new IconAction(tr( "Scroll view"), "psi/scroll", tr("Scroll"), 0, group_modes_ );
139 action->setData(QVariant(WbWidget::Scroll));
140 action->setCheckable(true);
141 separator = new QAction(group_modes_);
142 separator->setSeparator(true);
143 action = new IconAction(tr( "Draw paths"), "psi/drawPaths", tr("Draw paths"), 0, group_modes_ );
144 action->setData(QVariant(WbWidget::DrawPath));
145 action->setCheckable(true);
146 action->trigger();
147 // action = new IconAction(tr( "Draw lines"), "psi/drawLines", tr("Draw lines"), 0, group_modes_ );
148 // action->setData(QVariant(WbWidget::DrawLine));
149 // action->setCheckable(true);
150 // action = new IconAction(tr( "Draw ellipses"), "psi/drawEllipses", tr("Draw ellipses"), 0, group_modes_ );
151 // action->setData(QVariant(WbWidget::DrawEllipse));
152 // action->setCheckable(true);
153 // action = new IconAction(tr( "Draw circles"), "psi/drawCircles", tr("Draw circles"), 0, group_modes_ );
154 // action->setData(QVariant(WbWidget::DrawCircle));
155 // action->setCheckable(true);
156 // action = new IconAction(tr( "Draw rectangles"), "psi/drawRectangles", tr("Draw rectangles"), 0, group_modes_ );
157 // action->setData(QVariant(WbWidget::DrawRectangle));
158 // action->setCheckable(true);
159 // action = new IconAction(tr( "Add text"), "psi/addText", tr("Add text"), 0, group_modes_ );
160 // action->setData(QVariant(WbWidget::DrawText));
161 // action->setCheckable(true);
162 action = new IconAction(tr( "Add images"), "psi/addImage", tr("Add images"), 0, group_modes_ );
163 action->setData(QVariant(WbWidget::DrawImage));
164 action->setCheckable(true);
165
166 menu_widths_ = new QMenu(this);
167 menu_widths_->addActions(group_widths_->actions());
168 act_widths_->setMenu(menu_widths_);
169
170 menu_modes_ = new QMenu(this);
171 menu_modes_->addActions(group_modes_->actions());
172 act_modes_->setMenu(menu_modes_);
173
174 toolbar_ = new QToolBar(tr("Whiteboard toolbar"), this);
175 toolbar_->setIconSize(QSize(16,16));
176 toolbar_->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Minimum));
177 toolbar_->addAction(act_end_);
178 toolbar_->addAction(act_clear_);
179 toolbar_->addAction(act_save_);
180 toolbar_->addAction(act_geometry_);
181 toolbar_->addWidget(new StretchWidget(this));
182 toolbar_->addAction(act_fill_);
183 toolbar_->addAction(act_color_);
184 QToolButton *bw = new QToolButton;
185 bw->setIcon(IconsetFactory::icon("psi/drawPaths").icon());
186 bw->setMenu(menu_widths_);
187 bw->setPopupMode(QToolButton::InstantPopup);
188 toolbar_->addWidget(bw);
189 QToolButton *bm = new QToolButton;
190 bm->setIcon(IconsetFactory::icon("psi/select").icon());
191 bm->setMenu(menu_modes_);
192 bm->setPopupMode(QToolButton::InstantPopup);
193 toolbar_->addWidget(bm);
194 vb1->addWidget(toolbar_);
195
196 // Context menu
197 pm_settings_ = new QMenu(this);
198 connect(pm_settings_, SIGNAL(aboutToShow()), SLOT(buildMenu()));
199
200 X11WM_CLASS("whiteboard");
201
202 // set the Jid -> le_jid.
203 le_jid_->setText(QString("%1 (session: %2)").arg(session->target().full()).arg(session->session()));
204 le_jid_->setCursorPosition(0);
205 le_jid_->setToolTip(session->target().full());
206
207 // update the widget icon
208 #ifndef Q_OS_MAC
209 setWindowIcon(IconsetFactory::icon("psi/whiteboard").icon());
210 #endif
211
212 setWindowOpacity(double(qMax(MINIMUM_OPACITY, PsiOptions::instance()->getOption("options.ui.chat.opacity").toInt())) / 100);
213
214 setGeometryOptionPath(geometryOption);
215 }
216
~WbDlg()217 WbDlg::~WbDlg() {
218 qDebug("destruct WbDlg");
219 }
220
session() const221 SxeSession* WbDlg::session() const {
222 return wbWidget_->session();
223 }
224
allowEdits() const225 bool WbDlg::allowEdits() const {
226 return allowEdits_;
227 }
228
setAllowEdits(bool a)229 void WbDlg::setAllowEdits(bool a) {
230 allowEdits_ = a;
231 if(!allowEdits_)
232 wbWidget_->setMode(WbWidget::Scroll);
233 }
234
peerLeftSession(const Jid & peer)235 void WbDlg::peerLeftSession(const Jid &peer) {
236 le_jid_->setText(tr("%1 left (session: %2).").arg(peer.full()).arg(session()->session()));
237 }
238
endSession()239 void WbDlg::endSession() {
240 if(sender() == act_end_) {
241 int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to end the session?\nThe contents of the whiteboard will be lost."), tr("&Yes"), tr("&No"));
242 if(n != 0)
243 return;
244 }
245 // terminate the underlying SXE session
246 session()->endSession();
247
248 emit sessionEnded(this);
249 }
250
activated()251 void WbDlg::activated() {
252 if(pending_ > 0) {
253 pending_ = 0;
254 updateCaption();
255 }
256 doFlash(false);
257 }
258
keyPressEvent(QKeyEvent * e)259 void WbDlg::keyPressEvent(QKeyEvent *e) {
260 if(e->key() == Qt::Key_Escape && !PsiOptions::instance()->getOption("options.ui.tabs.use-tabs").toBool())
261 close();
262 else if(e->key() == Qt::Key_W && e->modifiers() & Qt::ControlModifier && !PsiOptions::instance()->getOption("options.ui.tabs.use-tabs").toBool())
263 close();
264 else
265 e->ignore();
266 }
267
closeEvent(QCloseEvent * e)268 void WbDlg::closeEvent(QCloseEvent *e) {
269 e->accept();
270 if(keepOpen_) {
271 int n = QMessageBox::information(this, tr("Warning"), tr("A new whiteboard message was just received.\nDo you still want to close the window?"), tr("&Yes"), tr("&No"));
272 if(n != 0) {
273 e->ignore();
274 return;
275 }
276 }
277
278 // destroy the dialog if delChats is dcClose
279 if(PsiOptions::instance()->getOption("options.ui.chat.delete-contents-after").toString() == "instant")
280 endSession();
281 else {
282 if(PsiOptions::instance()->getOption("options.ui.chat.delete-contents-after").toString() == "hour")
283 setSelfDestruct(60);
284 else if(PsiOptions::instance()->getOption("options.ui.chat.delete-contents-after").toString() == "day")
285 setSelfDestruct(60 * 24);
286 }
287 }
288
showEvent(QShowEvent *)289 void WbDlg::showEvent(QShowEvent *) {
290 setSelfDestruct(0);
291 }
292
changeEvent(QEvent * e)293 void WbDlg::changeEvent(QEvent *e) {
294 if (e->type() == QEvent::ActivationChange && isActiveWindow()) {
295 activated();
296 }
297 e->ignore();
298 }
299
setStrokeColor()300 void WbDlg::setStrokeColor() {
301 QColor newColor = QColorDialog::getColor();
302 if(newColor.isValid()) {
303 QPixmap pixmap(16, 16);
304 pixmap.fill(newColor);
305 act_color_->setIcon(QIcon(pixmap));
306 wbWidget_->setStrokeColor(newColor);
307 }
308 }
309
setFillColor(bool fill)310 void WbDlg::setFillColor(bool fill) {
311 QColor newColor;
312 if(fill) {
313 newColor = QColorDialog::getColor();
314 if(newColor.isValid()) {
315 QPixmap pixmap(16, 16);
316 pixmap.fill(newColor);
317 act_fill_->setIcon(QIcon(pixmap));
318 }
319 }
320 else {
321 newColor = Qt::transparent;
322 }
323 wbWidget_->setFillColor(newColor);
324 }
325
setStrokeWidth(QAction * a)326 void WbDlg::setStrokeWidth(QAction *a) {
327 wbWidget_->setStrokeWidth(a->data().toInt());
328 }
329
setMode(QAction * a)330 void WbDlg::setMode(QAction *a) {
331 if(allowEdits_)
332 wbWidget_->setMode(WbWidget::Mode(a->data().toInt()));
333 else
334 wbWidget_->setMode(WbWidget::Scroll);
335 }
336
setKeepOpenFalse()337 void WbDlg::setKeepOpenFalse() {
338 keepOpen_ = false;
339 }
340
buildMenu()341 void WbDlg::buildMenu()
342 {
343 pm_settings_->clear();
344 pm_settings_->addAction(act_modes_);
345 pm_settings_->addAction(act_widths_);
346 pm_settings_->addAction(act_color_);
347 pm_settings_->addSeparator();
348 pm_settings_->addAction(act_save_);
349 pm_settings_->addAction(act_clear_);
350 pm_settings_->addAction(act_end_);
351 }
352
setGeometry()353 void WbDlg::setGeometry() {
354 // TODO: make a proper dialog
355 QSize size;
356
357 bool ok;
358 size.setWidth(QInputDialog::getInt(this, tr("Set new width:"), tr("Width:"), static_cast<int>(wbWidget_->scene()->sceneRect().width()), 10, 100000, 10, &ok));
359 if(!ok) {
360 return;
361 }
362
363 size.setHeight(QInputDialog::getInt(this, tr("Set new height:"), tr("Height:"), static_cast<int>(wbWidget_->scene()->sceneRect().height()), 10, 100000, 10, &ok));
364 if(!ok)
365 return;
366
367 wbWidget_->setSize(size);
368 }
369
save()370 void WbDlg::save() {
371 QString fileName = QFileDialog::getSaveFileName(this, tr("Save Whitebaord"),
372 QDir::homePath(),
373 tr("Scalable Vector Graphics (*.svg)"));
374 fileName = fileName.trimmed();
375 if(!fileName.endsWith(".svg", Qt::CaseInsensitive))
376 fileName += ".svg";
377
378 QFile file(fileName);
379 if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
380 return;
381
382 QTextStream stream(&file);
383 wbWidget_->session()->document().save(stream, 2);
384
385 file.close();
386 }
387
contextMenuEvent(QContextMenuEvent * e)388 void WbDlg::contextMenuEvent(QContextMenuEvent * e) {
389 pm_settings_->exec(QCursor::pos());
390 e->accept();
391 }
392
setSelfDestruct(int minutes)393 void WbDlg::setSelfDestruct(int minutes) {
394 if(minutes <= 0) {
395 if(selfDestruct_) {
396 delete selfDestruct_;
397 selfDestruct_ = 0;
398 }
399 return;
400 }
401
402 if(!selfDestruct_) {
403 selfDestruct_ = new QTimer(this);
404 connect(selfDestruct_, SIGNAL(timeout()), SLOT(endSession()));
405 }
406
407 selfDestruct_->start(minutes * 60000);
408 }
409
updateCaption()410 void WbDlg::updateCaption() {
411 QString cap = "";
412
413 if(pending_ > 0) {
414 cap += "* ";
415 if(pending_ > 1)
416 cap += QString("[%1] ").arg(pending_);
417 }
418 cap += session()->target().full();
419
420 setWindowTitle(cap);
421 }
422