WSL/SLF GitLab Repository

Checklist.cc 9.62 KB
Newer Older
1
2
3
4
5
/*****************************************************************************/
/*  Copyright 2019 WSL Institute for Snow and Avalanche Research  SLF-DAVOS  */
/*****************************************************************************/
/* This file is part of INIshell.
   INIshell 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
8
9
10
11
12
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   INIshell is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
   GNU General Public License for more details.
14

15
16
   You should have received a copy of the GNU General Public License
   along with INIshell.  If not, see <http://www.gnu.org/licenses/>.
17
*/
18
19
20

#include "Checklist.h"
#include "Label.h"
21
#include "src/main/colors.h"
22
#include "src/main/common.h"
23
24
#include "src/main/inishell.h"

25
26
#include <QStringList>

27
28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * @class Checklist
 * @brief Default constructor for a Checklist panel.
 * @details A Checklist shows a checkable list where each list item can have arbitrary children
 * which are displayed below if the item is checked and hidden if it's not.
 * @param[in] section INI section the controlled value belongs to.
 * @param[in] key INI key corresponding to the value that is being controlled by this Checklist.
 * @param[in] options XML node responsible for this panel with all options and children.
 * @param[in] no_spacers Keep a tight layout for this panel.
 * @param[in] parent The parent widget.
 */
Checklist::Checklist(const QString &section, const QString &key, const QDomNode &options, const bool &no_spacers,
    QWidget *parent) : Atomic(section, key, parent)
{
41
	/* label, list, and container for child widgets */
42
	auto *key_label( new Label(QString(), QString(), options, no_spacers, key_, this) );
43
	list_ = new QListWidget;
44
	setEmphasisWidget(key_label->label_);
45
	connect(list_, &QListWidget::itemClicked, this, &Checklist::listClick);
46
	child_container_ = new Group(QString(), QString(), false, false, false, true); //tight layout
47
	child_container_->setVisible(false); //container for children activates only when clicked (saves space)
48
49

	/* layout of the basic elements */
50
51
	checklist_layout_ = new QVBoxLayout;
	checklist_layout_->setContentsMargins(0, 0, 0, 0);
52
	checklist_layout_->addWidget(list_);
53
	checklist_layout_->addWidget(child_container_);
54

55
	/* layout of basic elements plus checklist */
Mathias Bavay's avatar
Mathias Bavay committed
56
	auto *layout( new QHBoxLayout );
57
	setLayoutMargins(layout);
58
	layout->addWidget(key_label);
59
	layout->addLayout(checklist_layout_);
60
61
	if (!no_spacers)
		layout->addSpacerItem(buildSpacer());
62
63
	main_help_ = addHelp(layout, options, no_spacers, true); //force to add a Helptext for access by subitems
	main_help_->setProperty("main_help", options.firstChildElement("help").text());
64
65
66
	this->setLayout(layout);

	setOptions(options); //fill list items
67
68
69

	if (main_help_->property("main_help").isNull() && !has_child_helptexts_)
		main_help_->hide(); //no main help and no child panel help --> save space
70
71
72
73
74
75
}

/**
 * @brief Parse options for a Checklist from XML.
 * @param[in] options XML node holding the Checklist.
 */
76
void Checklist::setOptions(const QDomNode &options)
77
{
78
	QStringList item_strings;
79
80
81
	for (QDomElement op = options.firstChildElement(); !op.isNull(); op = op.nextSiblingElement()) {
		if (op.tagName() != "option" && op.tagName() != "o")
			continue; //short <o></o> notation possible
82
83
		if (!hasSectionSpecified(section_, op)) //option is excluded for this section
			continue;
84
		substituteKeys(op, "@", key_);
85
		//add the option value as list item:
Mathias Bavay's avatar
Mathias Bavay committed
86
87
		const QString value( op.attribute("value") );
		auto *item( new QListWidgetItem(value, list_) );
88
89

		/* set item properties */
90
		item_strings.push_back(op.attribute("value"));
91
		item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable); //not checkable because we do it on row click
92
		item->setCheckState(Qt::Unchecked);
93
		item->setToolTip(op.attribute("help"));
94
95
96
97
		if (!op.firstChildElement("help").isNull()) {
			item->setData(Qt::UserRole, op.firstChildElement("help").text());
			has_child_helptexts_ = true;
		}
98
99
100
		if (!op.attribute("color").isEmpty())
			item->setForeground(colors::getQColor(op.attribute("color")));
		item->setFont(setFontOptions(item->font(), op));
101

102
		//group all elements of this option together with a tight layout:
Mathias Bavay's avatar
Mathias Bavay committed
103
		auto *item_group( new Group(section_, "_checklist_itemgroup_" + key_, false, false, false, true) );
104
105
		//construct the children of this option:
		recursiveBuild(op, item_group, section_);
106
		child_container_->addWidget(item_group);
107
		item_group->setVisible(false); //becomes visible when checked
108

109
		if (op.attribute("default").toLower() == "true") {
Mathias Bavay's avatar
Mathias Bavay committed
110
			const QString def_val( this->property("default_value").toString() );
111
112
113
114
			this->setProperty("default_value", (def_val.isEmpty()? "" : def_val + " ") + value);
			list_->setCurrentRow(list_->count() - 1);
			emit listClick(list_->item(list_->count() - 1)); //to set the default value
		}
115
	}
116
117

	if (list_->count() == 0) {
118
		topLog(QString(tr(R"(Invalid XML syntax for Checklist panel "%1::%2": no checkable options set.)").arg(
119
		    section_, key_)), "error");
120
		list_->setVisible(false);
121
		return;
122
123
	}

124
	//this many rows should be visible (with some padding):
125
	list_->setFixedHeight(list_->sizeHintForRow(0) * qMin(Cst::nr_items_visible, list_->count()) +
126
	    Cst::checklist_safety_padding_vertical);
Michael Reisecker's avatar
Michael Reisecker committed
127
	list_->setMinimumWidth(getElementTextWidth(item_strings, Cst::tiny, Cst::width_checklist_max) +
128
	    Cst::checklist_safety_padding_horizontal);
Michael Reisecker's avatar
Michael Reisecker committed
129
	list_->setMaximumWidth(Cst::width_checklist_max);
130
131
132
}

/**
133
134
 * @brief Collect Checklist texts in the order they were checked in.
 * @return INI value with ordered texts.
135
 */
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
QString Checklist::getOrderedIniList() const
{
	QString list_values;
	for (auto &it : ordered_item_list_)
		list_values += it->text() + " "; //collect values as "value1 value2 value3..."
	list_values.chop(1); //trailing space (if available)
	return list_values;
}

/**
 * @brief Set the child containers' visibilities.
 * @param[in] index Index of the checkbox that is being clicked.
 * @param[in] checked State of the checkbox that is being clicked.
 */
void Checklist::setChildVisibility(QListWidgetItem *item)
151
{
Mathias Bavay's avatar
Mathias Bavay committed
152
153
	const QLayout *group_layout( child_container_->getLayout() );
	auto *item_group( static_cast<Group *>(group_layout->itemAt(list_->currentRow())->widget()) );
154
155

	bool one_visible = false;
156
	bool all_unchecked = true;
157
	for (int ii = 0; ii < list_->count(); ++ii) {
158
		if (list_->item(ii)->checkState() == Qt::Checked) {
159
			all_unchecked = false;
160
			if (!static_cast<Group *>(group_layout->itemAt(ii)->widget())->isEmpty()) {
161
				one_visible = true; //hide container if empty (a few blank pixels)
162
163
				break;
			}
164
165
		}
	}
166
	child_container_->setVisible(one_visible);
167
	if (all_unchecked) //switch back to main help:
168
169
		main_help_->updateText(main_help_->property("main_help").toString());

170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
	item_group->setVisible(item->checkState() == Qt::Checked && !item_group->isEmpty());
}

/**
 * @brief Event handler for clicks in the list widget.
 * @details This handles showing and hiding children belonging to list items.
 * @param[in] item Item that was clicked.
 */
void Checklist::listClick(QListWidgetItem *item)
{
	//click in line is enough to check/uncheck:
	if (item->checkState() == Qt::Unchecked) { //now it's CHECKED
		item->setCheckState(Qt::Checked);
		const QString item_help( item->data(Qt::UserRole).toString() );
		main_help_->updateText( //switch main help to item help if available
		    item_help.isEmpty()? main_help_->property("main_help").toString() : item_help);
		ordered_item_list_.push_back(item); //remember checking order
	} else { //now it's UNCHECKED
		setUpdatesEnabled(false); //we only experience flickering when hiding
		item->setCheckState(Qt::Unchecked);
		auto erase_it( std::find(ordered_item_list_.begin(), ordered_item_list_.end(), item) );
		if (erase_it != ordered_item_list_.end())
			ordered_item_list_.erase(erase_it);
	}
	setChildVisibility(item);

	const QString list_values( getOrderedIniList() );
197
	setDefaultPanelStyles(list_values);
198
	setIniValue(list_values);
199
	this->setToolTip(key_ + " = " + list_values); //show sorting to the user
200
	emphasis_widget_->setToolTip(this->toolTip());
201
	setBufferedUpdatesEnabled();
202
203
}

204
205
206
207
208
/**
 * @brief Event listener for changed INI values.
 * @details The "ini_value" property is set when parsing default values and potentially again
 * when setting INI keys while parsing a file.
 */
209
210
void Checklist::onPropertySet()
{
Mathias Bavay's avatar
Mathias Bavay committed
211
	const QString values( this->property("ini_value").toString() );
212
213
	if (ini_value_ == values)
		return;
Mathias Bavay's avatar
Mathias Bavay committed
214
	const QStringList value_list( values.split(QRegExp("\\s+"), QString::SkipEmptyParts) );
215

216
217
	/* clear the list, overwriting current settings */
	for (int ii = 0; ii < list_->count(); ++ii) {
218
219
		if (list_->item(ii)->checkState() == Qt::Checked) {
			list_->setCurrentRow(ii);
220
			emit listClick(list_->item(ii)); //unload child panels
221
222
223
		}
	}

224
225
	/* check all items found in the given value */
	for (auto &val : value_list) {
226
		//Note: since we find and click the checkbox for each value they are correctly inserted into the ordering
227
228
		bool found_option_to_set = false;
		for (int jj = 0; jj < list_->count(); ++jj) {
229
			if (QString::compare(list_->item(jj)->text(), val, Qt::CaseInsensitive) == 0) {
230
				list_->setCurrentRow(jj); //else we will get NULL when we fetch the item
231
				emit listClick(list_->item(jj)); //to set the default value
232
233
234
				found_option_to_set = true;
				break;
			}
235
		} //endfor jj
236
		if (!found_option_to_set)
237
			topLog(tr(R"(Checklist item "%1" could not be set from INI file for key "%2::%3": no such option specified in XML file)").arg(
238
			    val, section_, key_), "warning");
239
	} //endfor ii
240
}