PIKApp/libpikawidgets/pikabrowser.c

497 lines
13 KiB
C
Raw Permalink Normal View History

2023-09-26 00:35:21 +02:00
/* LIBPIKA - The PIKA Library
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
*
* pikabrowser.c
* Copyright (C) 2005 Michael Natterer <mitch@gimp.org>
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <gegl.h>
#include <gtk/gtk.h>
#include "pikawidgetstypes.h"
#include "pikawidgets.h"
#include "pikawidgetsmarshal.h"
#include "libpika/libpika-intl.h"
/**
* SECTION: pikabrowser
* @title: PikaBrowser
* @short_description: A base class for a documentation browser.
*
* A base class for a documentation browser.
**/
enum
{
SEARCH,
LAST_SIGNAL
};
struct _PikaBrowserPrivate
{
GtkWidget *left_vbox;
GtkWidget *search_entry;
guint search_timeout_id;
GtkWidget *search_type_combo;
gint search_type;
GtkWidget *count_label;
GtkWidget *right_vbox;
GtkWidget *right_widget;
};
#define GET_PRIVATE(obj) (((PikaBrowser *) (obj))->priv)
static void pika_browser_dispose (GObject *object);
static void pika_browser_combo_changed (GtkComboBox *combo,
PikaBrowser *browser);
static void pika_browser_entry_changed (GtkEntry *entry,
PikaBrowser *browser);
static void pika_browser_entry_icon_press (GtkEntry *entry,
GtkEntryIconPosition icon_pos,
GdkEvent *event,
PikaBrowser *browser);
static gboolean pika_browser_search_timeout (gpointer data);
G_DEFINE_TYPE_WITH_PRIVATE (PikaBrowser, pika_browser, GTK_TYPE_PANED)
#define parent_class pika_browser_parent_class
static guint browser_signals[LAST_SIGNAL] = { 0 };
static void
pika_browser_class_init (PikaBrowserClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
browser_signals[SEARCH] =
g_signal_new ("search",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET (PikaBrowserClass, search),
NULL, NULL,
_pika_widgets_marshal_VOID__STRING_INT,
G_TYPE_NONE, 2,
G_TYPE_STRING,
G_TYPE_INT);
object_class->dispose = pika_browser_dispose;
klass->search = NULL;
}
static void
pika_browser_init (PikaBrowser *browser)
{
PikaBrowserPrivate *priv;
GtkWidget *hbox;
GtkWidget *label;
GtkWidget *scrolled_window;
GtkWidget *viewport;
browser->priv = pika_browser_get_instance_private (browser);
priv = GET_PRIVATE (browser);
gtk_orientable_set_orientation (GTK_ORIENTABLE (browser),
GTK_ORIENTATION_HORIZONTAL);
priv->search_type = -1;
priv->left_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
gtk_paned_pack1 (GTK_PANED (browser), priv->left_vbox, FALSE, TRUE);
gtk_widget_show (priv->left_vbox);
/* search entry */
hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
gtk_box_pack_start (GTK_BOX (priv->left_vbox), hbox, FALSE, FALSE, 0);
gtk_widget_show (hbox);
label = gtk_label_new_with_mnemonic (_("_Search:"));
gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
gtk_widget_show (label);
priv->search_entry = gtk_entry_new ();
gtk_box_pack_start (GTK_BOX (hbox), priv->search_entry, TRUE, TRUE, 0);
gtk_widget_show (priv->search_entry);
gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->search_entry);
g_signal_connect (priv->search_entry, "changed",
G_CALLBACK (pika_browser_entry_changed),
browser);
gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->search_entry),
GTK_ENTRY_ICON_SECONDARY, "edit-clear");
gtk_entry_set_icon_activatable (GTK_ENTRY (priv->search_entry),
GTK_ENTRY_ICON_SECONDARY, TRUE);
gtk_entry_set_icon_sensitive (GTK_ENTRY (priv->search_entry),
GTK_ENTRY_ICON_SECONDARY, FALSE);
g_signal_connect (priv->search_entry, "icon-press",
G_CALLBACK (pika_browser_entry_icon_press),
browser);
/* count label */
priv->count_label = gtk_label_new (_("No matches"));
gtk_label_set_xalign (GTK_LABEL (priv->count_label), 0.0);
pika_label_set_attributes (GTK_LABEL (priv->count_label),
PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
-1);
gtk_box_pack_end (GTK_BOX (priv->left_vbox), priv->count_label,
FALSE, FALSE, 0);
gtk_widget_show (priv->count_label);
/* scrolled window */
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC,
GTK_POLICY_ALWAYS);
gtk_paned_pack2 (GTK_PANED (browser), scrolled_window, TRUE, TRUE);
gtk_widget_show (scrolled_window);
viewport = gtk_viewport_new (NULL, NULL);
gtk_container_add (GTK_CONTAINER (scrolled_window), viewport);
gtk_widget_show (viewport);
priv->right_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
gtk_container_set_border_width (GTK_CONTAINER (priv->right_vbox), 12);
gtk_container_add (GTK_CONTAINER (viewport), priv->right_vbox);
gtk_widget_show (priv->right_vbox);
gtk_widget_grab_focus (priv->search_entry);
}
static void
pika_browser_dispose (GObject *object)
{
PikaBrowserPrivate *priv = GET_PRIVATE (object);
if (priv->search_timeout_id)
{
g_source_remove (priv->search_timeout_id);
priv->search_timeout_id = 0;
}
G_OBJECT_CLASS (parent_class)->dispose (object);
}
/* public functions */
/**
* pika_browser_new:
*
* Create a new #PikaBrowser widget.
*
* Returns: a newly created #PikaBrowser.
*
* Since: 2.4
**/
GtkWidget *
pika_browser_new (void)
{
return g_object_new (PIKA_TYPE_BROWSER, NULL);
}
/**
* pika_browser_add_search_types: (skip)
* @browser: a #PikaBrowser widget
* @first_type_label: the label of the first search type
* @first_type_id: an integer that identifies the first search type
* @...: a %NULL-terminated list of more labels and ids.
*
* Populates the #GtkComboBox with search types.
*
* Since: 2.4
**/
void
pika_browser_add_search_types (PikaBrowser *browser,
const gchar *first_type_label,
gint first_type_id,
...)
{
PikaBrowserPrivate *priv;
g_return_if_fail (PIKA_IS_BROWSER (browser));
g_return_if_fail (first_type_label != NULL);
priv = GET_PRIVATE (browser);
if (! priv->search_type_combo)
{
GtkWidget *combo;
va_list args;
va_start (args, first_type_id);
combo = pika_int_combo_box_new_valist (first_type_label,
first_type_id,
args);
va_end (args);
gtk_widget_set_focus_on_click (combo, FALSE);
priv->search_type_combo = combo;
priv->search_type = first_type_id;
gtk_box_pack_end (GTK_BOX (gtk_widget_get_parent (priv->search_entry)),
combo, FALSE, FALSE, 0);
gtk_widget_show (combo);
pika_int_combo_box_connect (PIKA_INT_COMBO_BOX (combo),
priv->search_type,
G_CALLBACK (pika_int_combo_box_get_active),
&priv->search_type, NULL);
g_signal_connect (combo, "changed",
G_CALLBACK (pika_browser_combo_changed),
browser);
}
else
{
pika_int_combo_box_append (PIKA_INT_COMBO_BOX (priv->search_type_combo),
first_type_label, first_type_id,
NULL);
}
}
/**
* pika_browser_get_left_vbox:
* @browser: a #PikaBrowser widget
*
* Returns: (transfer none) (type GtkBox): The left vbox.
*
* Since: 3.0
**/
GtkWidget *
pika_browser_get_left_vbox (PikaBrowser *browser)
{
PikaBrowserPrivate *priv;
g_return_val_if_fail (PIKA_IS_BROWSER (browser), NULL);
priv = GET_PRIVATE (browser);
return priv->left_vbox;
}
/**
* pika_browser_get_right_vbox:
* @browser: a #PikaBrowser widget
*
* Returns: (transfer none) (type GtkBox): The right vbox.
*
* Since: 3.0
**/
GtkWidget *
pika_browser_get_right_vbox (PikaBrowser *browser)
{
PikaBrowserPrivate *priv;
g_return_val_if_fail (PIKA_IS_BROWSER (browser), NULL);
priv = GET_PRIVATE (browser);
return priv->right_vbox;
}
/**
* pika_browser_set_search_summary:
* @browser: a #PikaBrowser widget
* @summary: a string describing the search result
*
* Sets the search summary text.
*
* Since: 3.0
**/
void
pika_browser_set_search_summary (PikaBrowser *browser,
const gchar *summary)
{
PikaBrowserPrivate *priv;
g_return_if_fail (PIKA_IS_BROWSER (browser));
g_return_if_fail (summary != NULL);
priv = GET_PRIVATE (browser);
gtk_label_set_text (GTK_LABEL (priv->count_label), summary);
}
/**
* pika_browser_set_widget:
* @browser: a #PikaBrowser widget
* @widget: a #GtkWidget
*
* Sets the widget to appear on the right side of the @browser.
*
* Since: 2.4
**/
void
pika_browser_set_widget (PikaBrowser *browser,
GtkWidget *widget)
{
PikaBrowserPrivate *priv;
g_return_if_fail (PIKA_IS_BROWSER (browser));
g_return_if_fail (widget == NULL || GTK_IS_WIDGET (widget));
priv = GET_PRIVATE (browser);
if (widget == priv->right_widget)
return;
if (priv->right_widget)
gtk_container_remove (GTK_CONTAINER (priv->right_vbox),
priv->right_widget);
priv->right_widget = widget;
if (widget)
{
gtk_box_pack_start (GTK_BOX (priv->right_vbox), widget,
FALSE, FALSE, 0);
gtk_widget_show (widget);
}
}
/**
* pika_browser_show_message:
* @browser: a #PikaBrowser widget
* @message: text message
*
* Displays @message in the right side of the @browser. Unless the right
* side already contains a #GtkLabel, the widget previously added with
* pika_browser_set_widget() is removed and replaced by a #GtkLabel.
*
* Since: 2.4
**/
void
pika_browser_show_message (PikaBrowser *browser,
const gchar *message)
{
PikaBrowserPrivate *priv;
g_return_if_fail (PIKA_IS_BROWSER (browser));
g_return_if_fail (message != NULL);
priv = GET_PRIVATE (browser);
if (GTK_IS_LABEL (priv->right_widget))
{
gtk_label_set_text (GTK_LABEL (priv->right_widget), message);
}
else
{
GtkWidget *label = gtk_label_new (message);
pika_label_set_attributes (GTK_LABEL (label),
PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC,
-1);
pika_browser_set_widget (browser, label);
}
while (gtk_events_pending ())
gtk_main_iteration ();
}
/* private functions */
static void
pika_browser_queue_search (PikaBrowser *browser)
{
PikaBrowserPrivate *priv = GET_PRIVATE (browser);
if (priv->search_timeout_id)
g_source_remove (priv->search_timeout_id);
priv->search_timeout_id =
g_timeout_add (100, pika_browser_search_timeout, browser);
}
static void
pika_browser_combo_changed (GtkComboBox *combo,
PikaBrowser *browser)
{
pika_browser_queue_search (browser);
}
static void
pika_browser_entry_changed (GtkEntry *entry,
PikaBrowser *browser)
{
pika_browser_queue_search (browser);
gtk_entry_set_icon_sensitive (entry,
GTK_ENTRY_ICON_SECONDARY,
gtk_entry_get_text_length (entry) > 0);
}
static void
pika_browser_entry_icon_press (GtkEntry *entry,
GtkEntryIconPosition icon_pos,
GdkEvent *event,
PikaBrowser *browser)
{
GdkEventButton *bevent = (GdkEventButton *) event;
if (icon_pos == GTK_ENTRY_ICON_SECONDARY && bevent->button == 1)
{
gtk_entry_set_text (entry, "");
}
}
static gboolean
pika_browser_search_timeout (gpointer data)
{
PikaBrowserPrivate *priv = GET_PRIVATE (data);
const gchar *search_string;
search_string = gtk_entry_get_text (GTK_ENTRY (priv->search_entry));
if (! search_string)
search_string = "";
g_signal_emit (data, browser_signals[SEARCH], 0,
search_string, priv->search_type);
priv->search_timeout_id = 0;
return FALSE;
}