/*
* Copyright (C) 2015 The Lemon Man
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
using Gtk;
namespace Gedit {
namespace FindInFilesPlugin {
void toggle_fold (TreeView tv, TreePath path) {
if (tv.is_row_expanded (path))
tv.collapse_row (path);
else
tv.expand_row (path, false);
}
class ResultPanel : Overlay {
// The search job whose results we are showing
private FindJob job;
private string root;
private TreeView list;
private TreeStore results_model;
private Button stop_button;
private Gedit.Window win;
~ResultPanel () {
job.halt ();
}
public new void grab_focus () {
list.grab_focus ();
}
// Try to find 'key' anywhere in the path
private bool list_search (TreeModel model, int column, string key, TreeIter iter) {
Value val;
model.get_value (iter, 0, out val);
// The return values are actually swapped
return (Posix.strstr (val.get_string (), key) != null) ? false : true;
}
private bool on_button_press (Gdk.EventButton event) {
// Create and show the popup menu on right click
if (event.type == Gdk.EventType.BUTTON_PRESS && event.button == 3) {
var popup = new Gtk.Menu ();
var close_item = new Gtk.MenuItem.with_mnemonic (_("_Close"));
close_item.activate.connect (() => {
// Easy peasy
this.destroy ();
});
popup.attach_to_widget (this, null);
popup.add (close_item);
popup.show_all ();
popup.popup (null, null, null, event.button, event.time);
return true;
}
return false;
}
private void on_row_activated (TreePath path, TreeViewColumn column) {
TreeIter iter;
TreeIter parent;
if (!results_model.get_iter (out iter, path))
return;
// This happens when the user clicks on the file entry
if (!results_model.iter_parent (out parent, iter)) {
toggle_fold (list, path);
return;
}
Value val0, val1;
results_model.get_value (parent, 0, out val0);
results_model.get_value (iter, 1, out val1);
var selection_path = val0.get_string (),
selection_line = val1.get_int ();
Gedit.commands_load_location (win,
File.new_for_path (selection_path),
null,
selection_line,
0);
}
string get_relative_path (string path, string from) {
// Simplistic way to get a relative path, strip the leading slash too
if (path.has_prefix (from))
return path.substring (from.length + 1);
return path;
}
void column_data_func (TreeViewColumn column, CellRenderer cell, TreeModel model, TreeIter iter) {
TreeIter parent;
if (!results_model.iter_parent (out parent, iter)) {
Value val0, val1;
model.get_value (iter, 0, out val0);
model.get_value (iter, 1, out val1);
var path = val0.get_string ();
var hits = val1.get_int ();
(cell as CellRendererText).markup = "<b>%s</b> (%d %s)".printf (
get_relative_path (path, root),
hits,
(hits == 1) ? "hit" : "hits");
} else {
Value val0, val1;
model.get_value (iter, 0, out val0);
model.get_value (iter, 1, out val1);
var at = val1.get_int ();
var line = val0.get_string ();
(cell as CellRendererText).text = "%d:%s".printf (at, line);
}
}
public ResultPanel.for_job (FindJob job_, string root_, Gedit.Window win_) {
results_model = new TreeStore (2, typeof (string), typeof (int));
job = job_;
win = win_;
root = root_;
var it_table = new HashTable<string, TreeIter?> (str_hash, str_equal);
// Connect to the job signals
job.on_match_found.connect((result) => {
// The 'on_match_found' is emitted from the worker thread
Idle.add (() => {
TreeIter iter;
var parent = it_table.lookup (result.path);
// Create a new top-level node for the file
if (parent == null) {
results_model.append (out parent, null);
results_model.set (parent,
0, result.path,
1, 0);
it_table.insert (result.path, parent);
}
// Increment the hits counter
Value val0;
results_model.get_value (parent, 1, out val0);
results_model.set (parent, 1, val0.get_int () + 1);
// Append the result
results_model.append (out iter, parent);
results_model.set (iter,
0, result.context,
1, result.line);
return false;
});
});
job.on_search_finished.connect (() => {
job.halt ();
stop_button.set_visible (false);
list.expand_all ();
TreeIter dummy;
// Check if the search gave no results
if (!results_model.get_iter_first (out dummy)) {
results_model.append (out dummy, null);
results_model.set (dummy, 0, _("No results found"));
}
});
// Create the ui to hold the results
list = new TreeView.with_model (results_model);
list.set_search_column (0);
list.set_search_equal_func (list_search);
list.insert_column_with_data_func (-1,
"File",
new CellRendererText (),
column_data_func);
// list.set_activate_on_single_click (true);
list.row_activated.connect (on_row_activated);
list.button_press_event.connect (on_button_press);
// The stop button is showed in the bottom-left corner of the TreeView
stop_button = new Button.from_icon_name ("process-stop-symbolic", IconSize.BUTTON);
stop_button.set_tooltip_text (_("Stop the search"));
stop_button.set_visible (false);
stop_button.set_valign (Align.END);
stop_button.set_halign (Align.END);
stop_button.set_margin_bottom (4);
stop_button.set_margin_end (4);
stop_button.clicked.connect (() => {
stop_button.set_visible (false);
job.halt ();
});
var scroll = new ScrolledWindow (null, null);
scroll.set_policy (PolicyType.AUTOMATIC, PolicyType.AUTOMATIC);
scroll.add (list);
// The overlay contains the results list and the stop button
this.add_overlay (stop_button);
this.add (scroll);
}
public void toggle_stop_button (bool show) {
stop_button.set_visible (show);
}
}
} // namespace FindInFilesPlugin
} // namespace Gedit