view src/pan-view/pan-view-search.c @ 2530:949b146aaa23

Adds a keyword filtering feature to Timeline PanView. UI needs some work, and currently only supports intersection between multiple keywords. Would be easy to support keyword unions, if the UI supported it. Should probably hide the button in modes that don't yet support it.
author Omari Stephens <xsdg@google.com>
date Sun, 25 Dec 2016 08:25:13 +0000
parents 74d73f2f5667
children
line wrap: on
line source

/*
 * Copyright (C) 2006 John Ellis
 * Copyright (C) 2008 - 2016 The Geeqie Team
 *
 * Author: John Ellis
 *
 * 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 of the License, 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.
 */

#include "pan-view-search.h"

#include "image.h"
#include "pan-calendar.h"
#include "pan-item.h"
#include "pan-util.h"
#include "pan-view.h"
#include "ui_tabcomp.h"
#include "ui_misc.h"

PanViewSearchUi *pan_search_ui_new(PanWindow *pw)
{
	PanViewSearchUi *ui = g_new0(PanViewSearchUi, 1);
	GtkWidget *combo;
	GtkWidget *hbox;

	// Build the actual search UI.
	ui->search_box = gtk_hbox_new(FALSE, PREF_PAD_SPACE);
	pref_spacer(ui->search_box, 0);
	pref_label_new(ui->search_box, _("Find:"));

	hbox = gtk_hbox_new(TRUE, PREF_PAD_SPACE);
	gtk_box_pack_start(GTK_BOX(ui->search_box), hbox, TRUE, TRUE, 0);
	gtk_widget_show(hbox);

	combo = tab_completion_new_with_history(&ui->search_entry, "", "pan_view_search", -1,
						pan_search_activate_cb, pw);
	gtk_box_pack_start(GTK_BOX(hbox), combo, TRUE, TRUE, 0);
	gtk_widget_show(combo);

	ui->search_label = gtk_label_new("");
	gtk_box_pack_start(GTK_BOX(hbox), ui->search_label, TRUE, TRUE, 0);
	gtk_widget_show(ui->search_label);

	// Build the spin-button to show/hide the search UI.
	ui->search_button = gtk_toggle_button_new();
	gtk_button_set_relief(GTK_BUTTON(ui->search_button), GTK_RELIEF_NONE);
	gtk_button_set_focus_on_click(GTK_BUTTON(ui->search_button), FALSE);
	hbox = gtk_hbox_new(FALSE, PREF_PAD_GAP);
	gtk_container_add(GTK_CONTAINER(ui->search_button), hbox);
	gtk_widget_show(hbox);
	ui->search_button_arrow = gtk_arrow_new(GTK_ARROW_UP, GTK_SHADOW_NONE);
	gtk_box_pack_start(GTK_BOX(hbox), ui->search_button_arrow, FALSE, FALSE, 0);
	gtk_widget_show(ui->search_button_arrow);
	pref_label_new(hbox, _("Find"));

	g_signal_connect(G_OBJECT(ui->search_button), "clicked",
			 G_CALLBACK(pan_search_toggle_cb), pw);

	return ui;
}

void pan_search_ui_destroy(PanViewSearchUi **ui_ptr)
{
	if (ui_ptr == NULL || *ui_ptr == NULL) return;

	g_free(*ui_ptr);
	*ui_ptr = NULL;
}

static void pan_search_status(PanWindow *pw, const gchar *text)
{
	gtk_label_set_text(GTK_LABEL(pw->search_ui->search_label), (text) ? text : "");
}

static gint pan_search_by_path(PanWindow *pw, const gchar *path)
{
	PanItem *pi;
	GList *list;
	GList *found;
	PanItemType type;
	gchar *buf;

	type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;

	list = pan_item_find_by_path(pw, type, path, FALSE, FALSE);
	if (!list) return FALSE;

	found = g_list_find(list, pw->click_pi);
	if (found && found->next)
		{
		found = found->next;
		pi = found->data;
		}
	else
		{
		pi = list->data;
		}

	pan_info_update(pw, pi);
	image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);

	buf = g_strdup_printf("%s ( %d / %d )",
			      (path[0] == G_DIR_SEPARATOR) ? _("path found") : _("filename found"),
			      g_list_index(list, pi) + 1,
			      g_list_length(list));
	pan_search_status(pw, buf);
	g_free(buf);

	g_list_free(list);

	return TRUE;
}

static gboolean pan_search_by_partial(PanWindow *pw, const gchar *text)
{
	PanItem *pi;
	GList *list;
	GList *found;
	PanItemType type;
	gchar *buf;

	type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;

	list = pan_item_find_by_path(pw, type, text, TRUE, FALSE);
	if (!list) list = pan_item_find_by_path(pw, type, text, FALSE, TRUE);
	if (!list)
		{
		gchar *needle;

		needle = g_utf8_strdown(text, -1);
		list = pan_item_find_by_path(pw, type, needle, TRUE, TRUE);
		g_free(needle);
		}
	if (!list) return FALSE;

	found = g_list_find(list, pw->click_pi);
	if (found && found->next)
		{
		found = found->next;
		pi = found->data;
		}
	else
		{
		pi = list->data;
		}

	pan_info_update(pw, pi);
	image_scroll_to_point(pw->imd, pi->x + pi->width / 2, pi->y + pi->height / 2, 0.5, 0.5);

	buf = g_strdup_printf("%s ( %d / %d )",
			      _("partial match"),
			      g_list_index(list, pi) + 1,
			      g_list_length(list));
	pan_search_status(pw, buf);
	g_free(buf);

	g_list_free(list);

	return TRUE;
}

static gboolean valid_date_separator(gchar c)
{
	return (c == '/' || c == '-' || c == ' ' || c == '.' || c == ',');
}

static GList *pan_search_by_date_val(PanWindow *pw, PanItemType type,
				     gint year, gint month, gint day,
				     const gchar *key)
{
	GList *list = NULL;
	GList *work;

	work = g_list_last(pw->list_static);
	while (work)
		{
		PanItem *pi;

		pi = work->data;
		work = work->prev;

		if (pi->fd && (pi->type == type || type == PAN_ITEM_NONE) &&
		    ((!key && !pi->key) || (key && pi->key && strcmp(key, pi->key) == 0)))
			{
			struct tm *tl;

			tl = localtime(&pi->fd->date);
			if (tl)
				{
				gint match;

				match = (tl->tm_year == year - 1900);
				if (match && month >= 0) match = (tl->tm_mon == month - 1);
				if (match && day > 0) match = (tl->tm_mday == day);

				if (match) list = g_list_prepend(list, pi);
				}
			}
		}

	return g_list_reverse(list);
}

static gboolean pan_search_by_date(PanWindow *pw, const gchar *text)
{
	PanItem *pi = NULL;
	GList *list = NULL;
	GList *found;
	gint year;
	gint month = -1;
	gint day = -1;
	gchar *ptr;
	gchar *mptr;
	struct tm *lt;
	time_t t;
	gchar *message;
	gchar *buf;
	gchar *buf_count;

	if (!text) return FALSE;

	ptr = (gchar *)text;
	while (*ptr != '\0')
		{
		if (!g_unichar_isdigit(*ptr) && !valid_date_separator(*ptr)) return FALSE;
		ptr++;
		}

	t = time(NULL);
	if (t == -1) return FALSE;
	lt = localtime(&t);
	if (!lt) return FALSE;

	if (valid_date_separator(*text))
		{
		year = -1;
		mptr = (gchar *)text;
		}
	else
		{
		year = (gint)strtol(text, &mptr, 10);
		if (mptr == text) return FALSE;
		}

	if (*mptr != '\0' && valid_date_separator(*mptr))
		{
		gchar *dptr;

		mptr++;
		month = strtol(mptr, &dptr, 10);
		if (dptr == mptr)
			{
			if (valid_date_separator(*dptr))
				{
				month = lt->tm_mon + 1;
				dptr++;
				}
			else
				{
				month = -1;
				}
			}
		if (dptr != mptr && *dptr != '\0' && valid_date_separator(*dptr))
			{
			gchar *eptr;
			dptr++;
			day = strtol(dptr, &eptr, 10);
			if (dptr == eptr)
				{
				day = lt->tm_mday;
				}
			}
		}

	if (year == -1)
		{
		year = lt->tm_year + 1900;
		}
	else if (year < 100)
		{
		if (year > 70)
			year+= 1900;
		else
			year+= 2000;
		}

	if (year < 1970 ||
	    month < -1 || month == 0 || month > 12 ||
	    day < -1 || day == 0 || day > 31) return FALSE;

	t = pan_date_to_time(year, month, day);
	if (t < 0) return FALSE;

	if (pw->layout == PAN_LAYOUT_CALENDAR)
		{
		list = pan_search_by_date_val(pw, PAN_ITEM_BOX, year, month, day, "day");
		}
	else
		{
		PanItemType type;

		type = (pw->size > PAN_IMAGE_SIZE_THUMB_LARGE) ? PAN_ITEM_IMAGE : PAN_ITEM_THUMB;
		list = pan_search_by_date_val(pw, type, year, month, day, NULL);
		}

	if (list)
		{
		found = g_list_find(list, pw->search_pi);
		if (found && found->next)
			{
			found = found->next;
			pi = found->data;
			}
		else
			{
			pi = list->data;
			}
		}

	pw->search_pi = pi;

	if (pw->layout == PAN_LAYOUT_CALENDAR && pi && pi->type == PAN_ITEM_BOX)
		{
		pan_info_update(pw, NULL);
		pan_calendar_update(pw, pi);
		image_scroll_to_point(pw->imd,
				      pi->x + pi->width / 2,
				      pi->y + pi->height / 2, 0.5, 0.5);
		}
	else if (pi)
		{
		pan_info_update(pw, pi);
		image_scroll_to_point(pw->imd,
				      pi->x - PAN_BOX_BORDER * 5 / 2,
				      pi->y, 0.0, 0.5);
		}

	if (month > 0)
		{
		buf = pan_date_value_string(t, PAN_DATE_LENGTH_MONTH);
		if (day > 0)
			{
			gchar *tmp;
			tmp = buf;
			buf = g_strdup_printf("%d %s", day, tmp);
			g_free(tmp);
			}
		}
	else
		{
		buf = pan_date_value_string(t, PAN_DATE_LENGTH_YEAR);
		}

	if (pi)
		{
		buf_count = g_strdup_printf("( %d / %d )",
					    g_list_index(list, pi) + 1,
					    g_list_length(list));
		}
	else
		{
		buf_count = g_strdup_printf("(%s)", _("no match"));
		}

	message = g_strdup_printf("%s %s %s", _("Date:"), buf, buf_count);
	g_free(buf);
	g_free(buf_count);
	pan_search_status(pw, message);
	g_free(message);

	g_list_free(list);

	return TRUE;
}

void pan_search_activate_cb(const gchar *text, gpointer data)
{
	PanWindow *pw = data;

	if (!text) return;

	tab_completion_append_to_history(pw->search_ui->search_entry, text);

	if (pan_search_by_path(pw, text)) return;

	if ((pw->layout == PAN_LAYOUT_TIMELINE ||
	     pw->layout == PAN_LAYOUT_CALENDAR) &&
	    pan_search_by_date(pw, text))
		{
		return;
		}

	if (pan_search_by_partial(pw, text)) return;

	pan_search_status(pw, _("no match"));
}

void pan_search_activate(PanWindow *pw)
{
	gchar *text;

	text = g_strdup(gtk_entry_get_text(GTK_ENTRY(pw->search_ui->search_entry)));
	pan_search_activate_cb(text, pw);
	g_free(text);
}

void pan_search_toggle_cb(GtkWidget *button, gpointer data)
{
	PanWindow *pw = data;
	PanViewSearchUi *ui = pw->search_ui;
	gboolean visible;

	visible = gtk_widget_get_visible(ui->search_box);
	if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button)) == visible) return;

	if (visible)
		{
		gtk_widget_hide(ui->search_box);
		gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_UP, GTK_SHADOW_NONE);
		}
	else
		{
		gtk_widget_show(ui->search_box);
		gtk_arrow_set(GTK_ARROW(ui->search_button_arrow), GTK_ARROW_DOWN, GTK_SHADOW_NONE);
		gtk_widget_grab_focus(ui->search_entry);
		}
}

void pan_search_toggle_visible(PanWindow *pw, gboolean enable)
{
	PanViewSearchUi *ui = pw->search_ui;
	if (pw->fs) return;

	if (enable)
		{
		if (gtk_widget_get_visible(ui->search_box))
			{
			gtk_widget_grab_focus(ui->search_entry);
			}
		else
			{
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), TRUE);
			}
		}
	else
		{
		if (gtk_widget_get_visible(ui->search_entry))
			{
			if (gtk_widget_has_focus(ui->search_entry))
				{
				gtk_widget_grab_focus(GTK_WIDGET(pw->imd->widget));
				}
			gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ui->search_button), FALSE);
			}
		}
}