WSL/SLF GitLab Repository

Choice.cc 9.49 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 "Choice.h"
#include "Label.h"
Michael Reisecker's avatar
Michael Reisecker committed
21
#include "src/main/colors.h"
22
23
#include "src/main/inishell.h"

24
25
26
27
#ifdef DEBUG
	#include <iostream>
#endif //def DEBUG

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
 * @class Choice
 * @brief Default constructor for a Choice panel.
 * @details A choice panel shows a list of checkboxes, each of which controls showing/hiding of
 * additional options.
 * @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 Choice panel.
 * @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.
 */
Choice::Choice(const QString &section, const QString &key, const QDomNode &options, const bool &no_spacers,
    QWidget *parent) : Atomic(section, key, parent)
{
	const QDomElement element(options.toElement());

	//grouping element of the list of checkboxes:
45
	checkbox_container_ = new Group(QString(), QString(), false, true, false, true); //tight grid layout
46
	//grouping element for all children:
47
	child_container_ = new Group(QString(), QString()); //vertical layout
48
	child_container_->setVisible(false);
49
	auto *key_label = new Label(QString(), QString(), options, no_spacers, key_, this);
50
	setEmphasisWidget(key_label->label_);
51
52

	/* layout for checkboxes and children together */
53
54
55
56
57
58
	auto *box_layout = new QVBoxLayout;
	box_layout->setContentsMargins(0, 0, 0, 0);
	box_layout->addWidget(checkbox_container_);
	box_layout->addWidget(child_container_);

	auto *layout = new QHBoxLayout;
59
	setLayoutMargins(layout);
60
61
	layout->addWidget(key_label);
	layout->addLayout(box_layout);
62
	addHelp(layout, options);
63
64
65
66
67
68
69
70
71
	this->setLayout(layout);

	setOptions(options); //build children
}

/**
 * @brief Parse options for a Choice panel from XML.
 * @param[in] options XML node holding the Choice panel.
 */
72
void Choice::setOptions(const QDomNode &options)
73
74
{
	int counter = 0;
75
76
77
	for (QDomElement op = options.firstChildElement(); !op.isNull(); op = op.nextSiblingElement()) {
		if (op.tagName() != "option" && op.tagName() != "o")
			continue; //short <o></o> notation possible
78
79
80
		if (!hasSectionSpecified(section_, op)) //option is excluded for this section
			continue;

81
		substituteKeys(op, "@", key_);
Mathias Bavay's avatar
Mathias Bavay committed
82
		auto *checkbox( new QCheckBox(op.attribute("value")) );
83
84
		//connect to lambda function to emit current index (modern style signal mapping):
		connect(checkbox, &QCheckBox::stateChanged, this, [=] { changedState(counter); });
85
		checkbox_container_->addWidget(checkbox, counter, 0);
86

87
88
89
90
91
		/* set item properties */
		if (!op.attribute("color").isEmpty())
			checkbox->setStyleSheet("QCheckBox {color: " + colors::getQColor(op.attribute("color")).name() + "}");
		checkbox->setFont(setFontOptions(checkbox->font(), op));

92
		/* help text */
Mathias Bavay's avatar
Mathias Bavay committed
93
		QString helptext( op.firstChildElement("help").text() );
94
95
		if (helptext.isEmpty()) //same as addHelp but for a certain grid position
			helptext = op.attribute("help");
96
97
		if (helptext.isEmpty())
			helptext = op.attribute("h");
Mathias Bavay's avatar
Mathias Bavay committed
98
		auto *help( new Helptext(helptext, false, false) ); //if this is made optional account for it in onPropertySet's ... -1!
99
100
		if (helptext.isEmpty())
			help->hide();
101
		checkbox->setToolTip(op.attribute("help"));
102
		checkbox_container_->addWidget(help, counter, 1, 1, 1, Qt::AlignRight);
103
104

		/* child elements of this checkbox */
Mathias Bavay's avatar
Mathias Bavay committed
105
		auto *item_group( new Group(section_, "_item_choice_" + key_, false, false, false, true) ); //tight layout
106
107
108
109
		recursiveBuild(op, item_group, section_);
		item_group->setVisible(false);
		child_container_->addWidget(item_group);
		counter++;
110

111
		if (op.attribute("default").toLower() == "true") { //collect default values declared via attributes
Mathias Bavay's avatar
Mathias Bavay committed
112
			const QString def_val( this->property("default_value").toString() );
113
114
115
			this->setProperty("default_value", (def_val.isEmpty()? "" : def_val + " ") + op.attribute("value"));
			checkbox->setCheckState(Qt::Checked); //to set the default value
		}
116
117
118
119
	}
}

/**
120
121
 * @brief Collect checkbox texts in the order they were checked in.
 * @return INI value with ordered texts.
122
 */
123
124
125
126
127
128
129
130
131
QString Choice::getOrderedIniList() const
{
	QString list_values;
	for (auto &it : ordered_item_list_) {
		auto *box_nr = qobject_cast<QCheckBox *>(
		   checkbox_container_->getGridLayout()->itemAtPosition(it, 0)->widget());
		list_values += box_nr->text() + " "; //collect values as "value1 value2 value3..."
	}
	list_values.chop(1); //trailing space (if available)
132
	return list_values;
133
134
135
136
137
138
139
140
}

/**
 * @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 Choice::setChildVisibility(const int &index, const Qt::CheckState &checked)
141
{
Mathias Bavay's avatar
Mathias Bavay committed
142
143
	const QLayout *group_layout( child_container_->getLayout() ); //get item number 'index' from the child group's layout
	auto *item_group( qobject_cast<Group *>(group_layout->itemAt(index)->widget()) );
144

145
146
147
	//Run through all checkboxes and find out if at least one shows children.
	//This is done to hide the main container if not one child panel is visible,
	//saving a few pixels that would seem out of place.
148
	bool one_visible = false;
Michael Reisecker's avatar
Michael Reisecker committed
149
	for (int ii = 0; ii < checkbox_container_->getGridLayout()->rowCount(); ++ii) {
150
151
		auto *checkbox = qobject_cast<QCheckBox *>(checkbox_container_->getGridLayout()->
		    itemAtPosition(ii, 0)->widget());
152
		if (checkbox->checkState() == Qt::Checked) {
153
			if (!static_cast<Group *>(group_layout->itemAt(ii)->widget())->isEmpty()) {
154
				one_visible = true;
155
156
				break;
			}
157
		}
Michael Reisecker's avatar
Michael Reisecker committed
158
	}
159
160
161

	//show the clicked child's group on item check if it's not empty:
	item_group->setVisible(checked == Qt::Checked && !item_group->isEmpty());
162
	child_container_->setVisible(one_visible);
163
164
165
166
167
168
169
170
171
172
173
174
}

/**
 * @brief Event listener for when a single checkbox is checked/unchecked.
 * @details This function shows/hides child elements when a checkbox changes.
 * @param[in] index The index/row of the clicked item.
 */
void Choice::changedState(int index)
{
	auto *clicked_box = qobject_cast<QCheckBox *>(
	    checkbox_container_->getGridLayout()->itemAtPosition(index, 0)->widget());

175
	if (clicked_box->checkState() == Qt::Unchecked) {
176
177
178
		auto erase_it( std::find(ordered_item_list_.begin(), ordered_item_list_.end(), index) );
		if (erase_it != ordered_item_list_.end())
			ordered_item_list_.erase(erase_it);
179
	} else if (clicked_box->checkState() == Qt::Checked) {
180
181
182
183
		setUpdatesEnabled(false); //we only experience flickering when hiding
		ordered_item_list_.push_back(index);
	}
	setChildVisibility(index, clicked_box->checkState()); //hide child and maybe main containers
184

185
	const QString list_values( getOrderedIniList() );
186
	setDefaultPanelStyles(list_values);
Michael Reisecker's avatar
Michael Reisecker committed
187
	setIniValue(list_values);
188
	this->setToolTip(key_ + " = " + list_values); //show sorting to the user
189
	emphasis_widget_->setToolTip(this->toolTip());
190
	setBufferedUpdatesEnabled();
Michael Reisecker's avatar
Michael Reisecker committed
191
192
}

193
194
195
196
197
/**
 * @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.
 */
Michael Reisecker's avatar
Michael Reisecker committed
198
199
void Choice::onPropertySet()
{
200
	//in this case the INI value is a list of options to set, i. e. "key = value1 value2 value3..."
Mathias Bavay's avatar
Mathias Bavay committed
201
	const QString values( this->property("ini_value").toString() );
202
203
	if (ini_value_ == values)
		return;
Mathias Bavay's avatar
Mathias Bavay committed
204
	const QStringList value_list( values.split(QRegExp("\\s+"), QString::SkipEmptyParts) );
Michael Reisecker's avatar
Michael Reisecker committed
205

206
	if (checkbox_container_->count() == 1) {
207
		topLog(QString(tr(R"(XML error: No checkable options set for Choice panel "%1::%2".)").arg(
208
		    section_, key_)), "error");
209
210
211
		return;
	}

212
	//clear the list first (so that INI settings can overwrite XML settings):
213
	for (int ii = 0; ii < checkbox_container_->getGridLayout()->rowCount(); ++ii) {
214
		auto *checkbox = qobject_cast<QCheckBox *>(checkbox_container_->getGridLayout()->
215
		    itemAtPosition(ii, 0)->widget());
216
217
218
		checkbox->setCheckState(Qt::Unchecked);
	}

219
	for (auto &val : value_list) { //run through INI value list and find corresponding checkboxes
220
		//Note: since we find and click the checkbox for each value they are correctly inserted into the ordering
Michael Reisecker's avatar
Michael Reisecker committed
221
222
		bool found_option_to_set = false;
		for (int jj = 0; jj < checkbox_container_->getGridLayout()->rowCount(); ++jj) {
223
			auto *checkbox = qobject_cast<QCheckBox *>(checkbox_container_->getGridLayout()->
224
			    itemAtPosition(jj, 0)->widget());
225
			if (QString::compare(checkbox->text(), val, Qt::CaseInsensitive) == 0) {
Michael Reisecker's avatar
Michael Reisecker committed
226
				checkbox->setCheckState(Qt::Checked);
227
				found_option_to_set = true; //an XML value exists for this INI value
Michael Reisecker's avatar
Michael Reisecker committed
228
229
230
231
				break;
			}
		}
		if (!found_option_to_set)
232
			topLog(tr(R"(Choice item \"%1\" could not be set from INI file for key "%2::%3": no such option specified in XML file)").arg(
233
			    val, section_, key_), "warning");
Michael Reisecker's avatar
Michael Reisecker committed
234
	} //endfor ii
235
}