1 /*
2  *  Copyright 2008-2021 Fabrice Colin
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17  */
18 
19 #include <stdlib.h>
20 #include <iostream>
21 #include <algorithm>
22 #include <glibmm/convert.h>
23 #include <gdkmm/color.h>
24 #include <gtkmm/cellrenderertext.h>
25 #include <gtkmm/colorselection.h>
26 #include <gtkmm/entry.h>
27 #include <gtkmm/label.h>
28 #include <gtkmm/main.h>
29 #include <gtkmm/menu.h>
30 #include <gtkmm/messagedialog.h>
31 
32 #include "config.h"
33 #include "NLS.h"
34 #include "StringManip.h"
35 #ifdef HAVE_DBUS
36 #include "DBusIndex.h"
37 #endif
38 #include "ModuleFactory.h"
39 #include "PinotUtils.h"
40 #include "PrefsWindow.h"
41 
42 using namespace std;
43 using namespace Glib;
44 using namespace Gdk;
45 using namespace Gtk;
46 
47 #ifdef HAVE_DBUS
48 class StartDaemonThread : public WorkerThread
49 {
50 	public:
StartDaemonThread()51 		StartDaemonThread() :
52 			WorkerThread()
53 		{
54 		}
~StartDaemonThread()55 		virtual ~StartDaemonThread()
56 		{
57 		}
58 
getType(void) const59 		virtual std::string getType(void) const
60 		{
61 			return "StartDaemonThread";
62 		}
63 
64 	protected:
doWork(void)65 		virtual void doWork(void)
66 		{
67 			// Ask the daemon to reload its configuration
68 			// Let D-Bus activate the service if necessary
69 			// We need a pure DBusIndex object
70 			DBusIndex index(NULL);
71 
72 			index.reload();
73 		}
74 
75 	private:
76 		StartDaemonThread(const StartDaemonThread &other);
77 		StartDaemonThread &operator=(const StartDaemonThread &other);
78 
79 };
80 #endif
81 
InternalState(PrefsWindow * pWindow)82 PrefsWindow::InternalState::InternalState(PrefsWindow *pWindow) :
83 	QueueManager(PinotSettings::getInstance().m_docsIndexLocation),
84 	m_savedPrefs(false)
85 {
86 	m_onThreadEndSignal.connect(sigc::mem_fun(*pWindow, &PrefsWindow::on_thread_end));
87 }
88 
~InternalState()89 PrefsWindow::InternalState::~InternalState()
90 {
91 }
92 
PrefsWindow(_GtkWindow * & pParent,Glib::RefPtr<Gtk::Builder> & refBuilder)93 PrefsWindow::PrefsWindow(_GtkWindow *&pParent, Glib::RefPtr<Gtk::Builder>& refBuilder) :
94 	Window(pParent),
95 	m_settings(PinotSettings::getInstance()),
96 	m_state(this)
97 {
98 	refBuilder->get_widget("prefsCancelbutton", prefsCancelbutton);
99 	refBuilder->get_widget("prefsOkbutton", prefsOkbutton);
100 	refBuilder->get_widget("directoriesTreeview", directoriesTreeview);
101 	refBuilder->get_widget("addDirectoryButton", addDirectoryButton);
102 	refBuilder->get_widget("removeDirectoryButton", removeDirectoryButton);
103 	refBuilder->get_widget("patternsTreeview", patternsTreeview);
104 	refBuilder->get_widget("patternsCombobox", patternsCombobox);
105 	refBuilder->get_widget("addPatternButton", addPatternButton);
106 	refBuilder->get_widget("removePatternButton", removePatternButton);
107 	refBuilder->get_widget("resetPatternsButton", resetPatternsButton);
108 	refBuilder->get_widget("labelsTreeview", labelsTreeview);
109 	refBuilder->get_widget("addLabelButton", addLabelButton);
110 	refBuilder->get_widget("removeLabelButton", removeLabelButton);
111 	refBuilder->get_widget("directConnectionRadiobutton", directConnectionRadiobutton);
112 	refBuilder->get_widget("proxyRadiobutton", proxyRadiobutton);
113 	refBuilder->get_widget("proxyAddressEntry", proxyAddressEntry);
114 	refBuilder->get_widget("proxyPortSpinbutton", proxyPortSpinbutton);
115 	refBuilder->get_widget("proxyTypeCombobox", proxyTypeCombobox);
116 	refBuilder->get_widget("apiKeyLabel", apiKeyLabel);
117 	refBuilder->get_widget("apiKeyEntry", apiKeyEntry);
118 	refBuilder->get_widget("enableCompletionCheckbutton", enableCompletionCheckbutton);
119 	refBuilder->get_widget("newResultsColorbutton", newResultsColorbutton);
120 	refBuilder->get_widget("ignoreRobotsCheckbutton", ignoreRobotsCheckbutton);
121 	refBuilder->get_widget("robotsLabels", robotsLabels);
122 	refBuilder->get_widget("generalTable", generalTable);
123 	refBuilder->get_widget("prefsNotebook", prefsNotebook);
124 
125 	prefsCancelbutton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_prefsCancelbutton_clicked), false);
126 	prefsOkbutton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_prefsOkbutton_clicked), false);
127 	addDirectoryButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_addDirectoryButton_clicked), false);
128 	removeDirectoryButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_removeDirectoryButton_clicked), false);
129 	patternsCombobox->signal_changed().connect(sigc::mem_fun(*this, &PrefsWindow::on_patternsCombobox_changed), false);
130 	addPatternButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_addPatternButton_clicked), false);
131 	removePatternButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_removePatternButton_clicked), false);
132 	resetPatternsButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_resetPatternsButton_clicked), false);
133 	addLabelButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_addLabelButton_clicked), false);
134 	removeLabelButton->signal_clicked().connect(sigc::mem_fun(*this, &PrefsWindow::on_removeLabelButton_clicked), false);
135 	directConnectionRadiobutton->signal_toggled().connect(sigc::mem_fun(*this, &PrefsWindow::on_directConnectionRadiobutton_toggled), false);
136 	signal_delete_event().connect(sigc::mem_fun(*this, &PrefsWindow::on_prefsWindow_delete_event), false);
137 
138 	Color newColour;
139 	ustring desktopName(_("File Indexing and Search"));
140 	ustring desktopComment(_("Configure Pinot to index your files"));
141 
142 	newColour.set_red(m_settings.m_newResultsColourRed);
143 	newColour.set_green(m_settings.m_newResultsColourGreen);
144 	newColour.set_blue(m_settings.m_newResultsColourBlue);
145 
146 	// Initialize widgets
147 	// Ignore robots directives
148 	ignoreRobotsCheckbutton->set_active(m_settings.m_ignoreRobotsDirectives);
149 	// New results colour
150 	newResultsColorbutton->set_color(newColour);
151 	// Enable terms suggestion
152 	enableCompletionCheckbutton->set_active(m_settings.m_suggestQueryTerms);
153 
154 	// Any plugin editable parameter ?
155 	if (m_settings.m_editablePluginValues.empty() == false)
156 	{
157 		Glib::PropertyProxy<guint> columnsProp(generalTable->property_n_columns());
158 		Glib::PropertyProxy<guint> rowsProp(generalTable->property_n_rows());
159 		guint rowsCount = rowsProp.get_value();
160 
161 #ifdef DEBUG
162 		clog << "PrefsWindow: adding " << m_settings.m_editablePluginValues.size() << " more rows" << endl;
163 #endif
164 		generalTable->resize(rowsCount + m_settings.m_editablePluginValues.size(), columnsProp.get_value());
165 
166 		for (std::map<string, string>::const_iterator valueIter = m_settings.m_editablePluginValues.begin();
167 			valueIter != m_settings.m_editablePluginValues.end(); ++valueIter)
168 		{
169 			++rowsCount;
170 			attach_value_widgets(valueIter->first, valueIter->second, rowsCount);
171 		}
172 	}
173 
174 	populate_proxyTypeCombobox();
175 	proxyRadiobutton->set_active(m_settings.m_proxyEnabled);
176 	proxyAddressEntry->set_text(m_settings.m_proxyAddress);
177 	proxyPortSpinbutton->set_value((double)m_settings.m_proxyPort);
178 	int proxyType = 0;
179 	if (m_settings.m_proxyType == "SOCKS4")
180 	{
181 		proxyType = 1;
182 	}
183 	else if (m_settings.m_proxyType == "SOCKS5")
184 	{
185 		proxyType = 2;
186 	}
187 	proxyTypeCombobox->set_active(proxyType);
188 	on_directConnectionRadiobutton_toggled();
189 
190 	// Associate the columns model to the labels tree
191 	m_refLabelsTree = ListStore::create(m_labelsColumns);
192 	labelsTreeview->set_model(m_refLabelsTree);
193 	labelsTreeview->append_column_editable(_("Name"), m_labelsColumns.m_name);
194 	// Allow only single selection
195 	labelsTreeview->get_selection()->set_mode(SELECTION_SINGLE);
196 	populate_labelsTreeview();
197 
198 	// Associate the columns model to the directories tree
199 	m_refDirectoriesTree = ListStore::create(m_directoriesColumns);
200 	directoriesTreeview->set_model(m_refDirectoriesTree);
201 	directoriesTreeview->append_column_editable(_("Monitor"), m_directoriesColumns.m_monitor);
202 	directoriesTreeview->append_column(_("Location"), m_directoriesColumns.m_location);
203 	// Allow only single selection
204 	directoriesTreeview->get_selection()->set_mode(SELECTION_SINGLE);
205 	populate_directoriesTreeview();
206 
207 	// Associate the columns model to the file patterns tree
208 	m_refPatternsTree = ListStore::create(m_patternsColumns);
209 	patternsTreeview->set_model(m_refPatternsTree);
210 	patternsTreeview->append_column_editable(_("Pattern"), m_patternsColumns.m_location);
211 	// Allow only single selection
212 	patternsTreeview->get_selection()->set_mode(SELECTION_SINGLE);
213 	populate_patternsCombobox();
214 	populate_patternsTreeview(m_settings.m_filePatternsList, m_settings.m_isBlackList);
215 
216 	// Hide the Google API entry field ?
217 	if (ModuleFactory::isSupported("googleapi") == false)
218 	{
219 		apiKeyLabel->hide();
220 		apiKeyEntry->hide();
221 	}
222 
223 	// Connect to threads' finished signal
224 	m_state.connect();
225 }
226 
~PrefsWindow()227 PrefsWindow::~PrefsWindow()
228 {
229 }
230 
updateLabelRow(const ustring & path_string,const ustring & text)231 void PrefsWindow::updateLabelRow(const ustring &path_string, const ustring &text)
232 {
233 	Gtk::TreePath path(path_string);
234 
235 	// Get the row
236 	TreeModel::iterator iter = m_refLabelsTree->get_iter(path);
237 	if (iter)
238 	{
239 		TreeRow row = *iter;
240 
241 #ifdef DEBUG
242 		clog << "PrefsWindow::updateLabelRow: set label to " << text << endl;
243 #endif
244 		// Set the value of the name column
245 		row.set_value(m_labelsColumns.m_name, (ustring)text);
246 	}
247 }
248 
renderLabelNameColumn(CellRenderer * pRenderer,const TreeModel::iterator & iter)249 void PrefsWindow::renderLabelNameColumn(CellRenderer *pRenderer, const TreeModel::iterator &iter)
250 {
251 	TreeModel::Row row = *iter;
252 
253 	if (pRenderer == NULL)
254 	{
255 		return;
256 	}
257 
258 	CellRendererText *pTextRenderer = dynamic_cast<CellRendererText*>(pRenderer);
259 	if (pTextRenderer != NULL)
260 	{
261 		bool isNewLabel = false;
262 
263 		// Is this a new label ?
264 		if (row[m_labelsColumns.m_enabled] == false)
265 		{
266 			isNewLabel = true;
267 		}
268 
269 		// Set the editable property
270 #ifdef GLIBMM_PROPERTIES_ENABLED
271 		pTextRenderer->property_editable() = isNewLabel;
272 #else
273 		pTextRenderer->set_property("editable", isNewLabel);
274 #endif
275 	}
276 }
277 
on_thread_end(WorkerThread * pThread)278 void PrefsWindow::on_thread_end(WorkerThread *pThread)
279 {
280 	bool canQuit = false;
281 
282 	if (pThread == NULL)
283 	{
284 		return;
285 	}
286 
287 	// Any thread still running ?
288 	unsigned int threadsCount = m_state.get_threads_count();
289 
290 	// What type of thread was it ?
291 	string type = pThread->getType();
292 	// Did the thread fail ?
293 	string status = pThread->getStatus();
294 	if (status.empty() == false)
295 	{
296 #ifdef DEBUG
297 		clog << "PrefsWindow::on_thread_end: " << status << endl;
298 #endif
299 		// FIXME: tell the user the thread failed
300 	}
301 	else if (type == "StartDaemonThread")
302 	{
303 		// Save the settings
304 		m_state.m_savedPrefs = PinotSettings::getInstance().save(PinotSettings::SAVE_PREFS);
305 
306 		if (threadsCount == 0)
307 		{
308 			canQuit = true;
309 		}
310 	}
311 	else if (type == "LabelUpdateThread")
312 	{
313 		if (threadsCount == 0)
314 		{
315 			canQuit = true;
316 		}
317 	}
318 
319 	// Delete the thread
320 	delete pThread;
321 
322 	if (canQuit == false)
323 	{
324 		// We might be able to run a queued action
325 		m_state.pop_queue();
326 	}
327 	else
328 	{
329 #ifdef DEBUG
330 		clog << "PrefsWindow::on_thread_end: quitting" << endl;
331 #endif
332 		on_prefsWindow_delete_event(NULL);
333 	}
334 }
335 
attach_value_widgets(const string & name,const string & value,guint rowNumber)336 void PrefsWindow::attach_value_widgets(const string &name, const string &value, guint rowNumber)
337 {
338 	Label *valueLabel = manage(new Label(name + ":"));
339 	Entry *valueEntry = manage(new Entry());
340 
341 	// These settings are what Glade-- would use
342 	valueLabel->set_alignment(0,0.5);
343 	valueLabel->set_padding(4,4);
344 	valueLabel->set_justify(Gtk::JUSTIFY_LEFT);
345 	valueLabel->set_line_wrap(false);
346 	valueLabel->set_use_markup(false);
347 	valueLabel->set_selectable(false);
348 	valueLabel->set_ellipsize(Pango::ELLIPSIZE_NONE);
349 	valueLabel->set_width_chars(-1);
350 	valueLabel->set_angle(0);
351 	valueLabel->set_single_line_mode(false);
352 
353 	valueEntry->set_can_focus();
354 	valueEntry->set_visibility(true);
355 	valueEntry->set_editable(true);
356 	valueEntry->set_max_length(0);
357 	valueEntry->set_has_frame(true);
358 	valueEntry->set_activates_default(false);
359 
360 	valueEntry->set_text(to_utf8(value));
361 
362 	generalTable->attach(*valueLabel, 0, 1, rowNumber, rowNumber + 1, Gtk::FILL, Gtk::FILL, 0, 0);
363 	generalTable->attach(*valueEntry, 1, 2, rowNumber, rowNumber + 1, Gtk::EXPAND|Gtk::FILL, Gtk::FILL, 4, 4);
364 
365 	m_editableValueEntries.push_back(valueEntry);
366 
367 	valueLabel->show();
368 	valueEntry->show();
369 }
370 
populate_proxyTypeCombobox()371 void PrefsWindow::populate_proxyTypeCombobox()
372 {
373 	proxyTypeCombobox->append("HTTP");
374 	proxyTypeCombobox->append("SOCKS v4");
375 	proxyTypeCombobox->append("SOCKS v5");
376 }
377 
populate_labelsTreeview()378 void PrefsWindow::populate_labelsTreeview()
379 {
380 	set<string> &labels = m_settings.m_labels;
381 
382 	// Now this can be enabled
383 	addLabelButton->set_sensitive(true);
384 
385 	if (labels.empty() == true)
386 	{
387 		// This button will stay disabled until labels are added to the list
388 		removeLabelButton->set_sensitive(false);
389 		return;
390 	}
391 
392 	// Populate the tree
393 	for (set<string>::const_iterator labelIter = labels.begin();
394 		labelIter != labels.end();
395 		++labelIter)
396 	{
397 		// Create a new row
398 		TreeModel::iterator iter = m_refLabelsTree->append();
399 		TreeModel::Row row = *iter;
400 		// Set its name
401 		row[m_labelsColumns.m_name] = *labelIter;
402 		// This allows to differentiate existing labels from new labels the user may create
403 		row[m_labelsColumns.m_enabled] = true;
404 	}
405 
406 	removeLabelButton->set_sensitive(true);
407 }
408 
save_labelsTreeview()409 void PrefsWindow::save_labelsTreeview()
410 {
411 	set<string> &labels = m_settings.m_labels;
412 
413 	labels.clear();
414 
415 	// Go through the labels tree
416 	TreeModel::Children children = m_refLabelsTree->children();
417 	if (children.empty() == false)
418 	{
419 		TreeModel::Children::iterator iter = children.begin();
420 		for (; iter != children.end(); ++iter)
421 		{
422 			TreeModel::Row row = *iter;
423 			ustring labelName(row[m_labelsColumns.m_name]);
424 
425 			// Check user didn't recreate this label after having deleted it
426 			set<string>::iterator labelIter = m_deletedLabels.find(from_utf8(labelName));
427 			if (labelIter != m_deletedLabels.end())
428 			{
429 				m_deletedLabels.erase(labelIter);
430 			}
431 			// Is this a new label ?
432 			if (row[m_labelsColumns.m_enabled] == false)
433 			{
434 				m_addedLabels.insert(from_utf8(labelName));
435 			}
436 
437 #ifdef DEBUG
438 			clog << "PrefsWindow::save_labelsTreeview: " << labelName << endl;
439 #endif
440 			// Add this new label to the settings
441 			labels.insert(labelName);
442 		}
443 	}
444 }
445 
populate_directoriesTreeview()446 void PrefsWindow::populate_directoriesTreeview()
447 {
448 	ustring dirsString;
449 
450 	if (m_settings.m_indexableLocations.empty() == true)
451 	{
452 		// This button will stay disabled until directories are added to the list
453 		removeDirectoryButton->set_sensitive(false);
454 		return;
455 	}
456 
457 	// Populate the tree
458 	for (set<PinotSettings::IndexableLocation>::iterator dirIter = m_settings.m_indexableLocations.begin();
459 		dirIter != m_settings.m_indexableLocations.end();
460 		++dirIter)
461 	{
462 		// Create a new row
463 		TreeModel::iterator iter = m_refDirectoriesTree->append();
464 		TreeModel::Row row = *iter;
465 		row[m_directoriesColumns.m_monitor] = dirIter->m_monitor;
466 		row[m_directoriesColumns.m_location] = dirIter->m_name;
467 		dirsString += dirIter->m_name + (dirIter->m_monitor == true ? "1" : "0") + "|";
468 	}
469 
470 	m_directoriesHash = StringManip::hashString(dirsString);
471 	removeDirectoryButton->set_sensitive(true);
472 }
473 
save_directoriesTreeview()474 bool PrefsWindow::save_directoriesTreeview()
475 {
476 	string dirsString;
477 
478 	// Clear the current settings
479 	m_settings.m_indexableLocations.clear();
480 
481 	// Go through the directories tree
482 	TreeModel::Children children = m_refDirectoriesTree->children();
483 	if (children.empty() == false)
484 	{
485 		TreeModel::Children::iterator iter = children.begin();
486 		for (; iter != children.end(); ++iter)
487 		{
488 			TreeModel::Row row = *iter;
489 			PinotSettings::IndexableLocation indexableLocation;
490 
491 			// Add this new directory to the settings
492 			indexableLocation.m_monitor = row[m_directoriesColumns.m_monitor];
493 			indexableLocation.m_name = row[m_directoriesColumns.m_location];
494 
495 			string dirLabel("file://");
496 			dirLabel += from_utf8(indexableLocation.m_name);
497 
498 			// Check user didn't recreate this directory after having deleted it
499 			set<string>::iterator dirIter = m_deletedDirectories.find(dirLabel);
500 			if (dirIter != m_deletedDirectories.end())
501 			{
502 				m_deletedDirectories.erase(dirIter);
503 			}
504 
505 #ifdef DEBUG
506 			clog << "PrefsWindow::save_directoriesTreeview: " << indexableLocation.m_name << endl;
507 #endif
508 			m_settings.m_indexableLocations.insert(indexableLocation);
509 			dirsString += indexableLocation.m_name + (indexableLocation.m_monitor == true ? "1" : "0") + "|";
510 		}
511 	}
512 
513 	if (m_directoriesHash != StringManip::hashString(dirsString))
514 	{
515 #ifdef DEBUG
516 		clog << "PrefsWindow::save_directoriesTreeview: directories changed" << endl;
517 #endif
518 		return true;
519 	}
520 
521 	return false;
522 }
523 
populate_patternsCombobox()524 void PrefsWindow::populate_patternsCombobox()
525 {
526 	patternsCombobox->append(_("Exclude these patterns from indexing"));
527 	patternsCombobox->append(_("Only index these patterns"));
528 }
529 
populate_patternsTreeview(const set<ustring> & patternsList,bool isBlackList)530 void PrefsWindow::populate_patternsTreeview(const set<ustring> &patternsList, bool isBlackList)
531 {
532 	ustring patternsString;
533 
534 	if (patternsList.empty() == true)
535 	{
536 		// This button will stay disabled until a ppatern is added to the list
537 		removePatternButton->set_sensitive(false);
538 		return;
539 	}
540 
541 	// Populate the tree
542 	for (set<ustring>::iterator patternIter = patternsList.begin();
543 		patternIter != patternsList.end();
544 		++patternIter)
545 	{
546 		ustring pattern(*patternIter);
547 
548 		// Create a new row
549 		TreeModel::iterator iter = m_refPatternsTree->append();
550 		TreeModel::Row row = *iter;
551 		// Set its name
552 		row[m_patternsColumns.m_location] = pattern;
553 		patternsString += pattern + "|";
554 	}
555 
556 	removePatternButton->set_sensitive(true);
557 
558 	// Is it a black or white list ?
559 	if (isBlackList == true)
560 	{
561 		patternsCombobox->set_active(0);
562 		patternsString += "0";
563 	}
564 	else
565 	{
566 		patternsCombobox->set_active(1);
567 		patternsString += "1";
568 	}
569 
570 	m_patternsHash = StringManip::hashString(patternsString);
571 }
572 
save_patternsTreeview()573 bool PrefsWindow::save_patternsTreeview()
574 {
575 	ustring patternsString;
576 
577 	// Clear the current settings
578 	m_settings.m_filePatternsList.clear();
579 
580 	// Go through the file patterns tree
581 	TreeModel::Children children = m_refPatternsTree->children();
582 	if (children.empty() == false)
583 	{
584 		TreeModel::Children::iterator iter = children.begin();
585 		for (; iter != children.end(); ++iter)
586 		{
587 			TreeModel::Row row = *iter;
588 			ustring pattern(row[m_patternsColumns.m_location]);
589 
590 			if (pattern.empty() == false)
591 			{
592 				m_settings.m_filePatternsList.insert(pattern);
593 				patternsString += pattern + "|";
594 			}
595 		}
596 	}
597 	if (patternsCombobox->get_active_row_number() == 0)
598 	{
599 		m_settings.m_isBlackList = true;
600 		patternsString += "0";
601 	}
602 	else
603 	{
604 		m_settings.m_isBlackList = false;
605 		patternsString += "1";
606 	}
607 
608 	if (m_patternsHash != StringManip::hashString(patternsString))
609 	{
610 #ifdef DEBUG
611 		clog << "PrefsWindow::save_patternsTreeview: patterns changed" << endl;
612 #endif
613 		return true;
614 	}
615 
616 	return false;
617 }
618 
on_prefsCancelbutton_clicked()619 void PrefsWindow::on_prefsCancelbutton_clicked()
620 {
621 	on_prefsWindow_delete_event(NULL);
622 }
623 
on_prefsOkbutton_clicked()624 void PrefsWindow::on_prefsOkbutton_clicked()
625 {
626 	bool startedThread = false;
627 
628 	// Disable the buttons
629 	prefsCancelbutton->set_sensitive(false);
630 	prefsOkbutton->set_sensitive(false);
631 
632 	// Synchronise widgets with settings
633 	m_settings.m_ignoreRobotsDirectives = ignoreRobotsCheckbutton->get_active();
634 	Color newColour = newResultsColorbutton->get_color();
635 	m_settings.m_newResultsColourRed = newColour.get_red();
636 	m_settings.m_newResultsColourGreen = newColour.get_green();
637 	m_settings.m_newResultsColourBlue = newColour.get_blue();
638 	m_settings.m_suggestQueryTerms = enableCompletionCheckbutton->get_active();
639 	// Any plugin editable parameter ?
640 	if (m_settings.m_editablePluginValues.empty() == false)
641 	{
642 		std::map<string, string>::iterator valueIter = m_settings.m_editablePluginValues.begin();
643 		vector<Entry *>::const_iterator entryIter = m_editableValueEntries.begin();
644 		while ((valueIter != m_settings.m_editablePluginValues.end()) &&
645 			(entryIter != m_editableValueEntries.end()))
646 		{
647 			ustring value((*entryIter)->get_text());
648 
649 			valueIter->second = from_utf8(value);
650 
651 			// Next
652 			++valueIter;
653 			++entryIter;
654 		}
655 	}
656 
657 	m_settings.m_proxyEnabled = proxyRadiobutton->get_active();
658 	m_settings.m_proxyAddress = proxyAddressEntry->get_text();
659 	m_settings.m_proxyPort = (unsigned int)proxyPortSpinbutton->get_value();
660 	int proxyType = proxyTypeCombobox->get_active_row_number();
661 	if (proxyType == 1)
662 	{
663 		m_settings.m_proxyType = "SOCKS4";
664 	}
665 	else if (proxyType == 2)
666 	{
667 		m_settings.m_proxyType = "SOCKS5";
668 	}
669 	else
670 	{
671 		m_settings.m_proxyType = "HTTP";
672 	}
673 
674 	// Validate the current lists
675 	save_labelsTreeview();
676 	bool startForDirectories = save_directoriesTreeview();
677 	bool startForPatterns = save_patternsTreeview();
678 #ifdef HAVE_DBUS
679 	if ((startForDirectories == true) ||
680 		(startForPatterns == true))
681 	{
682 		StartDaemonThread *pThread = new StartDaemonThread();
683 
684 		if (m_state.start_thread(pThread) == false)
685 		{
686 			delete pThread;
687 		}
688 		else
689 		{
690 			startedThread = true;
691 		}
692 	}
693 #endif
694 	if ((m_addedLabels.empty() == false) ||
695 		(m_deletedLabels.empty() == false))
696 	{
697 		LabelUpdateThread *pThread = new LabelUpdateThread(m_addedLabels, m_deletedLabels);
698 
699 		if (m_state.start_thread(pThread) == false)
700 		{
701 			delete pThread;
702 		}
703 		else
704 		{
705 			startedThread = true;
706 		}
707 	}
708 
709 	if (startedThread == false)
710 	{
711 		on_prefsWindow_delete_event(NULL);
712 	}
713 	// FIXME: else, disable all buttons or provide some visual feedback, until threads are done
714 }
715 
on_addDirectoryButton_clicked()716 void PrefsWindow::on_addDirectoryButton_clicked()
717 {
718 	ustring dirName;
719 
720 	TreeModel::Children children = m_refDirectoriesTree->children();
721 	bool wasEmpty = children.empty();
722 
723 	if (select_file_name(_("Directory to index"), dirName, true, true) == true)
724 	{
725 #ifdef DEBUG
726 		clog << "PrefsWindow::on_addDirectoryButton_clicked: "
727 			<< dirName << endl;
728 #endif
729 		// Create a new entry in the directories list
730 		TreeModel::iterator iter = m_refDirectoriesTree->append();
731 		TreeModel::Row row = *iter;
732 
733 		row[m_directoriesColumns.m_monitor] = false;
734 		row[m_directoriesColumns.m_location] = to_utf8(dirName);
735 
736 		if (wasEmpty == true)
737 		{
738 			// Enable this button
739 			removeDirectoryButton->set_sensitive(true);
740 		}
741 	}
742 }
743 
on_removeDirectoryButton_clicked()744 void PrefsWindow::on_removeDirectoryButton_clicked()
745 {
746 	// Get the selected directory in the list
747 	TreeModel::iterator iter = directoriesTreeview->get_selection()->get_selected();
748 	if (iter)
749 	{
750 		string dirLabel("file://");
751 
752 		// Unselect
753 		directoriesTreeview->get_selection()->unselect(iter);
754 		// Select another row
755 		TreeModel::Path dirPath = m_refDirectoriesTree->get_path(iter);
756 		dirPath.next();
757 		directoriesTreeview->get_selection()->select(dirPath);
758 
759 		// Erase
760 		TreeModel::Row row = *iter;
761 		dirLabel += from_utf8(row[m_directoriesColumns.m_location]);
762 		m_deletedDirectories.insert(dirLabel);
763 		m_refDirectoriesTree->erase(row);
764 
765 		TreeModel::Children children = m_refDirectoriesTree->children();
766 		if (children.empty() == true)
767 		{
768 			// Disable this button
769 			removeDirectoryButton->set_sensitive(false);
770 		}
771 	}
772 }
773 
on_patternsCombobox_changed()774 void PrefsWindow::on_patternsCombobox_changed()
775 {
776 	int activeRow = patternsCombobox->get_active_row_number();
777 
778 	if (((activeRow == 0) && (m_settings.m_isBlackList == true)) ||
779 		((activeRow > 0) && (m_settings.m_isBlackList == false)))
780 	{
781 		// No change
782 		return;
783 	}
784 
785 	// Unselect
786 	patternsTreeview->get_selection()->unselect_all();
787 	// Remove all patterns in order to make sure the user enters a new bunch
788 	m_refPatternsTree->clear();
789 }
790 
on_addPatternButton_clicked()791 void PrefsWindow::on_addPatternButton_clicked()
792 {
793 	TreeModel::Children children = m_refPatternsTree->children();
794 	bool wasEmpty = children.empty();
795 
796 	// Create a new entry in the file patterns list
797 	TreeModel::iterator iter = m_refPatternsTree->append();
798 	TreeModel::Row row = *iter;
799 
800 	row[m_patternsColumns.m_location] = "";
801 	row[m_patternsColumns.m_mTime] = time(NULL);
802 
803 	// Select and start editing the row
804 	TreeViewColumn *pColumn = patternsTreeview->get_column(0);
805 	if (pColumn != NULL)
806 	{
807 		TreeModel::Path patternPath = m_refPatternsTree->get_path(iter);
808 		patternsTreeview->set_cursor(patternPath, *pColumn, true);
809 	}
810 
811 	if (wasEmpty == true)
812 	{
813 		// Enable this button
814 		removePatternButton->set_sensitive(true);
815 	}
816 }
817 
on_removePatternButton_clicked()818 void PrefsWindow::on_removePatternButton_clicked()
819 {
820 	// Get the selected file pattern in the list
821 	TreeModel::iterator iter = patternsTreeview->get_selection()->get_selected();
822 	if (iter)
823 	{
824 		// Unselect
825 		patternsTreeview->get_selection()->unselect(iter);
826 		// Select another row
827 		TreeModel::Path patternPath = m_refPatternsTree->get_path(iter);
828 		patternPath.next();
829 		patternsTreeview->get_selection()->select(patternPath);
830 
831 		// Erase
832 		TreeModel::Row row = *iter;
833 		m_refPatternsTree->erase(row);
834 
835 		TreeModel::Children children = m_refPatternsTree->children();
836 		if (children.empty() == true)
837 		{
838 			// Disable this button
839 			removePatternButton->set_sensitive(false);
840 		}
841 	}
842 }
843 
on_resetPatternsButton_clicked()844 void PrefsWindow::on_resetPatternsButton_clicked()
845 {
846 	set<ustring> defaultPatterns;
847 	bool isBlackList = m_settings.getDefaultPatterns(defaultPatterns);
848 
849 	// Unselect
850 	patternsTreeview->get_selection()->unselect_all();
851 	// Remove all patterns
852 	m_refPatternsTree->clear();
853 
854 	// Repopulate with defaults
855 	populate_patternsTreeview(defaultPatterns, isBlackList);
856 }
857 
on_addLabelButton_clicked()858 void PrefsWindow::on_addLabelButton_clicked()
859 {
860 	// Now create a new entry in the labels list
861 	TreeModel::iterator iter = m_refLabelsTree->append();
862 	TreeModel::Row row = *iter;
863 	row[m_labelsColumns.m_name] = to_utf8(_("New Label"));
864 	// This marks the label as new
865 	row[m_labelsColumns.m_enabled] = false;
866 
867 	// Enable this button
868 	removeLabelButton->set_sensitive(true);
869 }
870 
on_removeLabelButton_clicked()871 void PrefsWindow::on_removeLabelButton_clicked()
872 {
873 	// Get the selected label in the list
874 	TreeModel::iterator iter = labelsTreeview->get_selection()->get_selected();
875 	if (iter)
876 	{
877 		// Unselect
878 		labelsTreeview->get_selection()->unselect(iter);
879 		// Select another row
880 		TreeModel::Path labelPath = m_refLabelsTree->get_path(iter);
881 		labelPath.next();
882 		labelsTreeview->get_selection()->select(labelPath);
883 		// Erase
884 		TreeModel::Row row = *iter;
885 		m_deletedLabels.insert(from_utf8(row[m_labelsColumns.m_name]));
886 		m_refLabelsTree->erase(row);
887 
888 		TreeModel::Children children = m_refLabelsTree->children();
889 		if (children.empty() == true)
890 		{
891 			// Disable this button
892 			removeLabelButton->set_sensitive(false);
893 		}
894 	}
895 }
896 
on_directConnectionRadiobutton_toggled()897 void PrefsWindow::on_directConnectionRadiobutton_toggled()
898 {
899 	bool enabled = proxyRadiobutton->get_active();
900 
901 	proxyAddressEntry->set_sensitive(enabled);
902 	proxyPortSpinbutton->set_sensitive(enabled);
903 	proxyTypeCombobox->set_sensitive(enabled);
904 }
905 
on_prefsWindow_delete_event(GdkEventAny * ev)906 bool PrefsWindow::on_prefsWindow_delete_event(GdkEventAny *ev)
907 {
908 	// Any thread still running ?
909 	if (m_state.get_threads_count() > 0)
910 	{
911 		ustring boxMsg(_("At least one task hasn't completed yet. Quit now ?"));
912 		MessageDialog msgDialog(boxMsg, false, MESSAGE_QUESTION, BUTTONS_YES_NO);
913 		msgDialog.set_title(_("Quit"));
914 		msgDialog.set_transient_for(*this);
915 		msgDialog.show();
916 		int result = msgDialog.run();
917 		if (result == RESPONSE_NO)
918 		{
919 			return true;
920 		}
921 
922 		m_state.disconnect();
923 		m_state.mustQuit(true);
924 	}
925 	else
926 	{
927 		m_state.disconnect();
928 	}
929 
930 	if (m_state.m_savedPrefs == false)
931 	{
932 		// Save the settings
933 		PinotSettings::getInstance().save(PinotSettings::SAVE_PREFS);
934 	}
935 
936 	Main::quit();
937 
938 	return false;
939 }
940 
941