WSL/SLF GitLab Repository

Checklist.cc 9.86 KB
Newer Older
Mathias Bavay's avatar
Mathias Bavay committed
1
//SPDX-License-Identifier: GPL-3.0-or-later
2
3
4
5
6
/*****************************************************************************/
/*  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
7
   it under the terms of the GNU General Public License as published by
8
9
10
11
12
13
   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
14
   GNU General Public License for more details.
15

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

20
21
22
23
24
25
#include <src/panels/Checklist.h>
#include <src/panels/Label.h>
#include <src/main/colors.h>
#include <src/main/common.h>
#include <src/main/inishell.h>
#include <src/panels/dynamic_panels.h>
26

27
28
#include <QStringList>

Mathias Bavay's avatar
Mathias Bavay committed
29
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
30
31
32
33
34
35
namespace Qt
{
    static auto SkipEmptyParts = QString::SkipEmptyParts;
}
#endif

36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * @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)
{
50
	/* label, list, and container for child widgets */
51
	auto *key_label( new Label(QString(), QString(), options, no_spacers, key_, this) );
52
	list_ = new QListWidget;
53
	setEmphasisWidget(key_label->label_);
54
	connect(list_, &QListWidget::itemClicked, this, &Checklist::listClick);
55
	child_container_ = new Group(QString(), QString(), false, false, false, true); //tight layout
56
	child_container_->setVisible(false); //container for children activates only when clicked (saves space)
57
58

	/* layout of the basic elements */
59
60
	checklist_layout_ = new QVBoxLayout;
	checklist_layout_->setContentsMargins(0, 0, 0, 0);
61
	checklist_layout_->addWidget(list_);
62
	checklist_layout_->addWidget(child_container_);
63

64
	/* layout of basic elements plus checklist */
Mathias Bavay's avatar
Mathias Bavay committed
65
	auto *layout( new QHBoxLayout );
66
	setLayoutMargins(layout);
67
	layout->addWidget(key_label);
68
	layout->addLayout(checklist_layout_);
69
70
	if (!no_spacers)
		layout->addSpacerItem(buildSpacer());
71
72
	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());
73
74
75
	this->setLayout(layout);

	setOptions(options); //fill list items
76
77
78

	if (main_help_->property("main_help").isNull() && !has_child_helptexts_)
		main_help_->hide(); //no main help and no child panel help --> save space
79
80
81
82
83
84
}

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

		/* set item properties */
99
		item_strings.push_back(op.attribute("value"));
100
		item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable); //not checkable because we do it on row click
101
		item->setCheckState(Qt::Unchecked);
102
		item->setToolTip(op.attribute("help"));
103
104
105
106
		if (!op.firstChildElement("help").isNull()) {
			item->setData(Qt::UserRole, op.firstChildElement("help").text());
			has_child_helptexts_ = true;
		}
107
108
109
		if (!op.attribute("color").isEmpty())
			item->setForeground(colors::getQColor(op.attribute("color")));
		item->setFont(setFontOptions(item->font(), op));
110

111
		//group all elements of this option together with a tight layout:
Mathias Bavay's avatar
Mathias Bavay committed
112
		auto *item_group( new Group(section_, "_checklist_itemgroup_" + key_, false, false, false, true) );
113
114
		//construct the children of this option:
		recursiveBuild(op, item_group, section_);
115
		child_container_->addWidget(item_group);
116
		item_group->setVisible(false); //becomes visible when checked
117

118
		if (op.attribute("default").toLower() == "true") {
Mathias Bavay's avatar
Mathias Bavay committed
119
			const QString def_val( this->property("default_value").toString() );
120
121
122
123
			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
		}
124
	}
125
126

	if (list_->count() == 0) {
127
		topLog(QString(tr(R"(XML error: Invalid XML syntax for Checklist panel "%1::%2": no checkable options set.)").arg(
128
		    section_, key_)), "error");
129
		list_->setVisible(false);
130
		return;
131
132
	}

133
	//this many rows should be visible (with some padding):
134
	list_->setFixedHeight(list_->sizeHintForRow(0) * qMin(Cst::nr_items_visible, list_->count()) +
135
	    Cst::checklist_safety_padding_vertical);
Michael Reisecker's avatar
Michael Reisecker committed
136
	list_->setMinimumWidth(getElementTextWidth(item_strings, Cst::tiny, Cst::width_checklist_max) +
137
	    Cst::checklist_safety_padding_horizontal);
Michael Reisecker's avatar
Michael Reisecker committed
138
	list_->setMaximumWidth(Cst::width_checklist_max);
139
140
141
}

/**
142
143
 * @brief Collect Checklist texts in the order they were checked in.
 * @return INI value with ordered texts.
144
 */
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
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)
160
{
Mathias Bavay's avatar
Mathias Bavay committed
161
162
	const QLayout *group_layout( child_container_->getLayout() );
	auto *item_group( static_cast<Group *>(group_layout->itemAt(list_->currentRow())->widget()) );
163
164

	bool one_visible = false;
165
	bool all_unchecked = true;
166
	for (int ii = 0; ii < list_->count(); ++ii) {
167
		if (list_->item(ii)->checkState() == Qt::Checked) {
168
			all_unchecked = false;
169
			if (!static_cast<Group *>(group_layout->itemAt(ii)->widget())->isEmpty()) {
170
				one_visible = true; //hide container if empty (a few blank pixels)
171
172
				break;
			}
173
174
		}
	}
175
	child_container_->setVisible(one_visible);
176
	if (all_unchecked) //switch back to main help:
177
178
		main_help_->updateText(main_help_->property("main_help").toString());

179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
	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() );
206
	setDefaultPanelStyles(list_values);
207
	setIniValue(list_values);
208
	this->setToolTip(key_ + " = " + list_values); //show sorting to the user
209
	emphasis_widget_->setToolTip(this->toolTip());
210
	setBufferedUpdatesEnabled();
211
212
}

213
214
215
216
217
/**
 * @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.
 */
218
219
void Checklist::onPropertySet()
{
Mathias Bavay's avatar
Mathias Bavay committed
220
	const QString values( this->property("ini_value").toString() );
221
222
	if (ini_value_ == values)
		return;
223
	const QStringList value_list( values.split(QRegExp("\\s+"), Qt::SkipEmptyParts) );
224

225
226
	/* clear the list, overwriting current settings */
	for (int ii = 0; ii < list_->count(); ++ii) {
227
228
		if (list_->item(ii)->checkState() == Qt::Checked) {
			list_->setCurrentRow(ii);
229
			emit listClick(list_->item(ii)); //unload child panels
230
231
232
		}
	}

233
234
	/* check all items found in the given value */
	for (auto &val : value_list) {
235
		//Note: since we find and click the checkbox for each value they are correctly inserted into the ordering
236
237
		bool found_option_to_set = false;
		for (int jj = 0; jj < list_->count(); ++jj) {
238
			if (QString::compare(list_->item(jj)->text(), val, Qt::CaseInsensitive) == 0) {
239
				list_->setCurrentRow(jj); //else we will get NULL when we fetch the item
240
				emit listClick(list_->item(jj)); //to set the default value
241
242
243
				found_option_to_set = true;
				break;
			}
244
		} //endfor jj
245
		if (!found_option_to_set)
246
			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(
247
			    val, section_, key_), "warning");
248
	} //endfor ii
249
}