summaryrefslogtreecommitdiff
path: root/src/formaction.cpp
blob: 114f2dba2fa30413933092e9feb85febb27118d1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#include <formaction.h>
#include <interpreter.h>
#include <view.h>
#include <utils.h>
#include <config.h>
#include <logger.h>
#include <cassert>

namespace newsbeuter {

history formaction::searchhistory;
history formaction::cmdlinehistory;

formaction::formaction(view * vv, std::string formstr) : v(vv), f(0), do_redraw(true) { 
	f = new stfl::form(formstr);
}

void formaction::set_keymap_hints() {
	f->set("help", prepare_keymap_hint(this->get_keymap_hint()));
}

void formaction::recalculate_form() {
	f->run(-3);
}

formaction::~formaction() { 
	delete f;
}

stfl::form * formaction::get_form() {
	return f;
}

std::string formaction::prepare_keymap_hint(keymap_hint_entry * hints) {
	/*
	 * This function generates the "keymap hint" line by putting
	 * together the elements of a structure, and looking up the
	 * currently set keybinding so that the "keymap hint" line always
	 * reflects the current configuration.
	 */
	std::string keymap_hint;
	for (int i=0;hints[i].op != OP_NIL; ++i) {
		keymap_hint.append(v->get_keys()->getkey(hints[i].op));
		keymap_hint.append(":");
		keymap_hint.append(hints[i].text);
		keymap_hint.append(" ");
	}
	return keymap_hint;	
}

std::string formaction::get_value(const std::string& value) {
	return f->get(value);
}


void formaction::start_cmdline() {
	std::vector<qna_pair> qna;
	qna.push_back(qna_pair(":", ""));
	this->start_qna(qna, OP_INT_END_CMDLINE, &formaction::cmdlinehistory);
}


void formaction::process_op(operation op, bool automatic, std::vector<std::string> * args) {
	switch (op) {
		case OP_CMDLINE: 
			start_cmdline();
			break;
		case OP_INT_CANCEL_QNA:
			f->modify("lastline","replace","{hbox[lastline] .expand:0 {label[msglabel] .expand:h text[msg]:\"\"}}");
			break;
		case OP_INT_QNA_NEXTHIST:
			if (qna_history) {
				f->set("qna_value", qna_history->next());
			}
			break;
		case OP_INT_QNA_PREVHIST:
			if (qna_history) {
				f->set("qna_value", qna_history->prev());
			}
			break;
		case OP_RUNFUNCTION:
			if (automatic && args->size() > 0) {
				GetLogger().log(LOG_DEBUG, "formaction::process_op: before run_function");
#if HAVE_RUBY
				GetInterpreter()->run_function((*args)[0]);
#else
				v->show_error(_("Error: newsbeuter is compiled without Ruby support. Please recompile with Ruby support in order to run Ruby functions."));
#endif
				GetLogger().log(LOG_DEBUG, "formaction::process_op: after run_function");
			}
			break;
		case OP_INT_END_QUESTION:
			/*
			 * An answer has been entered, we save the value, and ask the next question.
			 */
			qna_responses.push_back(f->get("qna_value"));
			start_next_question();
			break;
		default:
			this->process_operation(op, automatic, args);
	}
}

void formaction::handle_cmdline(const std::string& cmdline) {
	/*
	 * this is the command line handling that is available on all dialogs.
	 * It is only called when the handle_cmdline() methods of the derived classes
	 * are unable to handle to command line or when the derived class doesn't
	 * implement the handle_cmdline() method by itself.
	 *
	 * It works the same way basically everywhere: first the command line
	 * is tokenized, and then the tokens are looked at.
	 */
	std::vector<std::string> tokens = utils::tokenize_quoted(cmdline, " \t=");
	char buf[1024];
	configcontainer * cfg = v->get_cfg();
	assert(cfg != NULL);
	if (tokens.size() > 0) {
		std::string cmd = tokens[0];
		tokens.erase(tokens.begin());
		if (cmd == "set") {
			if (tokens.size()==0) {
				v->show_error(_("usage: set <variable>[=<value>]"));
			} else if (tokens.size()==1) {
				snprintf(buf,sizeof(buf), "  %s=%s", tokens[0].c_str(), cfg->get_configvalue(tokens[0]).c_str());
				v->set_status(buf);
			} else if (tokens.size()==2) {
				cfg->set_configvalue(tokens[0], tokens[1]);
				set_redraw(true); // because some configuration value might have changed something UI-related
			} else {
				v->show_error(_("usage: set <variable>[=<value>]"));
			}
		} else if (cmd == "quit") {
			while (v->formaction_stack_size() > 0) {
				v->pop_current_formaction();
			}
		}
	}
}

void formaction::start_qna(const std::vector<qna_pair>& prompts, operation finish_op, history * h) {
	/*
	 * the formaction base class contains a "Q&A" mechanism that makes it possible for all formaction-derived classes to
	 * query the user for 1 or more values, optionally with a history.
	 *
	 * Every question is a prompt (such as "Search for: "), with an default value. These need to be provided as a vector
	 * of (string, string) tuples. What also needs to be provided is the operation that will to be signaled to the 
	 * finished_qna() method when reading all answers is finished, and optionally, a pointer to a history object to support
	 * browsing of the input history. When reading is done, the responses can be found in the qna_responses vector. In this
	 * vector, the first fields corresponds with the first prompt, the second field with the second prompt, etc.
	 */
	qna_prompts = prompts;
	if (qna_responses.size() > 0) {
		qna_responses.erase(qna_responses.begin(), qna_responses.end());
	}
	finish_operation = finish_op;
	qna_history = h;
	start_next_question();
}

void formaction::finished_qna(operation op) {
	switch (op) {
		/*
		 * since bookmarking is available in several formactions, I decided to put this into
		 * the base class so that all derived classes can take advantage of it. We also see
		 * here how the signaling of a finished "Q&A" is handled:
		 * 	- check for the right operation
		 * 	- take the responses
		 * 	- run operation (in this case, save the bookmark)
		 * 	- signal success (or failure) to the user
		 */
		case OP_INT_BM_END: {
				assert(qna_responses.size() == 3 && qna_prompts.size() == 0); // everything must be answered
				v->set_status(_("Saving bookmark..."));
				std::string retval = v->get_ctrl()->bookmark(qna_responses[0], qna_responses[1], qna_responses[2]);
				if (retval.length() == 0) {
					v->set_status(_("Saved bookmark."));
				} else {
					v->set_status((std::string(_("Error while saving bookmark: ")) + retval).c_str());
				}
			}
			break;
		case OP_INT_END_CMDLINE: {
				f->set_focus("feeds");
				std::string cmdline = qna_responses[0];
				formaction::cmdlinehistory.add_line(cmdline);
				GetLogger().log(LOG_DEBUG,"formaction: commandline = `%s'", cmdline.c_str());
				this->handle_cmdline(cmdline);
			}
			break;
		default:
			break;
	}
}


void formaction::start_bookmark_qna(const std::string& default_title, const std::string& default_url, const std::string& default_desc) {
	GetLogger().log(LOG_DEBUG, "formaction::start_bookmark_qna: OK, starting bookmark Q&A...");
	std::vector<qna_pair> prompts;

	prompts.push_back(qna_pair(_("URL: "), default_url));
	prompts.push_back(qna_pair(_("Title: "), default_title));
	prompts.push_back(qna_pair(_("Description: "), default_desc));

	start_qna(prompts, OP_INT_BM_END);
}

void formaction::start_next_question() {
	/*
	 * If there is one more prompt to be presented to the user, set it up.
	 */
	if (qna_prompts.size() > 0) {
		std::string replacestr("{hbox[lastline] .expand:0 {label .expand:0 text:");
		replacestr.append(stfl::quote(qna_prompts[0].first));
		replacestr.append("}{input[qnainput] on_ESC:cancel-qna on_UP:qna-prev-history on_DOWN:qna-next-history on_ENTER:end-question modal:1 .expand:h text[qna_value]:");
		replacestr.append(stfl::quote(qna_prompts[0].second));
		replacestr.append("}}");
		qna_prompts.erase(qna_prompts.begin());
		f->modify("lastline", "replace", replacestr);
		f->set_focus("qnainput");
	} else {
	/* 
	 * If there are no more prompts, restore the last line with the usual label, and signal the end of the "Q&A" to the finished_qna() method.
	 */
		f->modify("lastline","replace","{hbox[lastline] .expand:0 {label[msglabel] .expand:h text[msg]:\"\"}}");
		this->finished_qna(finish_operation);
	}
}


}