1 /*
2 	Copyright 2006-2019 The QElectroTech Team
3 	This file is part of QElectroTech.
4 
5 	QElectroTech is free software: you can redistribute it and/or modify
6 	it under the terms of the GNU General Public License as published by
7 	the Free Software Foundation, either version 2 of the License, or
8 	(at your option) any later version.
9 
10 	QElectroTech 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
13 	GNU General Public License for more details.
14 
15 	You should have received a copy of the GNU General Public License
16 	along with QElectroTech.  If not, see <http://www.gnu.org/licenses/>.
17 */
18 #include "linksingleelementwidget.h"
19 #include "ui_linksingleelementwidget.h"
20 #include "diagram.h"
21 #include "elementprovider.h"
22 #include "linkelementcommand.h"
23 #include "diagramposition.h"
24 #include "conductor.h"
25 
26 #include <QTreeWidgetItem>
27 
28 /**
29  * @brief LinkSingleElementWidget::LinkSingleElementWidget
30  * Default constructor
31  * @param elmt
32  * the edited element
33  * @param parent
34  * the parent widget
35  */
LinkSingleElementWidget(Element * elmt,QWidget * parent)36 LinkSingleElementWidget::LinkSingleElementWidget(Element *elmt, QWidget *parent) :
37 	AbstractElementPropertiesEditorWidget(parent),
38 	ui(new Ui::LinkSingleElementWidget)
39 {
40 	ui->setupUi(this);
41 
42 	ui->m_tree_widget->setContextMenuPolicy(Qt::CustomContextMenu);
43 	m_context_menu  = new QMenu(this);
44 	m_link_action   = new QAction(tr("Lier l'élément"), this);
45 	m_show_qtwi     = new QAction(tr("Montrer l'élément"), this);
46 	m_show_element  = new QAction(tr("Montrer l'élément esclave"), this);
47 	m_save_header_state = new QAction(tr("Enregistrer la disposition"), this);
48 
49 	connect(m_show_qtwi, &QAction::triggered, [this]() {this->on_m_tree_widget_itemDoubleClicked(this->m_qtwi_at_context_menu, 0);});
50 	connect(m_link_action, &QAction::triggered, this, &LinkSingleElementWidget::linkTriggered);
51 
52 	connect(m_show_element,  &QAction::triggered, [this]()
53 	{
54 		this->m_element->diagram()->showMe();
55 		this->m_element->setHighlighted(true);
56 		if(this->m_showed_element)
57 			m_showed_element->setHighlighted(false);
58 	});
59 
60 	QHeaderView *qhv = ui->m_tree_widget->header();
61 	qhv->setContextMenuPolicy(Qt::CustomContextMenu);
62 	connect(qhv, &QHeaderView::customContextMenuRequested, this, &LinkSingleElementWidget::headerCustomContextMenuRequested);
63 	connect(m_save_header_state, &QAction::triggered, [this, qhv]()
64 	{
65 		QByteArray qba = qhv->saveState();
66 		QSettings settings;
67 
68 		if (this->m_element->linkType() & Element::AllReport)
69 			settings.setValue("link-element-widget/report-state", qba);
70 		else if (this->m_element->linkType() == Element::Slave)
71 			settings.setValue("link-element-widget/slave-state", qba);
72 	});
73 
74 	setElement(elmt);
75 }
76 
77 /**
78  * @brief LinkSingleElementWidget::~LinkSingleElementWidget
79  * Default destructor
80  */
~LinkSingleElementWidget()81 LinkSingleElementWidget::~LinkSingleElementWidget()
82 {
83 	if(m_showed_element)
84 		m_showed_element->setHighlighted(false);
85 
86 	if(m_element)
87 	{
88 		m_element->setHighlighted(false);
89 		if (!m_element->isFree())
90 			m_element->linkedElements().first()->setHighlighted(false);
91 	}
92 	delete ui;
93 }
94 
95 /**
96  * @brief LinkSingleElementWidget::setElement
97  * Set element to be the edited element.
98  * @param element
99  */
setElement(Element * element)100 void LinkSingleElementWidget::setElement(Element *element)
101 {
102 	if (m_element == element)
103 		return;
104 
105 		//Remove connection of previous edited element
106 	if (m_element)
107 	{
108 		disconnect(m_element->diagram()->project(), &QETProject::diagramRemoved, this, &LinkSingleElementWidget::diagramWasRemovedFromProject);
109 		disconnect(m_element.data(), &Element::linkedElementChanged, this, &LinkSingleElementWidget::updateUi);
110 		m_element->setHighlighted(false);
111 	}
112 
113 	if(m_showed_element)
114 		m_showed_element->setHighlighted(false);
115 
116 	m_unlink = false;
117 	m_showed_element = nullptr;
118 	m_element_to_link = nullptr;
119 	m_pending_qtwi = nullptr;
120 
121 		//Setup the new element, connection and ui
122 	m_element = element;
123 
124 	if (m_element->linkType() & Element::Slave)
125 		m_filter = Element::Master;
126 	else if (m_element->linkType() & Element::AllReport)
127 		m_filter = m_element->linkType() == Element::NextReport? Element::PreviousReport : Element::NextReport;
128 	else
129 		m_filter = Element::Simple;
130 
131 	connect(m_element->diagram()->project(), &QETProject::diagramRemoved, this, &LinkSingleElementWidget::diagramWasRemovedFromProject);
132 	connect(m_element.data(), &Element::linkedElementChanged, this, &LinkSingleElementWidget::updateUi, Qt::QueuedConnection);
133 
134 	updateUi();
135 }
136 
137 /**
138  * @brief LinkSingleElementWidget::apply
139  * Apply the new property of the edited element by pushing
140  * the associated undo command to parent project undo stack
141  */
apply()142 void LinkSingleElementWidget::apply()
143 {
144 	QUndoCommand *undo = associatedUndo();
145 	if (undo)
146 		m_element->diagram()->undoStack().push(undo);
147 
148 	m_unlink = false;
149 	m_element_to_link = nullptr;
150 	m_pending_qtwi = nullptr;
151 }
152 
153 /**
154  * @brief LinkSingleElementWidget::associatedUndo
155  * @return the undo command associated to the current edition
156  * if there isn't change, return nulptr
157  */
associatedUndo() const158 QUndoCommand *LinkSingleElementWidget::associatedUndo() const
159 {
160 	LinkElementCommand *undo = new LinkElementCommand(m_element);
161 
162 	if (m_element_to_link || m_unlink)
163 	{
164 		if (m_element_to_link)
165 			undo->setLink(m_element_to_link);
166 		else if (m_unlink)
167 			undo->unlinkAll();
168 
169 		return undo;
170 	}
171 
172 	return nullptr;
173 }
174 
175 /**
176  * @brief LinkSingleElementWidget::title
177  * @return the title used for this editor
178  */
title() const179 QString LinkSingleElementWidget::title() const
180 {
181 	if (m_element->linkType() & Element::AllReport)
182 		return tr("Report de folio");
183 	else
184 		return tr("Référence croisée (esclave)");
185 }
186 
187 /**
188  * @brief LinkSingleElementWidget::updateUi
189  * Update the content of this widget
190  */
updateUi()191 void LinkSingleElementWidget::updateUi()
192 {
193 	m_unlink = false;
194 
195 		//Update the behavior of link/unlink button
196 	if (m_element->isFree())
197 		hideButtons();
198 	else
199 		showButtons();
200 
201 	buildTree();
202 }
203 
204 /**
205  * @brief LinkSingleElementWidget::buildTree
206  * Build the content of the QTreeWidget
207  */
buildTree()208 void LinkSingleElementWidget::buildTree()
209 {
210 	clearTreeWidget();
211 	setUpHeaderLabels();
212 	QSettings settings;
213 
214 	const QList <Element *> elmt_list = availableElements();
215 	if (m_element->linkType() == Element::Slave)
216 	{
217 
218 		for(Element *elmt : elmt_list)
219 		{
220 			QStringList search_list;
221 			QStringList str_list;
222 
223 			QString formula = elmt->elementInformations()["formula"].toString();
224 			if(!formula.isEmpty())
225 			{
226 				str_list << autonum::AssignVariables::formulaToLabel(formula, elmt->rSequenceStruct(), elmt->diagram(), elmt);
227 				search_list << str_list.last();
228 			}
229 			else
230 			{
231 				str_list << elmt->elementInformations()["label"].toString();
232 				if(!str_list.last().isEmpty())
233 					search_list << str_list.last();
234 			}
235 
236 			str_list << elmt->elementInformations()["comment"].toString();
237 			if (!str_list.last().isEmpty())
238 				search_list << str_list.last();
239 
240 			if (Diagram *diag = elmt->diagram())
241 			{
242 				if (settings.value("genericpanel/folio", false).toBool())
243 				{
244 					autonum::sequentialNumbers seq;
245 					QString F =autonum::AssignVariables::formulaToLabel(diag->border_and_titleblock.folio(), seq, diag, elmt);
246 					str_list << F;
247 				}
248 				else
249 				{
250 					str_list << QString::number(diag->folioIndex() + 1);
251 				}
252 				str_list << diag->convertPosition(elmt->scenePos()).toString();
253 				str_list << diag->title();
254 			}
255 			else
256 			{
257 				qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram";
258 			}
259 
260 			QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list);
261 			m_qtwi_elmt_hash.insert(qtwi, elmt);
262 			m_qtwi_strl_hash.insert(qtwi, search_list);
263 		}
264 
265 
266 		QVariant v = settings.value("link-element-widget/slave-state");
267 		if(!v.isNull())
268 			ui->m_tree_widget->header()->restoreState(v.toByteArray());
269 	}
270 
271 	else if (m_element->linkType() & Element::AllReport)
272 	{
273 		for(Element *elmt : elmt_list)
274 		{
275 			QStringList search_list;
276 			QStringList str_list;
277 
278 			if (elmt->conductors().size())
279 			{
280 				ConductorProperties cp = elmt->conductors().first()->properties();
281 				str_list << cp.text;
282 				if (!str_list.last().isEmpty())
283 					search_list << str_list.last();
284 				str_list << cp.m_function;
285 				if (!str_list.last().isEmpty())
286 					search_list << str_list.last();
287 				str_list << cp.m_tension_protocol;
288 				if (!str_list.last().isEmpty())
289 					search_list << str_list.last();
290 			}
291 			else
292 				str_list << "" << "" << "";
293 
294 			if (Diagram *diag = elmt->diagram())
295 			{
296 				if (settings.value("genericpanel/folio", false).toBool())
297 				{
298 					autonum::sequentialNumbers seq;
299 					QString F =autonum::AssignVariables::formulaToLabel(diag->border_and_titleblock.folio(), seq, diag, elmt);
300 					str_list << F;
301 				}
302 				else
303 				{
304 					str_list << QString::number(diag->folioIndex() + 1);
305 				}
306 				str_list << diag->convertPosition(elmt->scenePos()).toString();
307 				str_list << diag->title();
308 			}
309 			else
310 			{
311 				qDebug() << "In method void LinkSingleElementWidget::updateUi(), provided element must be in a diagram";
312 			}
313 
314 			QTreeWidgetItem *qtwi = new QTreeWidgetItem(ui->m_tree_widget, str_list);
315 			m_qtwi_elmt_hash.insert(qtwi, elmt);
316 			m_qtwi_strl_hash.insert(qtwi, search_list);
317 		}
318 
319 		QSettings settings;
320 		QVariant v = settings.value("link-element-widget/report-state");
321 		if(!v.isNull())
322 			ui->m_tree_widget->header()->restoreState(v.toByteArray());
323 	}
324 
325 	setUpCompleter();
326 }
327 
328 /**
329  * @brief LinkSingleElementWidget::setLiveEdit
330  * @param live_edit
331  * @return
332  */
setLiveEdit(bool live_edit)333 bool LinkSingleElementWidget::setLiveEdit(bool live_edit)
334 {
335 	if (m_live_edit == live_edit)
336 		return true;
337 
338 	m_live_edit = live_edit;
339 
340 	return true;
341 }
342 
343 /**
344  * @brief LinkSingleElementWidget::availableElements
345  * @return A QList with all available element
346  * to be linked with the edited element.
347  * This methode take care of the combo box "find in diagram"
348  */
availableElements()349 QList <Element *> LinkSingleElementWidget::availableElements()
350 {
351 	QList <Element *> elmt_list;
352 		//if element isn't free and unlink isn't pressed, return an empty list
353 	if (!m_element->isFree() && !m_unlink)
354 		return elmt_list;
355 
356 	if (!m_element->diagram() || !m_element->diagram()->project()) return elmt_list;
357 
358 	ElementProvider ep(m_element->diagram()->project());
359 	if (m_filter & Element::AllReport)
360 		elmt_list = ep.freeElement(m_filter);
361 	else
362 		elmt_list = ep.find(m_filter);
363 
364 		//If element is linked, remove is parent from the list
365 	if(!m_element->isFree()) elmt_list.removeAll(m_element->linkedElements().first());
366 
367 	return elmt_list;
368 }
369 
370 /**
371  * @brief LinkSingleElementWidget::setUpCompleter
372  * Setup the completer of search_field
373  */
setUpCompleter()374 void LinkSingleElementWidget::setUpCompleter()
375 {
376 	ui->m_search_field->clear();
377 	if(ui->m_search_field->completer())
378 		delete ui->m_search_field->completer();
379 
380 	QStringList search;
381 	foreach(QStringList strl , m_qtwi_strl_hash.values())
382 		search.append(strl);
383 
384 	QCompleter *c = new QCompleter(search, ui->m_search_field);
385 	c->setCaseSensitivity(Qt::CaseInsensitive);
386 	ui->m_search_field->setCompleter(c);
387 }
388 
389 /**
390  * @brief LinkSingleElementWidget::clearTreeWidget
391  * Clear the tree widget.
392  * Delete all QTreeWidget (in the tree widget and in the hash).
393  * Clear the hash.
394  */
clearTreeWidget()395 void LinkSingleElementWidget::clearTreeWidget()
396 {
397 	while(ui->m_tree_widget->topLevelItemCount())
398 	{
399 		QTreeWidgetItem *qtwi = ui->m_tree_widget->takeTopLevelItem(0);
400 		if (!m_qtwi_elmt_hash.contains(qtwi))
401 			delete qtwi;
402 	}
403 
404 	foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys())
405 		delete qtwi;
406 
407 	m_qtwi_elmt_hash.clear();
408 	m_qtwi_strl_hash.clear();
409 }
410 
setUpHeaderLabels()411 void LinkSingleElementWidget::setUpHeaderLabels()
412 {
413 	QStringList list;
414 	QSettings settings;
415 
416 	if (m_element->linkType() == Element::Slave)
417 	{
418 		if (settings.value("genericpanel/folio", false).toBool())
419 		{
420 			list << tr("Label") << tr("Commentaire") << tr("Label de folio") << tr("Position") << tr("Titre de folio");
421 		}
422 		else
423 		{
424 			list << tr("Label") << tr("Commentaire") << tr("N° de folio") << tr("Position") << tr("Titre de folio");
425 		}
426 	}
427 
428 	if (m_element->linkType() & Element::AllReport)
429 	{
430 		if (settings.value("genericpanel/folio", false).toBool())
431 		{
432 			list << tr("N° de fil") << tr("Fonction") << tr("Tension / Protocole") << tr("Label de folio") << tr("Position") << tr("Titre de folio");
433 		}
434 		else
435 		{
436 			list << tr("N° de fil") << tr("Fonction") << tr("Tension / Protocole") << tr("N° de folio") << tr("Position") << tr("Titre de folio");
437 		}
438 	}
439 
440 	ui->m_tree_widget->setHeaderLabels(list);
441 }
442 
443 /**
444  * @brief LinkSingleElementWidget::diagramWasRemovedFromProject
445  *  * This slot is called when a diagram is removed from the parent project of edited element
446  * to update the content of this widget
447  */
diagramWasRemovedFromProject()448 void LinkSingleElementWidget::diagramWasRemovedFromProject()
449 {
450 		//We use a timer because if the removed diagram contain the master element linked to the edited element
451 		//we must to wait for this elements be unlinked, else the list of available master isn't up to date
452 	QTimer::singleShot(10, this, SLOT(updateUi()));
453 }
454 
showedElementWasDeleted()455 void LinkSingleElementWidget::showedElementWasDeleted()
456 {
457 	m_showed_element = nullptr;
458 }
459 
460 /**
461  * @brief LinkSingleElementWidget::linkTriggered
462  * Action linkis triggered
463  */
linkTriggered()464 void LinkSingleElementWidget::linkTriggered()
465 {
466 	if(!m_qtwi_at_context_menu)
467 		return;
468 
469 	m_element_to_link = m_qtwi_elmt_hash.value(m_qtwi_at_context_menu);
470 
471 	if(m_live_edit)
472 	{
473 		apply();
474 		updateUi();
475 	}
476 	else
477 	{
478 			//In no live edit mode, we set the background of the qtwi green, to inform the user
479 			//which element will be linked when he press the apply button
480 		if (m_pending_qtwi)
481 		{
482 			QBrush brush(Qt::white, Qt::NoBrush);
483 			for(int i=0 ; i<6 ; i++)
484 			{
485 				m_pending_qtwi->setBackground(i,brush);
486 			}
487 		}
488 
489 		for (int i=0 ; i<6 ; i++)
490 		{
491 			m_qtwi_at_context_menu->setBackgroundColor(i, Qt::green);
492 		}
493 		m_pending_qtwi = m_qtwi_at_context_menu;
494 	}
495 
496 }
497 
498 /**
499  * @brief LinkSingleElementWidget::hideButtons
500  * Hide the button displayed when element is already linked
501  */
hideButtons()502 void LinkSingleElementWidget::hideButtons()
503 {
504 	ui->m_label->hide();
505 	ui->m_unlink_pb->hide();
506 	ui->m_show_linked_pb->hide();
507 	ui->m_show_this_pb->hide();
508 	ui->m_search_field->show();
509 }
510 
511 /**
512  * @brief LinkSingleElementWidget::showButtons
513  * Show the button displayed when element is already linked
514  */
showButtons()515 void LinkSingleElementWidget::showButtons()
516 {
517 	ui->m_label->show();
518 	ui->m_unlink_pb->show();
519 	ui->m_show_linked_pb->show();
520 	ui->m_show_this_pb->show();
521 	ui->m_search_field->hide();
522 }
523 
headerCustomContextMenuRequested(const QPoint & pos)524 void LinkSingleElementWidget::headerCustomContextMenuRequested(const QPoint &pos)
525 {
526 	m_context_menu->clear();
527 	m_context_menu->addAction(m_save_header_state);
528 	m_context_menu->popup(ui->m_tree_widget->header()->mapToGlobal(pos));
529 }
530 
on_m_unlink_pb_clicked()531 void LinkSingleElementWidget::on_m_unlink_pb_clicked()
532 {
533 	m_unlink = true;
534 
535 	if(m_live_edit)
536 	{
537 		apply();
538 		updateUi();
539 	}
540 	else
541 		buildTree();
542 }
543 
544 /**
545  * @brief LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked
546  * Highlight the element represented by @item
547  * @param item
548  * @param column
549  */
on_m_tree_widget_itemDoubleClicked(QTreeWidgetItem * item,int column)550 void LinkSingleElementWidget::on_m_tree_widget_itemDoubleClicked(QTreeWidgetItem *item, int column)
551 {
552 	Q_UNUSED(column);
553 
554 	if (m_showed_element)
555 	{
556 		disconnect(m_showed_element, SIGNAL(destroyed()), this, SLOT(showedElementWasDeleted()));
557 		m_showed_element->setHighlighted(false);
558 	}
559 
560 	Element *elmt = m_qtwi_elmt_hash.value(item);
561 	elmt->diagram()->showMe();
562 	elmt->setHighlighted(true);
563 	m_showed_element = elmt;
564 	connect(m_showed_element, SIGNAL(destroyed()), this, SLOT(showedElementWasDeleted()));
565 
566 }
567 
on_m_tree_widget_customContextMenuRequested(const QPoint & pos)568 void LinkSingleElementWidget::on_m_tree_widget_customContextMenuRequested(const QPoint &pos)
569 {
570 		//add the size of the header to display the topleft of the QMenu at the position of the mouse.
571 		//See doc about QWidget::customContextMenuRequested section related to QAbstractScrollArea
572 	QPoint point = pos;
573 	point.ry()+=ui->m_tree_widget->header()->height();
574 	point = ui->m_tree_widget->mapToGlobal(point);
575 
576 	m_context_menu->clear();
577 
578 	if (ui->m_tree_widget->currentItem())
579 	{
580 		m_qtwi_at_context_menu = ui->m_tree_widget->currentItem();
581 		m_context_menu->addAction(m_link_action);
582 		m_context_menu->addAction(m_show_qtwi);
583 	}
584 
585 	m_context_menu->addAction(m_show_element);
586 	m_context_menu->popup(point);
587 }
588 
on_m_show_linked_pb_clicked()589 void LinkSingleElementWidget::on_m_show_linked_pb_clicked()
590 {
591 	if (!m_element->isFree())
592 	{
593 		Element *elmt = m_element->linkedElements().first();
594 		elmt->diagram()->showMe();
595 		elmt->setHighlighted(true);
596 	}
597 }
598 
on_m_show_this_pb_clicked()599 void LinkSingleElementWidget::on_m_show_this_pb_clicked()
600 {
601 	m_show_element->trigger();
602 }
603 
604 /**
605  * @brief LinkSingleElementWidget::on_m_search_field_textEdited
606  * Search all items which match with @arg1 and shows it, other items is hidden.
607  * If @arg1 is empty, show all items.
608  * @param arg1
609  */
on_m_search_field_textEdited(const QString & arg1)610 void LinkSingleElementWidget::on_m_search_field_textEdited(const QString &arg1)
611 {
612 		//Show all items if arg1 is empty, if not hide all items
613 	foreach(QTreeWidgetItem *qtwi, m_qtwi_elmt_hash.keys())
614 		qtwi->setHidden(!arg1.isEmpty());
615 
616 	QList <QTreeWidgetItem *> qtwi_list;
617 
618 	foreach(QTreeWidgetItem *qtwi, m_qtwi_strl_hash.keys())
619 	{
620 		foreach(QString str, m_qtwi_strl_hash.value(qtwi))
621 		{
622 			if(str.contains(arg1, Qt::CaseInsensitive))
623 			{
624 				qtwi_list << qtwi;
625 				continue;
626 			}
627 		}
628 	}
629 
630 		//Show items which match with arg1
631 	foreach(QTreeWidgetItem *qtwi, qtwi_list)
632 		qtwi->setHidden(false);
633 }
634