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