WSL/SLF GitLab Repository

FilePath.cc 8.33 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
21

#include "FilePath.h"
#include "Label.h"
#include "src/main/colors.h"
22
#include "src/main/settings.h"
23
24

#include <QFileDialog>
25
#include <QFileInfo>
26
#include <QHBoxLayout>
27

28
29
30
31
32
33
34
35
36
37
38
39
40
/**
 * @class FilePath
 * @brief Default constructor for a file/path picker.
 * @details The file/path picker displays a dialog to select either a file or a folder.
 * @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 file/path picker.
 * @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.
 */
FilePath::FilePath(const QString &section, const QString &key, const QDomNode &options, const bool &no_spacers,
    QWidget *parent) : Atomic(section, key, parent)
{
41
	/* label and text field */
42
	auto *key_label( new Label(QString(), QString(), options, no_spacers, key_, this) );
43
	setEmphasisWidget(key_label->label_);
44
	path_text_ = new QLineEdit; //textfield with one line
45
46
47
	connect(path_text_, &QLineEdit::textEdited, this, &FilePath::checkValue);

	/* "open" button and info label */
48
	open_button_ = new QPushButton("…");
49
50
	open_button_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
	connect(open_button_, &QPushButton::clicked, this, &FilePath::openFile);
51
52
	info_text_ = new QLabel();
	info_text_->setAlignment(Qt::AlignCenter);
Mathias Bavay's avatar
Mathias Bavay committed
53
	QPalette label_palette( info_text_->palette() ); //custom color
54
	label_palette.setColor(QPalette::WindowText, colors::getQColor("warning"));
55
	info_text_->setPalette(label_palette);
56
	info_text_->setVisible(false);
57
58

	/* layout of the basic elements */
Mathias Bavay's avatar
Mathias Bavay committed
59
	auto *filepath_layout( new QHBoxLayout );
60
61
	filepath_layout->addWidget(key_label, 0, Qt::AlignLeft);
	filepath_layout->addWidget(path_text_);
62
	filepath_layout->addWidget(open_button_);
Michael Reisecker's avatar
Michael Reisecker committed
63
	path_text_->setMinimumWidth(no_spacers? Cst::tiny : Cst::width_filepath_min);
64
	addHelp(filepath_layout, options, no_spacers);
65
66

	/* main layout with interactive widgets and info label */
Mathias Bavay's avatar
Mathias Bavay committed
67
	auto *main_layout( new QVBoxLayout );
68
	main_layout->addLayout(filepath_layout);
Michael Reisecker's avatar
Michael Reisecker committed
69
70
	main_layout->addWidget(info_text_, 0, Qt::AlignLeft);
	setLayoutMargins(main_layout);
71
72
	this->setLayout(main_layout);

73
	setOptions(options); //file_and_path, filename or path
74
	path_text_->setPlaceholderText(path_only_? tr("<no path set>") : tr("<no file set>"));
75
76
77
78
79
80
}

/**
 * @brief Parse options for a file/patch picker from XML.
 * @param[in] options XML node holding the file/path picker.
 */
81
void FilePath::setOptions(const QDomNode &options)
82
{
83
84
	const QString type(options.toElement().attribute("type"));
	if ( type == "path") {
85
		path_only_ = true;
86
87
		open_button_->setToolTip(tr("Open path"));
	} else {
88
		if (type == "filename") filename_only_ = true;
89
90
91
		open_button_->setToolTip(tr("Open file"));
	}

92
	for (QDomElement op = options.firstChildElement("option"); !op.isNull(); op = op.nextSiblingElement("option")) {
Mathias Bavay's avatar
Mathias Bavay committed
93
		const QString ext( op.attribute("extension") ); //selectable file extensions can be set
94
95
96
		extensions_ += ext + (ext.isEmpty()? "" : ";;");
	}
	if (extensions_.isEmpty())
97
		extensions_= tr("All Files (*)");
98
99
	else
		extensions_.chop(2); //remove trailing ;;
100
101
102
103
104
105

	if (options.toElement().attribute("mode") == "input") //so we can do a little more checking
		io_mode = INPUT;
	else if (options.toElement().attribute("mode").toLower() == "output")
		io_mode = OUTPUT;

106
	setDefaultPanelStyles(path_text_->text());
107
}
108
109
110
111
112
113

/**
 * @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.
 */
114
void FilePath::onPropertySet()
115
{
Mathias Bavay's avatar
Mathias Bavay committed
116
	const QString filename( this->property("ini_value").toString() );
117
118
	if (ini_value_ == filename)
		return;
119
	path_text_->setText(filename);
120
121
122
	checkValue(filename);
}

123
124
125
126
127
128
/**
 * @brief Perform checks on the selected file name.
 * @details While we always set the file name in the INI (could run on different machines)
 * some integrity checks regarding existence, permissions, ..., are performed here.
 * @param[in] filename The chosen file name or path.
 */
129
130
void FilePath::checkValue(const QString &filename)
{
131
132
	path_text_->setText(filename);
	info_text_->setVisible(true);
133
	const QFileInfo file_info( filename ); //TODO: Handle paths relative to the loaded INI
134

135
	if (filename.isEmpty()) {
136
137
138
139
		setUpdatesEnabled(false);
		info_text_->setVisible(false);
	} else if (filename.trimmed().isEmpty()) {
		info_text_->setText(tr("[Empty file name]"));
140
141
142
	} else if (filename_only_) { //no other checks possible, we don't know which path the file belongs to
		setUpdatesEnabled(false);
		info_text_->setVisible(false);
143
	} else if (io_mode == INPUT && !file_info.exists()) {
144
		info_text_->setText(path_only_? tr("[Folder does not exist]") : tr("[File does not exist]"));
145
146
147
148
149
	} else if (path_only_ && file_info.isFile()) {
		info_text_->setText(tr("[Directory path points to a file]"));
	} else if (!path_only_ && file_info.isDir()) {
		info_text_->setText(tr("[File path points to a directory]"));
	} else if (io_mode == INPUT && file_info.exists() && !file_info.isReadable()) {
150
151
		info_text_->setText(tr(
		    R"([File not readable for current user (owned by "%1")])").arg(file_info.owner()));
152
	} else if (io_mode == OUTPUT && file_info.exists() && !file_info.isWritable()) {
153
154
		info_text_->setText(tr(
		    R"([File not writable for current user (owned by "%1")])").arg(file_info.owner()));
155
	} else if (io_mode == UNSPECIFIED && file_info.exists() && !file_info.isReadable() && !file_info.isWritable()) {
156
157
		info_text_->setText(tr(
		    R"([File not accessible for current user (owned by "%1")])").arg(file_info.owner()));
158
159
160
161
162
163
164
165
	} else if (file_info.isExecutable() && !file_info.isDir()) {
		info_text_->setText(tr("[File is an executable]"));
	} else if (file_info.isSymLink()) {
		info_text_->setText(tr("[File is a symbolic link]"));
	} else if (io_mode == OUTPUT && !path_only_ && file_info.exists()) {
		info_text_->setText(tr("[File already exists]"));
	} else if (io_mode == OUTPUT && filename.trimmed() != filename) {
		info_text_->setText(tr("[File name has leading or trailing whitespaces]"));
166
	} else {
167
168
		setUpdatesEnabled(false);
		info_text_->setVisible(false);
169
	}
170
171
172

	setDefaultPanelStyles(filename);
	setIniValue(filename); //the label is just info -> file does not actually have to exist
173
	setBufferedUpdatesEnabled(1); //hiding the info text sometimes flickers
174
175
176
177
178
179
180
181
}

/**
 * @brief Event listener for the open button.
 * @details Open a file or path by displaying a dialog window.
 */
void FilePath::openFile()
{
182
	path_text_->setProperty("shows_default", "true");
Mathias Bavay's avatar
Mathias Bavay committed
183
	QString start_path( getSetting("auto::history::last_panel_path", "path") );
184
185
	if (start_path.isEmpty())
		start_path = QDir::currentPath();
186

187
	QString path;
188
189
	if (path_only_) {
		path = QFileDialog::getExistingDirectory(this, tr("Open Folder"), start_path,
190
		    QFileDialog::ShowDirsOnly);
191
	} else {
192
		if (io_mode == INPUT) {
193
			path = QFileDialog::getOpenFileName(this, tr("Open File"), start_path,
194
			    extensions_, nullptr, QFileDialog::DontConfirmOverwrite);
195
		} else if (io_mode == OUTPUT || io_mode == UNSPECIFIED) {
196
			path = QFileDialog::getSaveFileName(this, tr("Open File"), start_path,
197
			    extensions_, nullptr, QFileDialog::DontConfirmOverwrite);
198
199
		}
	}
200
	
201
	if (!path.isNull()) {
202
		setSetting("auto::history::last_panel_path", "path", QFileInfo( path ).absoluteDir().path());
203
204
205
206
207
		//setProperty calls checkValue()
		if (filename_only_)
			this->setProperty("ini_value", QFileInfo( path ).fileName());
		else
			this->setProperty("ini_value", path);
208
209
	}
}