Initial checkin of Pika from heckimp
This commit is contained in:
31
app/gui/dbus-service.xml
Normal file
31
app/gui/dbus-service.xml
Normal file
@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
|
||||
<node>
|
||||
|
||||
<interface name="technology.heckin.PIKA.UI">
|
||||
|
||||
<method name="Open">
|
||||
<arg type="s" name="uri" direction="in" />
|
||||
<arg type="b" name="success" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="OpenAsNew">
|
||||
<arg type="s" name="uri" direction="in" />
|
||||
<arg type="b" name="success" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="BatchRun">
|
||||
<arg type="s" name="interpreter" direction="in" />
|
||||
<arg type="s" name="command" direction="in" />
|
||||
<arg type="b" name="success" direction="out" />
|
||||
</method>
|
||||
|
||||
<method name="Activate" />
|
||||
|
||||
<signal name="Opened">
|
||||
<arg type="s" name="uri" />
|
||||
</signal>
|
||||
|
||||
</interface>
|
||||
|
||||
</node>
|
499
app/gui/gui-message.c
Normal file
499
app/gui/gui-message.c
Normal file
@ -0,0 +1,499 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikawidgets/pikawidgets.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikaprogress.h"
|
||||
|
||||
#include "plug-in/pikaplugin.h"
|
||||
|
||||
#include "widgets/pikacriticaldialog.h"
|
||||
#include "widgets/pikadialogfactory.h"
|
||||
#include "widgets/pikadockable.h"
|
||||
#include "widgets/pikaerrorconsole.h"
|
||||
#include "widgets/pikaerrordialog.h"
|
||||
#include "widgets/pikaprogressdialog.h"
|
||||
#include "widgets/pikasessioninfo.h"
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
#include "widgets/pikawindowstrategy.h"
|
||||
|
||||
#include "about.h"
|
||||
|
||||
#include "gui-message.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
#define MAX_TRACES 3
|
||||
#define MAX_ERRORS 10
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
Pika *pika;
|
||||
gchar *domain;
|
||||
gchar *message;
|
||||
gchar *trace;
|
||||
GObject *handler;
|
||||
PikaMessageSeverity severity;
|
||||
} PikaLogMessageData;
|
||||
|
||||
|
||||
static gboolean gui_message_idle (gpointer user_data);
|
||||
static gboolean gui_message_error_console (Pika *pika,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message);
|
||||
static gboolean gui_message_error_dialog (Pika *pika,
|
||||
GObject *handler,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message,
|
||||
const gchar *trace);
|
||||
static void gui_message_console (PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message);
|
||||
static gchar * gui_message_format (PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message);
|
||||
|
||||
static GtkWidget * global_error_dialog (void);
|
||||
static GtkWidget * global_critical_dialog (void);
|
||||
|
||||
static void gui_message_reset_errors (GObject *object,
|
||||
gpointer user_data);
|
||||
|
||||
|
||||
static GMutex mutex;
|
||||
static gint n_traces = 0;
|
||||
static gint n_errors = 0;
|
||||
|
||||
|
||||
void
|
||||
gui_message (Pika *pika,
|
||||
GObject *handler,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message)
|
||||
{
|
||||
gchar *trace = NULL;
|
||||
gboolean gen_trace = FALSE;
|
||||
|
||||
switch (pika->message_handler)
|
||||
{
|
||||
case PIKA_ERROR_CONSOLE:
|
||||
if (gui_message_error_console (pika, severity, domain, message))
|
||||
return;
|
||||
|
||||
pika->message_handler = PIKA_MESSAGE_BOX;
|
||||
/* fallthru */
|
||||
|
||||
case PIKA_MESSAGE_BOX:
|
||||
if (severity >= PIKA_MESSAGE_BUG_WARNING)
|
||||
{
|
||||
g_mutex_lock (&mutex);
|
||||
/* Trace creation can be time consuming so don't block the
|
||||
* mutex for too long and only increment and set a boolean
|
||||
* here.
|
||||
*/
|
||||
if (n_traces < MAX_TRACES)
|
||||
{
|
||||
gen_trace = TRUE;
|
||||
n_traces++;
|
||||
}
|
||||
g_mutex_unlock (&mutex);
|
||||
}
|
||||
|
||||
if (gen_trace)
|
||||
{
|
||||
/* We need to create the trace here because for multi-thread
|
||||
* errors (i.e. non-PIKA ones), the backtrace after a idle
|
||||
* function will simply be useless. It needs to happen in the
|
||||
* buggy thread to be meaningful.
|
||||
*/
|
||||
pika_stack_trace_print (NULL, NULL, &trace);
|
||||
}
|
||||
|
||||
if (g_strcmp0 (PIKA_ACRONYM, domain) != 0)
|
||||
{
|
||||
/* Handle non-PIKA messages in a multi-thread safe way,
|
||||
* because we can't know for sure whether the log message may
|
||||
* not have been called from a thread other than the main one.
|
||||
*/
|
||||
PikaLogMessageData *data;
|
||||
|
||||
data = g_new0 (PikaLogMessageData, 1);
|
||||
data->pika = pika;
|
||||
data->domain = g_strdup (domain);
|
||||
data->message = g_strdup (message);
|
||||
data->trace = trace;
|
||||
data->handler = handler? g_object_ref (handler) : NULL;
|
||||
data->severity = severity;
|
||||
|
||||
gdk_threads_add_idle_full (G_PRIORITY_DEFAULT_IDLE,
|
||||
gui_message_idle,
|
||||
data, g_free);
|
||||
return;
|
||||
}
|
||||
if (gui_message_error_dialog (pika, handler, severity,
|
||||
domain, message, trace))
|
||||
break;
|
||||
|
||||
pika->message_handler = PIKA_CONSOLE;
|
||||
/* fallthru */
|
||||
|
||||
case PIKA_CONSOLE:
|
||||
gui_message_console (severity, domain, message);
|
||||
break;
|
||||
}
|
||||
if (trace)
|
||||
g_free (trace);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_message_idle (gpointer user_data)
|
||||
{
|
||||
PikaLogMessageData *data = (PikaLogMessageData *) user_data;
|
||||
|
||||
if (! gui_message_error_dialog (data->pika,
|
||||
data->handler,
|
||||
data->severity,
|
||||
data->domain,
|
||||
data->message,
|
||||
data->trace))
|
||||
{
|
||||
gui_message_console (data->severity,
|
||||
data->domain,
|
||||
data->message);
|
||||
}
|
||||
g_free (data->domain);
|
||||
g_free (data->message);
|
||||
if (data->trace)
|
||||
g_free (data->trace);
|
||||
if (data->handler)
|
||||
g_object_unref (data->handler);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_message_error_console (Pika *pika,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message)
|
||||
{
|
||||
GtkWidget *dockable;
|
||||
|
||||
dockable = pika_dialog_factory_find_widget (pika_dialog_factory_get_singleton (),
|
||||
"pika-error-console");
|
||||
|
||||
/* avoid raising the error console for unhighlighted messages */
|
||||
if (dockable)
|
||||
{
|
||||
GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable));
|
||||
|
||||
if (PIKA_ERROR_CONSOLE (child)->highlight[severity])
|
||||
dockable = NULL;
|
||||
}
|
||||
|
||||
if (! dockable)
|
||||
{
|
||||
GdkMonitor *monitor = pika_get_monitor_at_pointer ();
|
||||
|
||||
dockable =
|
||||
pika_window_strategy_show_dockable_dialog (PIKA_WINDOW_STRATEGY (pika_get_window_strategy (pika)),
|
||||
pika,
|
||||
pika_dialog_factory_get_singleton (),
|
||||
monitor,
|
||||
"pika-error-console");
|
||||
}
|
||||
|
||||
if (dockable)
|
||||
{
|
||||
GtkWidget *child = gtk_bin_get_child (GTK_BIN (dockable));
|
||||
|
||||
pika_error_console_add (PIKA_ERROR_CONSOLE (child),
|
||||
severity, domain, message);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
progress_error_dialog_unset (PikaProgress *progress)
|
||||
{
|
||||
g_object_set_data (G_OBJECT (progress), "pika-error-dialog", NULL);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
progress_error_dialog (PikaProgress *progress)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PROGRESS (progress), NULL);
|
||||
|
||||
dialog = g_object_get_data (G_OBJECT (progress), "pika-error-dialog");
|
||||
|
||||
if (! dialog)
|
||||
{
|
||||
dialog = pika_error_dialog_new (_("PIKA Message"));
|
||||
|
||||
g_object_set_data (G_OBJECT (progress), "pika-error-dialog", dialog);
|
||||
|
||||
g_signal_connect_object (dialog, "destroy",
|
||||
G_CALLBACK (progress_error_dialog_unset),
|
||||
progress, G_CONNECT_SWAPPED);
|
||||
|
||||
if (GTK_IS_WIDGET (progress))
|
||||
{
|
||||
GtkWidget *toplevel = gtk_widget_get_toplevel (GTK_WIDGET (progress));
|
||||
|
||||
if (GTK_IS_WINDOW (toplevel))
|
||||
gtk_window_set_transient_for (GTK_WINDOW (dialog),
|
||||
GTK_WINDOW (toplevel));
|
||||
}
|
||||
else
|
||||
{
|
||||
guint32 window_id = pika_progress_get_window_id (progress);
|
||||
|
||||
if (window_id)
|
||||
pika_window_set_transient_for (GTK_WINDOW (dialog), window_id);
|
||||
}
|
||||
}
|
||||
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_message_error_dialog (Pika *pika,
|
||||
GObject *handler,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message,
|
||||
const gchar *trace)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkMessageType type = GTK_MESSAGE_ERROR;
|
||||
|
||||
switch (severity)
|
||||
{
|
||||
case PIKA_MESSAGE_INFO:
|
||||
type = GTK_MESSAGE_INFO;
|
||||
break;
|
||||
case PIKA_MESSAGE_WARNING:
|
||||
type = GTK_MESSAGE_WARNING;
|
||||
break;
|
||||
case PIKA_MESSAGE_ERROR:
|
||||
type = GTK_MESSAGE_ERROR;
|
||||
break;
|
||||
case PIKA_MESSAGE_BUG_WARNING:
|
||||
case PIKA_MESSAGE_BUG_CRITICAL:
|
||||
type = GTK_MESSAGE_OTHER;
|
||||
break;
|
||||
}
|
||||
|
||||
if (severity >= PIKA_MESSAGE_BUG_WARNING)
|
||||
{
|
||||
/* Process differently programming errors.
|
||||
* The reason is that we will generate traces, which will take
|
||||
* significant place, and cannot be processed as a progress
|
||||
* message or in the global dialog. It will require its own
|
||||
* dedicated dialog which will encourage people to report the bug.
|
||||
*/
|
||||
gboolean gui_error = FALSE;
|
||||
|
||||
g_mutex_lock (&mutex);
|
||||
if (n_errors < MAX_ERRORS)
|
||||
{
|
||||
gui_error = TRUE;
|
||||
n_errors++;
|
||||
}
|
||||
g_mutex_unlock (&mutex);
|
||||
|
||||
if (gui_error || trace)
|
||||
{
|
||||
gchar *text;
|
||||
|
||||
dialog = global_critical_dialog ();
|
||||
|
||||
text = gui_message_format (severity, domain, message);
|
||||
pika_critical_dialog_add (dialog, text, trace, FALSE, NULL, 0);
|
||||
|
||||
gtk_widget_show (dialog);
|
||||
|
||||
g_free (text);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
else
|
||||
{
|
||||
const gchar *reason = "Message";
|
||||
|
||||
pika_enum_get_value (PIKA_TYPE_MESSAGE_SEVERITY, severity,
|
||||
NULL, NULL, &reason, NULL);
|
||||
|
||||
/* Since we overridden glib default's WARNING and CRITICAL
|
||||
* handler, if we decide not to handle this error in the end,
|
||||
* let's just print it in terminal in a similar fashion as
|
||||
* glib's default handler (though without the fancy terminal
|
||||
* colors right now).
|
||||
*/
|
||||
g_printerr ("%s-%s: %s\n", domain, reason, message);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else if (PIKA_IS_PROGRESS (handler))
|
||||
{
|
||||
/* If there's already an error dialog associated with this
|
||||
* progress, then continue without trying pika_progress_message().
|
||||
*/
|
||||
if (! g_object_get_data (handler, "pika-error-dialog") &&
|
||||
pika_progress_message (PIKA_PROGRESS (handler), pika,
|
||||
severity, domain, message))
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
else if (GTK_IS_WIDGET (handler))
|
||||
{
|
||||
GtkWidget *parent = GTK_WIDGET (handler);
|
||||
|
||||
dialog =
|
||||
gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (parent)),
|
||||
GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
type, GTK_BUTTONS_OK,
|
||||
"%s", message);
|
||||
|
||||
g_signal_connect (dialog, "response",
|
||||
G_CALLBACK (gtk_widget_destroy),
|
||||
NULL);
|
||||
|
||||
gtk_widget_show (dialog);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (PIKA_IS_PROGRESS (handler) && ! PIKA_IS_PROGRESS_DIALOG (handler))
|
||||
dialog = progress_error_dialog (PIKA_PROGRESS (handler));
|
||||
else
|
||||
dialog = global_error_dialog ();
|
||||
|
||||
if (dialog)
|
||||
{
|
||||
gtk_window_set_keep_above (GTK_WINDOW (dialog), TRUE);
|
||||
pika_error_dialog_add (PIKA_ERROR_DIALOG (dialog),
|
||||
pika_get_message_icon_name (severity),
|
||||
domain, message);
|
||||
gtk_window_present (GTK_WINDOW (dialog));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static void
|
||||
gui_message_console (PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message)
|
||||
{
|
||||
gchar *formatted_message;
|
||||
|
||||
formatted_message = gui_message_format (severity, domain, message);
|
||||
g_printerr ("%s\n\n", formatted_message);
|
||||
g_free (formatted_message);
|
||||
}
|
||||
|
||||
static gchar *
|
||||
gui_message_format (PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message)
|
||||
{
|
||||
const gchar *desc = "Message";
|
||||
gchar *formatted_message;
|
||||
|
||||
pika_enum_get_value (PIKA_TYPE_MESSAGE_SEVERITY, severity,
|
||||
NULL, NULL, &desc, NULL);
|
||||
|
||||
formatted_message = g_strdup_printf ("%s-%s: %s", domain, desc, message);
|
||||
|
||||
return formatted_message;
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
global_error_dialog (void)
|
||||
{
|
||||
GdkMonitor *monitor = pika_get_monitor_at_pointer ();
|
||||
|
||||
return pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (),
|
||||
monitor,
|
||||
NULL /*ui_manager*/,
|
||||
NULL,
|
||||
"pika-error-dialog", -1,
|
||||
FALSE);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
global_critical_dialog (void)
|
||||
{
|
||||
GdkMonitor *monitor = pika_get_monitor_at_pointer ();
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (),
|
||||
monitor,
|
||||
NULL /*ui_manager*/,
|
||||
NULL,
|
||||
"pika-critical-dialog", -1,
|
||||
FALSE);
|
||||
g_signal_handlers_disconnect_by_func (dialog,
|
||||
gui_message_reset_errors,
|
||||
NULL);
|
||||
g_signal_connect (dialog, "destroy",
|
||||
G_CALLBACK (gui_message_reset_errors),
|
||||
NULL);
|
||||
return dialog;
|
||||
}
|
||||
|
||||
static void
|
||||
gui_message_reset_errors (GObject *object,
|
||||
gpointer user_data)
|
||||
{
|
||||
g_mutex_lock (&mutex);
|
||||
n_errors = 0;
|
||||
n_traces = 0;
|
||||
g_mutex_unlock (&mutex);
|
||||
}
|
33
app/gui/gui-message.h
Normal file
33
app/gui/gui-message.h
Normal file
@ -0,0 +1,33 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GUI_MESSAGE_H__
|
||||
#define __GUI_MESSAGE_H__
|
||||
|
||||
|
||||
void gui_message (Pika *pika,
|
||||
GObject *handler,
|
||||
PikaMessageSeverity severity,
|
||||
const gchar *domain,
|
||||
const gchar *message);
|
||||
|
||||
|
||||
#endif /* __GUI_VTABLE_H__ */
|
32
app/gui/gui-types.h
Normal file
32
app/gui/gui-types.h
Normal file
@ -0,0 +1,32 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GUI_TYPES_H__
|
||||
#define __GUI_TYPES_H__
|
||||
|
||||
|
||||
#include "tools/tools-types.h"
|
||||
#include "dialogs/dialogs-types.h"
|
||||
#include "menus/menus-types.h"
|
||||
|
||||
#include "pikaapp.h"
|
||||
|
||||
#endif /* __GUI_TYPES_H__ */
|
413
app/gui/gui-unique.c
Normal file
413
app/gui/gui-unique.c
Normal file
@ -0,0 +1,413 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef GDK_WINDOWING_QUARTZ
|
||||
#import <AppKit/AppKit.h>
|
||||
#endif
|
||||
|
||||
#include "gui/gui-types.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikacontainer.h"
|
||||
|
||||
#include "display/pikadisplay.h"
|
||||
#include "display/pikadisplayshell.h"
|
||||
#include "display/pikaimagewindow.h"
|
||||
|
||||
#include "file/file-open.h"
|
||||
|
||||
#include "pikadbusservice.h"
|
||||
#include "gui-unique.h"
|
||||
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
|
||||
static void gui_unique_win32_init (Pika *pika);
|
||||
static void gui_unique_win32_exit (void);
|
||||
|
||||
static Pika *unique_pika = NULL;
|
||||
static HWND proxy_window = NULL;
|
||||
|
||||
#elif defined (GDK_WINDOWING_QUARTZ)
|
||||
|
||||
static void gui_unique_quartz_init (Pika *pika);
|
||||
static void gui_unique_quartz_exit (void);
|
||||
|
||||
@interface PikaAppleEventHandler : NSObject {}
|
||||
- (void) handleEvent:(NSAppleEventDescriptor *) inEvent
|
||||
andReplyWith:(NSAppleEventDescriptor *) replyEvent;
|
||||
@end
|
||||
|
||||
static Pika *unique_pika = NULL;
|
||||
static PikaAppleEventHandler *event_handler = NULL;
|
||||
|
||||
#else
|
||||
|
||||
static void gui_dbus_service_init (Pika *pika);
|
||||
static void gui_dbus_service_exit (void);
|
||||
|
||||
static GDBusObjectManagerServer *dbus_manager = NULL;
|
||||
static guint dbus_name_id = 0;
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
void
|
||||
gui_unique_init (Pika *pika)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
gui_unique_win32_init (pika);
|
||||
#elif defined (GDK_WINDOWING_QUARTZ)
|
||||
gui_unique_quartz_init (pika);
|
||||
#else
|
||||
gui_dbus_service_init (pika);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
gui_unique_exit (void)
|
||||
{
|
||||
#ifdef G_OS_WIN32
|
||||
gui_unique_win32_exit ();
|
||||
#elif defined (GDK_WINDOWING_QUARTZ)
|
||||
gui_unique_quartz_exit ();
|
||||
#else
|
||||
gui_dbus_service_exit ();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GFile *file;
|
||||
gboolean as_new;
|
||||
} IdleOpenData;
|
||||
|
||||
static IdleOpenData *
|
||||
idle_open_data_new (GFile *file,
|
||||
gboolean as_new)
|
||||
{
|
||||
IdleOpenData *data = g_slice_new0 (IdleOpenData);
|
||||
|
||||
data->file = g_object_ref (file);
|
||||
data->as_new = as_new;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
idle_open_data_free (IdleOpenData *data)
|
||||
{
|
||||
g_object_unref (data->file);
|
||||
g_slice_free (IdleOpenData, data);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_unique_win32_idle_open (IdleOpenData *data)
|
||||
{
|
||||
/* We want to be called again later in case that PIKA is not fully
|
||||
* started yet.
|
||||
*/
|
||||
if (! pika_is_restored (unique_pika))
|
||||
return TRUE;
|
||||
|
||||
if (data->file)
|
||||
{
|
||||
file_open_from_command_line (unique_pika, data->file,
|
||||
data->as_new, NULL);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* raise the first display */
|
||||
PikaObject *display;
|
||||
|
||||
display = pika_container_get_first_child (unique_pika->displays);
|
||||
|
||||
pika_display_shell_present (pika_display_get_shell (PIKA_DISPLAY (display)));
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static LRESULT CALLBACK
|
||||
gui_unique_win32_message_handler (HWND hWnd,
|
||||
UINT uMsg,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam)
|
||||
{
|
||||
switch (uMsg)
|
||||
{
|
||||
case WM_COPYDATA:
|
||||
if (unique_pika)
|
||||
{
|
||||
COPYDATASTRUCT *copydata = (COPYDATASTRUCT *) lParam;
|
||||
PikaObject *display;
|
||||
|
||||
if (copydata->cbData > 0)
|
||||
{
|
||||
GSource *source;
|
||||
GClosure *closure;
|
||||
GFile *file;
|
||||
IdleOpenData *data;
|
||||
|
||||
file = g_file_new_for_uri (copydata->lpData);
|
||||
|
||||
data = idle_open_data_new (file,
|
||||
copydata->dwData != 0);
|
||||
|
||||
g_object_unref (file);
|
||||
|
||||
closure = g_cclosure_new (G_CALLBACK (gui_unique_win32_idle_open),
|
||||
data,
|
||||
(GClosureNotify) idle_open_data_free);
|
||||
|
||||
g_object_watch_closure (G_OBJECT (unique_pika), closure);
|
||||
|
||||
source = g_idle_source_new ();
|
||||
g_source_set_priority (source, G_PRIORITY_LOW);
|
||||
g_source_set_closure (source, closure);
|
||||
g_source_attach (source, NULL);
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
/* Deiconify the window if minimized. */
|
||||
display = pika_container_get_first_child (unique_pika->displays);
|
||||
if (display)
|
||||
pika_display_shell_present (pika_display_get_shell (PIKA_DISPLAY (display)));
|
||||
}
|
||||
return TRUE;
|
||||
|
||||
default:
|
||||
return DefWindowProcW (hWnd, uMsg, wParam, lParam);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gui_unique_win32_init (Pika *pika)
|
||||
{
|
||||
WNDCLASSW wc;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
g_return_if_fail (unique_pika == NULL);
|
||||
|
||||
unique_pika = pika;
|
||||
|
||||
/* register window class for proxy window */
|
||||
memset (&wc, 0, sizeof (wc));
|
||||
|
||||
wc.hInstance = GetModuleHandleW (NULL);
|
||||
wc.lpfnWndProc = gui_unique_win32_message_handler;
|
||||
wc.lpszClassName = PIKA_UNIQUE_WIN32_WINDOW_CLASS;
|
||||
|
||||
RegisterClassW (&wc);
|
||||
|
||||
proxy_window = CreateWindowExW (0,
|
||||
PIKA_UNIQUE_WIN32_WINDOW_CLASS,
|
||||
PIKA_UNIQUE_WIN32_WINDOW_NAME,
|
||||
WS_POPUP, 0, 0, 1, 1, NULL, NULL, wc.hInstance, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_unique_win32_exit (void)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (unique_pika));
|
||||
|
||||
unique_pika = NULL;
|
||||
|
||||
DestroyWindow (proxy_window);
|
||||
}
|
||||
|
||||
#elif defined (GDK_WINDOWING_QUARTZ)
|
||||
|
||||
static gboolean
|
||||
gui_unique_quartz_idle_open (GFile *file)
|
||||
{
|
||||
/* We want to be called again later in case that PIKA is not fully
|
||||
* started yet.
|
||||
*/
|
||||
if (! pika_is_restored (unique_pika))
|
||||
return TRUE;
|
||||
|
||||
if (file)
|
||||
{
|
||||
file_open_from_command_line (unique_pika, file, FALSE, NULL);
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
@implementation PikaAppleEventHandler
|
||||
- (void) handleEvent: (NSAppleEventDescriptor *) inEvent
|
||||
andReplyWith: (NSAppleEventDescriptor *) replyEvent
|
||||
{
|
||||
NSAutoreleasePool *urlpool;
|
||||
NSInteger count;
|
||||
NSInteger i;
|
||||
|
||||
urlpool = [[NSAutoreleasePool alloc] init];
|
||||
|
||||
count = [inEvent numberOfItems];
|
||||
|
||||
for (i = 1; i <= count; i++)
|
||||
{
|
||||
NSURL *url;
|
||||
const gchar *path;
|
||||
GSource *source;
|
||||
GClosure *closure;
|
||||
|
||||
url = [NSURL URLWithString: [[inEvent descriptorAtIndex: i] stringValue]];
|
||||
path = [[url path] UTF8String];
|
||||
|
||||
closure = g_cclosure_new (G_CALLBACK (gui_unique_quartz_idle_open),
|
||||
g_file_new_for_path (path),
|
||||
(GClosureNotify) g_object_unref);
|
||||
|
||||
g_object_watch_closure (G_OBJECT (unique_pika), closure);
|
||||
|
||||
source = g_idle_source_new ();
|
||||
g_source_set_priority (source, G_PRIORITY_LOW);
|
||||
g_source_set_closure (source, closure);
|
||||
g_source_attach (source, NULL);
|
||||
g_source_unref (source);
|
||||
}
|
||||
|
||||
[urlpool drain];
|
||||
}
|
||||
@end
|
||||
|
||||
static void
|
||||
gui_unique_quartz_init (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
g_return_if_fail (unique_pika == NULL);
|
||||
|
||||
unique_pika = pika;
|
||||
|
||||
/* Using the event handler is a hack, it is necessary because
|
||||
* gtkosx_application will drop the file open events if any
|
||||
* event processing is done before gtkosx_application_ready is
|
||||
* called, which we unfortuantly can't avoid doing right now.
|
||||
*/
|
||||
event_handler = [[PikaAppleEventHandler alloc] init];
|
||||
|
||||
[[NSAppleEventManager sharedAppleEventManager]
|
||||
setEventHandler: event_handler
|
||||
andSelector: @selector (handleEvent: andReplyWith:)
|
||||
forEventClass: kCoreEventClass
|
||||
andEventID: kAEOpenDocuments];
|
||||
}
|
||||
|
||||
static void
|
||||
gui_unique_quartz_exit (void)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (unique_pika));
|
||||
|
||||
unique_pika = NULL;
|
||||
|
||||
[[NSAppleEventManager sharedAppleEventManager]
|
||||
removeEventHandlerForEventClass: kCoreEventClass
|
||||
andEventID: kAEOpenDocuments];
|
||||
|
||||
[event_handler release];
|
||||
|
||||
event_handler = NULL;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void
|
||||
gui_dbus_bus_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
Pika *pika)
|
||||
{
|
||||
GDBusObjectSkeleton *object;
|
||||
GObject *service;
|
||||
|
||||
/* this should use PIKA_DBUS_SERVICE_PATH, but that's historically wrong */
|
||||
dbus_manager = g_dbus_object_manager_server_new ("/technology.heckin/PIKA");
|
||||
|
||||
object = g_dbus_object_skeleton_new (PIKA_DBUS_INTERFACE_PATH);
|
||||
|
||||
service = pika_dbus_service_new (pika);
|
||||
g_dbus_object_skeleton_add_interface (object,
|
||||
G_DBUS_INTERFACE_SKELETON (service));
|
||||
g_object_unref (service);
|
||||
|
||||
g_dbus_object_manager_server_export (dbus_manager, object);
|
||||
g_object_unref (object);
|
||||
|
||||
g_dbus_object_manager_server_set_connection (dbus_manager, connection);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_dbus_name_acquired (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
Pika *pika)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
gui_dbus_name_lost (GDBusConnection *connection,
|
||||
const gchar *name,
|
||||
Pika *pika)
|
||||
{
|
||||
if (connection == NULL)
|
||||
g_printerr ("%s: connection to the bus cannot be established.\n",
|
||||
G_STRFUNC);
|
||||
else
|
||||
g_printerr ("%s: the name \"%s\" could not be acquired on the bus.\n",
|
||||
G_STRFUNC, name);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_dbus_service_init (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
g_return_if_fail (dbus_name_id == 0);
|
||||
|
||||
dbus_name_id = g_bus_own_name (G_BUS_TYPE_SESSION,
|
||||
PIKA_DBUS_SERVICE_NAME,
|
||||
G_BUS_NAME_OWNER_FLAGS_NONE,
|
||||
(GBusAcquiredCallback) gui_dbus_bus_acquired,
|
||||
(GBusNameAcquiredCallback) gui_dbus_name_acquired,
|
||||
(GBusNameLostCallback) gui_dbus_name_lost,
|
||||
pika, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_dbus_service_exit (void)
|
||||
{
|
||||
g_bus_unown_name (dbus_name_id);
|
||||
g_clear_object (&dbus_manager);
|
||||
}
|
||||
|
||||
#endif
|
35
app/gui/gui-unique.h
Normal file
35
app/gui/gui-unique.h
Normal file
@ -0,0 +1,35 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GUI_UNIQUE_H__
|
||||
#define __GUI_UNIQUE_H__
|
||||
|
||||
#ifdef G_OS_WIN32
|
||||
#define PIKA_UNIQUE_WIN32_WINDOW_CLASS L"PikaWin32UniqueHandler"
|
||||
#define PIKA_UNIQUE_WIN32_WINDOW_NAME L"PikaProxy"
|
||||
#endif
|
||||
|
||||
|
||||
void gui_unique_init (Pika *pika);
|
||||
void gui_unique_exit (void);
|
||||
|
||||
|
||||
#endif /* __GUI_UNIQUE_H__ */
|
1070
app/gui/gui-vtable.c
Normal file
1070
app/gui/gui-vtable.c
Normal file
File diff suppressed because it is too large
Load Diff
34
app/gui/gui-vtable.h
Normal file
34
app/gui/gui-vtable.h
Normal file
@ -0,0 +1,34 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GUI_VTABLE_H__
|
||||
#define __GUI_VTABLE_H__
|
||||
|
||||
|
||||
void gui_vtable_init (Pika *pika);
|
||||
|
||||
/* this function lives in gui.c but must only be used from gui-vtable.c;
|
||||
* also, gui.h can't contain any Gdk types.
|
||||
*/
|
||||
GdkMonitor * gui_get_initial_monitor (Pika *pika);
|
||||
|
||||
|
||||
#endif /* __GUI_VTABLE_H__ */
|
971
app/gui/gui.c
Normal file
971
app/gui/gui.c
Normal file
@ -0,0 +1,971 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikawidgets/pikawidgets.h"
|
||||
#include "libpikawidgets/pikawidgets-private.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
#include "pikaapp.h"
|
||||
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikacontainer.h"
|
||||
#include "core/pikacontext.h"
|
||||
#include "core/pikaimage.h"
|
||||
#include "core/pikatoolinfo.h"
|
||||
|
||||
#include "plug-in/pikaenvirontable.h"
|
||||
#include "plug-in/pikapluginmanager.h"
|
||||
|
||||
#include "display/pikadisplay.h"
|
||||
#include "display/pikadisplay-foreach.h"
|
||||
#include "display/pikadisplayshell.h"
|
||||
#include "display/pikastatusbar.h"
|
||||
|
||||
#include "tools/pika-tools.h"
|
||||
#include "tools/pikatool.h"
|
||||
#include "tools/tool_manager.h"
|
||||
|
||||
#include "widgets/pikaaction.h"
|
||||
#include "widgets/pikaactiongroup.h"
|
||||
#include "widgets/pikaaction-history.h"
|
||||
#include "widgets/pikaclipboard.h"
|
||||
#include "widgets/pikacolorselectorpalette.h"
|
||||
#include "widgets/pikacontrollers.h"
|
||||
#include "widgets/pikadevices.h"
|
||||
#include "widgets/pikadialogfactory.h"
|
||||
#include "widgets/pikadnd.h"
|
||||
#include "widgets/pikarender.h"
|
||||
#include "widgets/pikahelp.h"
|
||||
#include "widgets/pikahelp-ids.h"
|
||||
#include "widgets/pikamenufactory.h"
|
||||
#include "widgets/pikamessagebox.h"
|
||||
#include "widgets/pikaradioaction.h"
|
||||
#include "widgets/pikasessioninfo.h"
|
||||
#include "widgets/pikauimanager.h"
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
#include "widgets/pikalanguagestore-parser.h"
|
||||
|
||||
#include "actions/actions.h"
|
||||
#include "actions/windows-commands.h"
|
||||
|
||||
#include "menus/menus.h"
|
||||
|
||||
#include "dialogs/dialogs.h"
|
||||
|
||||
#include "pikauiconfigurer.h"
|
||||
#include "gui.h"
|
||||
#include "gui-unique.h"
|
||||
#include "gui-vtable.h"
|
||||
#include "icon-themes.h"
|
||||
#include "modifiers.h"
|
||||
#include "session.h"
|
||||
#include "splash.h"
|
||||
#include "themes.h"
|
||||
|
||||
#ifdef GDK_WINDOWING_QUARTZ
|
||||
#import <AppKit/AppKit.h>
|
||||
|
||||
/* Forward declare since we are building against old SDKs. */
|
||||
#if !defined(MAC_OS_X_VERSION_10_12) || \
|
||||
MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_12
|
||||
|
||||
@interface NSWindow(ForwardDeclarations)
|
||||
+ (void)setAllowsAutomaticWindowTabbing:(BOOL)allow;
|
||||
@end
|
||||
|
||||
#endif
|
||||
|
||||
#endif /* GDK_WINDOWING_QUARTZ */
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
/* local function prototypes */
|
||||
|
||||
static gchar * gui_sanity_check (void);
|
||||
static void gui_help_func (const gchar *help_id,
|
||||
gpointer help_data);
|
||||
static gboolean gui_get_background_func (PikaRGB *color);
|
||||
static gboolean gui_get_foreground_func (PikaRGB *color);
|
||||
|
||||
static void gui_initialize_after_callback (Pika *pika,
|
||||
PikaInitStatusFunc callback);
|
||||
|
||||
static void gui_restore_callback (Pika *pika,
|
||||
PikaInitStatusFunc callback);
|
||||
static void gui_restore_after_callback (Pika *pika,
|
||||
PikaInitStatusFunc callback);
|
||||
|
||||
static gboolean gui_exit_callback (Pika *pika,
|
||||
gboolean force);
|
||||
static gboolean gui_exit_after_callback (Pika *pika,
|
||||
gboolean force);
|
||||
|
||||
static void gui_show_help_button_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika);
|
||||
static void gui_user_manual_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika);
|
||||
static void gui_single_window_mode_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *pspec,
|
||||
PikaUIConfigurer *ui_configurer);
|
||||
|
||||
static void gui_clipboard_changed (Pika *pika);
|
||||
|
||||
static void gui_menu_show_tooltip (PikaUIManager *manager,
|
||||
const gchar *tooltip,
|
||||
Pika *pika);
|
||||
static void gui_menu_hide_tooltip (PikaUIManager *manager,
|
||||
Pika *pika);
|
||||
|
||||
static void gui_display_changed (PikaContext *context,
|
||||
PikaDisplay *display,
|
||||
Pika *pika);
|
||||
|
||||
static void gui_check_unique_accelerators (Pika *pika);
|
||||
|
||||
|
||||
/* private variables */
|
||||
|
||||
static Pika *the_gui_pika = NULL;
|
||||
static PikaUIConfigurer *ui_configurer = NULL;
|
||||
static GdkMonitor *initial_monitor = NULL;
|
||||
|
||||
|
||||
/* public functions */
|
||||
|
||||
void
|
||||
gui_libs_init (GOptionContext *context)
|
||||
{
|
||||
g_return_if_fail (context != NULL);
|
||||
|
||||
g_option_context_add_group (context, gtk_get_option_group (TRUE));
|
||||
|
||||
/* make the PikaDisplay type known by name early, needed for the PDB */
|
||||
g_type_class_ref (PIKA_TYPE_DISPLAY);
|
||||
}
|
||||
|
||||
void
|
||||
gui_abort (const gchar *abort_message)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkWidget *box;
|
||||
|
||||
g_return_if_fail (abort_message != NULL);
|
||||
|
||||
dialog = pika_dialog_new (_("PIKA Message"), "pika-abort",
|
||||
NULL, GTK_DIALOG_MODAL, NULL, NULL,
|
||||
|
||||
_("_OK"), GTK_RESPONSE_OK,
|
||||
|
||||
NULL);
|
||||
|
||||
gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
|
||||
|
||||
box = g_object_new (PIKA_TYPE_MESSAGE_BOX,
|
||||
"icon-name", PIKA_ICON_MASCOT_EEK,
|
||||
"border-width", 12,
|
||||
NULL);
|
||||
|
||||
pika_message_box_set_text (PIKA_MESSAGE_BOX (box), "%s", abort_message);
|
||||
|
||||
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
||||
box, TRUE, TRUE, 0);
|
||||
gtk_widget_show (box);
|
||||
|
||||
pika_dialog_run (PIKA_DIALOG (dialog));
|
||||
|
||||
exit (EXIT_FAILURE);
|
||||
}
|
||||
|
||||
/**
|
||||
* gui_init:
|
||||
* @pika:
|
||||
* @no_splash:
|
||||
* @test_base_dir: a base prefix directory.
|
||||
*
|
||||
* @test_base_dir should be set to %NULL in all our codebase except for
|
||||
* unit testing calls.
|
||||
*/
|
||||
PikaInitStatusFunc
|
||||
gui_init (Pika *pika,
|
||||
gboolean no_splash,
|
||||
PikaApp *app,
|
||||
const gchar *test_base_dir)
|
||||
{
|
||||
PikaInitStatusFunc status_callback = NULL;
|
||||
gchar *abort_message;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
g_return_val_if_fail (the_gui_pika == NULL, NULL);
|
||||
g_return_val_if_fail (PIKA_IS_APP (app) || app == NULL, NULL);
|
||||
|
||||
abort_message = gui_sanity_check ();
|
||||
if (abort_message)
|
||||
gui_abort (abort_message);
|
||||
|
||||
the_gui_pika = pika;
|
||||
|
||||
/* Normally this should have been taken care of during command line
|
||||
* parsing as a post-parse hook of gtk_get_option_group(), using the
|
||||
* system locales.
|
||||
* But user config may have overridden the language, therefore we must
|
||||
* check the widget directions again.
|
||||
*/
|
||||
gtk_widget_set_default_direction (gtk_get_locale_direction ());
|
||||
|
||||
gui_unique_init (pika);
|
||||
pika_language_store_parser_init ();
|
||||
|
||||
/* initialize icon themes before pika_widgets_init() so we avoid
|
||||
* setting the configured theme twice
|
||||
*/
|
||||
icon_themes_init (pika);
|
||||
|
||||
pika_widgets_init (gui_help_func,
|
||||
gui_get_foreground_func,
|
||||
gui_get_background_func,
|
||||
NULL, test_base_dir);
|
||||
|
||||
g_type_class_ref (PIKA_TYPE_COLOR_SELECT);
|
||||
|
||||
/* disable automatic startup notification */
|
||||
gtk_window_set_auto_startup_notification (FALSE);
|
||||
|
||||
#ifdef GDK_WINDOWING_QUARTZ
|
||||
/* Before the first window is created (typically the splash window),
|
||||
* we need to disable automatic tabbing behavior introduced on Sierra.
|
||||
* This is known to cause all kinds of weird issues (see for instance
|
||||
* Bugzilla #776294) and needs proper GTK+ support if we would want to
|
||||
* enable it.
|
||||
*/
|
||||
if ([NSWindow respondsToSelector:@selector(setAllowsAutomaticWindowTabbing:)])
|
||||
[NSWindow setAllowsAutomaticWindowTabbing:NO];
|
||||
#endif /* GDK_WINDOWING_QUARTZ */
|
||||
|
||||
pika_dnd_init (pika);
|
||||
|
||||
themes_init (pika);
|
||||
|
||||
initial_monitor = pika_get_monitor_at_pointer ();
|
||||
|
||||
if (! no_splash)
|
||||
{
|
||||
splash_create (pika, pika->be_verbose, initial_monitor, app);
|
||||
status_callback = splash_update;
|
||||
}
|
||||
|
||||
g_signal_connect_after (pika, "initialize",
|
||||
G_CALLBACK (gui_initialize_after_callback),
|
||||
NULL);
|
||||
|
||||
g_signal_connect (pika, "restore",
|
||||
G_CALLBACK (gui_restore_callback),
|
||||
NULL);
|
||||
g_signal_connect_after (pika, "restore",
|
||||
G_CALLBACK (gui_restore_after_callback),
|
||||
NULL);
|
||||
|
||||
g_signal_connect (pika, "exit",
|
||||
G_CALLBACK (gui_exit_callback),
|
||||
NULL);
|
||||
g_signal_connect_after (pika, "exit",
|
||||
G_CALLBACK (gui_exit_after_callback),
|
||||
NULL);
|
||||
|
||||
return status_callback;
|
||||
}
|
||||
|
||||
/*
|
||||
* gui_recover:
|
||||
* @n_recoveries: number of recovered files.
|
||||
*
|
||||
* Query the user interactively if files were saved from a previous
|
||||
* crash, asking whether to try and recover or discard them.
|
||||
*
|
||||
* Returns: TRUE if answer is to try and recover, FALSE otherwise.
|
||||
*/
|
||||
gboolean
|
||||
gui_recover (gint n_recoveries)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
GtkWidget *box;
|
||||
gboolean recover;
|
||||
|
||||
dialog = pika_dialog_new (_("Image Recovery"), "pika-recovery",
|
||||
NULL, GTK_DIALOG_MODAL, NULL, NULL,
|
||||
_("_Discard"), GTK_RESPONSE_CANCEL,
|
||||
_("_Recover"), GTK_RESPONSE_OK,
|
||||
NULL);
|
||||
gtk_dialog_set_default_response (GTK_DIALOG (dialog),
|
||||
GTK_RESPONSE_OK);
|
||||
|
||||
box = pika_message_box_new (PIKA_ICON_MASCOT_EEK);
|
||||
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
||||
box, TRUE, TRUE, 0);
|
||||
gtk_widget_show (box);
|
||||
|
||||
pika_message_box_set_primary_text (PIKA_MESSAGE_BOX (box),
|
||||
_("Eeek! It looks like PIKA recovered from a crash!"));
|
||||
|
||||
pika_message_box_set_text (PIKA_MESSAGE_BOX (box),
|
||||
/* TRANSLATORS: even if English singular form does
|
||||
* not use %d, you can use %d for translation in
|
||||
* any singular/plural form of your language if
|
||||
* suited. It will just work and be replaced by the
|
||||
* number of images as expected.
|
||||
*/
|
||||
ngettext ("An image was salvaged from the crash. "
|
||||
"Do you want to try and recover it?",
|
||||
"%d images were salvaged from the crash. "
|
||||
"Do you want to try and recover them?",
|
||||
n_recoveries), n_recoveries);
|
||||
|
||||
recover = (pika_dialog_run (PIKA_DIALOG (dialog)) == GTK_RESPONSE_OK);
|
||||
gtk_widget_destroy (dialog);
|
||||
|
||||
return recover;
|
||||
}
|
||||
|
||||
GdkMonitor *
|
||||
gui_get_initial_monitor (Pika *pika)
|
||||
{
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), 0);
|
||||
|
||||
return initial_monitor;
|
||||
}
|
||||
|
||||
|
||||
/* private functions */
|
||||
|
||||
static gchar *
|
||||
gui_sanity_check (void)
|
||||
{
|
||||
#define GTK_REQUIRED_MAJOR 3
|
||||
#define GTK_REQUIRED_MINOR 24
|
||||
#define GTK_REQUIRED_MICRO 0
|
||||
|
||||
const gchar *mismatch = gtk_check_version (GTK_REQUIRED_MAJOR,
|
||||
GTK_REQUIRED_MINOR,
|
||||
GTK_REQUIRED_MICRO);
|
||||
|
||||
if (mismatch)
|
||||
{
|
||||
return g_strdup_printf
|
||||
("%s\n\n"
|
||||
"PIKA requires GTK version %d.%d.%d or later.\n"
|
||||
"Installed GTK version is %d.%d.%d.\n\n"
|
||||
"Somehow you or your software packager managed\n"
|
||||
"to install PIKA with an older GTK version.\n\n"
|
||||
"Please upgrade to GTK version %d.%d.%d or later.",
|
||||
mismatch,
|
||||
GTK_REQUIRED_MAJOR, GTK_REQUIRED_MINOR, GTK_REQUIRED_MICRO,
|
||||
gtk_major_version, gtk_minor_version, gtk_micro_version,
|
||||
GTK_REQUIRED_MAJOR, GTK_REQUIRED_MINOR, GTK_REQUIRED_MICRO);
|
||||
}
|
||||
|
||||
#undef GTK_REQUIRED_MAJOR
|
||||
#undef GTK_REQUIRED_MINOR
|
||||
#undef GTK_REQUIRED_MICRO
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
gui_help_func (const gchar *help_id,
|
||||
gpointer help_data)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (the_gui_pika));
|
||||
|
||||
pika_help (the_gui_pika, NULL, NULL, help_id);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_get_foreground_func (PikaRGB *color)
|
||||
{
|
||||
g_return_val_if_fail (color != NULL, FALSE);
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (the_gui_pika), FALSE);
|
||||
|
||||
pika_context_get_foreground (pika_get_user_context (the_gui_pika), color);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_get_background_func (PikaRGB *color)
|
||||
{
|
||||
g_return_val_if_fail (color != NULL, FALSE);
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (the_gui_pika), FALSE);
|
||||
|
||||
pika_context_get_background (pika_get_user_context (the_gui_pika), color);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
gui_initialize_after_callback (Pika *pika,
|
||||
PikaInitStatusFunc status_callback)
|
||||
{
|
||||
const gchar *name = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("INIT: %s\n", G_STRFUNC);
|
||||
|
||||
#if defined (GDK_WINDOWING_X11)
|
||||
name = "DISPLAY";
|
||||
#elif defined (GDK_WINDOWING_DIRECTFB) || defined (GDK_WINDOWING_FB)
|
||||
name = "GDK_DISPLAY";
|
||||
#endif
|
||||
|
||||
/* TODO: Need to care about display migration with GTK+ 2.2 at some point */
|
||||
|
||||
if (name)
|
||||
{
|
||||
const gchar *display = gdk_display_get_name (gdk_display_get_default ());
|
||||
|
||||
pika_environ_table_add (pika->plug_in_manager->environ_table,
|
||||
name, display, NULL);
|
||||
}
|
||||
|
||||
pika_tools_init (pika);
|
||||
|
||||
pika_context_set_tool (pika_get_user_context (pika),
|
||||
pika_tool_info_get_standard (pika));
|
||||
}
|
||||
|
||||
static void
|
||||
gui_restore_callback (Pika *pika,
|
||||
PikaInitStatusFunc status_callback)
|
||||
{
|
||||
PikaDisplayConfig *display_config = PIKA_DISPLAY_CONFIG (pika->config);
|
||||
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("INIT: %s\n", G_STRFUNC);
|
||||
|
||||
gui_vtable_init (pika);
|
||||
|
||||
pika_dialogs_show_help_button (gui_config->use_help &&
|
||||
gui_config->show_help_button);
|
||||
|
||||
g_signal_connect (gui_config, "notify::use-help",
|
||||
G_CALLBACK (gui_show_help_button_notify),
|
||||
pika);
|
||||
g_signal_connect (gui_config, "notify::user-manual-online",
|
||||
G_CALLBACK (gui_user_manual_notify),
|
||||
pika);
|
||||
g_signal_connect (gui_config, "notify::show-help-button",
|
||||
G_CALLBACK (gui_show_help_button_notify),
|
||||
pika);
|
||||
|
||||
g_signal_connect (pika_get_user_context (pika), "display-changed",
|
||||
G_CALLBACK (gui_display_changed),
|
||||
pika);
|
||||
|
||||
/* make sure the monitor resolution is valid */
|
||||
if (display_config->monitor_res_from_gdk ||
|
||||
display_config->monitor_xres < PIKA_MIN_RESOLUTION ||
|
||||
display_config->monitor_yres < PIKA_MIN_RESOLUTION)
|
||||
{
|
||||
gdouble xres, yres;
|
||||
|
||||
pika_get_monitor_resolution (initial_monitor, &xres, &yres);
|
||||
|
||||
g_object_set (pika->config,
|
||||
"monitor-xresolution", xres,
|
||||
"monitor-yresolution", yres,
|
||||
"monitor-resolution-from-windowing-system", TRUE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
actions_init (pika);
|
||||
menus_init (pika);
|
||||
pika_render_init (pika);
|
||||
|
||||
dialogs_init (pika);
|
||||
|
||||
pika_clipboard_init (pika);
|
||||
if (pika_get_clipboard_image (pika))
|
||||
pika_clipboard_set_image (pika, pika_get_clipboard_image (pika));
|
||||
else
|
||||
pika_clipboard_set_buffer (pika, pika_get_clipboard_buffer (pika));
|
||||
|
||||
g_signal_connect (pika, "clipboard-changed",
|
||||
G_CALLBACK (gui_clipboard_changed),
|
||||
NULL);
|
||||
|
||||
pika_devices_init (pika);
|
||||
pika_controllers_init (pika);
|
||||
modifiers_init (pika);
|
||||
session_init (pika);
|
||||
|
||||
g_type_class_unref (g_type_class_ref (PIKA_TYPE_COLOR_SELECTOR_PALETTE));
|
||||
|
||||
status_callback (NULL, _("Tool Options"), 1.0);
|
||||
pika_tools_restore (pika);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_restore_after_callback (Pika *pika,
|
||||
PikaInitStatusFunc status_callback)
|
||||
{
|
||||
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
|
||||
PikaUIManager *image_ui_manager;
|
||||
PikaDisplay *display;
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("INIT: %s\n", G_STRFUNC);
|
||||
|
||||
pika->message_handler = PIKA_MESSAGE_BOX;
|
||||
|
||||
/* load the recent documents after pika_real_restore() because we
|
||||
* need the mime-types implemented by plug-ins
|
||||
*/
|
||||
status_callback (NULL, _("Documents"), 0.9);
|
||||
pika_recent_list_load (pika);
|
||||
|
||||
/* enable this to always have icons everywhere */
|
||||
if (g_getenv ("PIKA_ICONS_LIKE_A_BOSS"))
|
||||
{
|
||||
GdkScreen *screen = gdk_screen_get_default ();
|
||||
|
||||
g_object_set (G_OBJECT (gtk_settings_get_for_screen (screen)),
|
||||
"gtk-button-images", TRUE,
|
||||
"gtk-menu-images", TRUE,
|
||||
NULL);
|
||||
}
|
||||
|
||||
ui_configurer = g_object_new (PIKA_TYPE_UI_CONFIGURER,
|
||||
"pika", pika,
|
||||
NULL);
|
||||
|
||||
image_ui_manager = menus_get_image_manager_singleton (pika);
|
||||
pika_ui_manager_update (image_ui_manager, pika);
|
||||
|
||||
if (gui_config->restore_accels)
|
||||
menus_restore (pika);
|
||||
|
||||
/* Check that every accelerator is unique. */
|
||||
gui_check_unique_accelerators (pika);
|
||||
|
||||
pika_action_history_init (pika);
|
||||
|
||||
g_signal_connect_object (gui_config, "notify::single-window-mode",
|
||||
G_CALLBACK (gui_single_window_mode_notify),
|
||||
ui_configurer, 0);
|
||||
g_signal_connect (image_ui_manager, "show-tooltip",
|
||||
G_CALLBACK (gui_menu_show_tooltip),
|
||||
pika);
|
||||
g_signal_connect (image_ui_manager, "hide-tooltip",
|
||||
G_CALLBACK (gui_menu_hide_tooltip),
|
||||
pika);
|
||||
|
||||
pika_devices_restore (pika);
|
||||
pika_controllers_restore (pika, image_ui_manager);
|
||||
modifiers_restore (pika);
|
||||
|
||||
if (status_callback == splash_update)
|
||||
splash_destroy ();
|
||||
|
||||
if (pika_get_show_gui (pika))
|
||||
{
|
||||
PikaDisplayShell *shell;
|
||||
GtkWidget *toplevel;
|
||||
|
||||
/* create the empty display */
|
||||
display = PIKA_DISPLAY (pika_create_display (pika, NULL,
|
||||
PIKA_UNIT_PIXEL, 1.0,
|
||||
G_OBJECT (initial_monitor)));
|
||||
|
||||
shell = pika_display_get_shell (display);
|
||||
|
||||
if (gui_config->restore_session)
|
||||
session_restore (pika, initial_monitor);
|
||||
|
||||
toplevel = gtk_widget_get_toplevel (GTK_WIDGET (shell));
|
||||
|
||||
/* move keyboard focus to the display */
|
||||
gtk_window_present (GTK_WINDOW (toplevel));
|
||||
}
|
||||
|
||||
/* indicate that the application has finished loading */
|
||||
gdk_notify_startup_complete ();
|
||||
|
||||
/* clear startup monitor variables */
|
||||
initial_monitor = NULL;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_exit_callback (Pika *pika,
|
||||
gboolean force)
|
||||
{
|
||||
PikaGuiConfig *gui_config = PIKA_GUI_CONFIG (pika->config);
|
||||
PikaTool *active_tool;
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("EXIT: %s\n", G_STRFUNC);
|
||||
|
||||
if (! force && pika_displays_dirty (pika))
|
||||
{
|
||||
PikaContext *context = pika_get_user_context (pika);
|
||||
PikaDisplay *display = pika_context_get_display (context);
|
||||
GdkMonitor *monitor = pika_get_monitor_at_pointer ();
|
||||
GtkWidget *parent = NULL;
|
||||
|
||||
if (display)
|
||||
{
|
||||
PikaDisplayShell *shell = pika_display_get_shell (display);
|
||||
|
||||
parent = GTK_WIDGET (pika_display_shell_get_window (shell));
|
||||
}
|
||||
|
||||
pika_dialog_factory_dialog_raise (pika_dialog_factory_get_singleton (),
|
||||
monitor, parent, "pika-quit-dialog", -1);
|
||||
|
||||
return TRUE; /* stop exit for now */
|
||||
}
|
||||
|
||||
pika->message_handler = PIKA_CONSOLE;
|
||||
|
||||
gui_unique_exit ();
|
||||
|
||||
/* If any modifier is set when quitting (typically when exiting with
|
||||
* Ctrl-q for instance!), when serializing the tool options, it will
|
||||
* save any alternate value instead of the main one. Make sure that
|
||||
* any modifier is reset before saving options.
|
||||
*/
|
||||
active_tool = tool_manager_get_active (pika);
|
||||
if (active_tool && active_tool->focus_display)
|
||||
pika_tool_set_modifier_state (active_tool, 0, active_tool->focus_display);
|
||||
|
||||
if (gui_config->save_session_info)
|
||||
session_save (pika, FALSE);
|
||||
|
||||
if (gui_config->save_device_status)
|
||||
pika_devices_save (pika, FALSE);
|
||||
|
||||
if (TRUE /* gui_config->save_controllers */)
|
||||
pika_controllers_save (pika);
|
||||
|
||||
modifiers_save (pika, FALSE);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (pika_get_user_context (pika),
|
||||
gui_display_changed,
|
||||
pika);
|
||||
|
||||
pika_displays_delete (pika);
|
||||
|
||||
if (gui_config->save_accels)
|
||||
menus_save (pika, FALSE);
|
||||
|
||||
pika_tools_save (pika, gui_config->save_tool_options, FALSE);
|
||||
pika_tools_exit (pika);
|
||||
|
||||
pika_language_store_parser_clean ();
|
||||
|
||||
return FALSE; /* continue exiting */
|
||||
}
|
||||
|
||||
static gboolean
|
||||
gui_exit_after_callback (Pika *pika,
|
||||
gboolean force)
|
||||
{
|
||||
if (pika->be_verbose)
|
||||
g_print ("EXIT: %s\n", G_STRFUNC);
|
||||
|
||||
g_signal_handlers_disconnect_by_func (pika->config,
|
||||
gui_show_help_button_notify,
|
||||
pika);
|
||||
g_signal_handlers_disconnect_by_func (pika->config,
|
||||
gui_user_manual_notify,
|
||||
pika);
|
||||
|
||||
pika_action_history_exit (pika);
|
||||
|
||||
g_object_unref (ui_configurer);
|
||||
ui_configurer = NULL;
|
||||
|
||||
/* exit the clipboard before shutting down the GUI because it runs
|
||||
* a whole lot of code paths. See bug #731389.
|
||||
*/
|
||||
g_signal_handlers_disconnect_by_func (pika,
|
||||
G_CALLBACK (gui_clipboard_changed),
|
||||
NULL);
|
||||
pika_clipboard_exit (pika);
|
||||
|
||||
session_exit (pika);
|
||||
menus_exit (pika);
|
||||
actions_exit (pika);
|
||||
pika_render_exit (pika);
|
||||
|
||||
pika_controllers_exit (pika);
|
||||
modifiers_exit (pika);
|
||||
pika_devices_exit (pika);
|
||||
dialogs_exit (pika);
|
||||
themes_exit (pika);
|
||||
|
||||
g_type_class_unref (g_type_class_peek (PIKA_TYPE_COLOR_SELECT));
|
||||
|
||||
return FALSE; /* continue exiting */
|
||||
}
|
||||
|
||||
static void
|
||||
gui_show_help_button_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *param_spec,
|
||||
Pika *pika)
|
||||
{
|
||||
pika_dialogs_show_help_button (gui_config->use_help &&
|
||||
gui_config->show_help_button);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_user_manual_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *param_spec,
|
||||
Pika *pika)
|
||||
{
|
||||
pika_help_user_manual_changed (pika);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_single_window_mode_notify (PikaGuiConfig *gui_config,
|
||||
GParamSpec *pspec,
|
||||
PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
pika_ui_configurer_configure (ui_configurer,
|
||||
gui_config->single_window_mode);
|
||||
}
|
||||
|
||||
static void
|
||||
gui_clipboard_changed (Pika *pika)
|
||||
{
|
||||
if (pika_get_clipboard_image (pika))
|
||||
pika_clipboard_set_image (pika, pika_get_clipboard_image (pika));
|
||||
else
|
||||
pika_clipboard_set_buffer (pika, pika_get_clipboard_buffer (pika));
|
||||
}
|
||||
|
||||
static void
|
||||
gui_menu_show_tooltip (PikaUIManager *manager,
|
||||
const gchar *tooltip,
|
||||
Pika *pika)
|
||||
{
|
||||
PikaContext *context = pika_get_user_context (pika);
|
||||
PikaDisplay *display = pika_context_get_display (context);
|
||||
|
||||
if (display)
|
||||
{
|
||||
PikaDisplayShell *shell = pika_display_get_shell (display);
|
||||
PikaStatusbar *statusbar = pika_display_shell_get_statusbar (shell);
|
||||
|
||||
pika_statusbar_push (statusbar, "menu-tooltip",
|
||||
NULL, "%s", tooltip);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gui_menu_hide_tooltip (PikaUIManager *manager,
|
||||
Pika *pika)
|
||||
{
|
||||
PikaContext *context = pika_get_user_context (pika);
|
||||
PikaDisplay *display = pika_context_get_display (context);
|
||||
|
||||
if (display)
|
||||
{
|
||||
PikaDisplayShell *shell = pika_display_get_shell (display);
|
||||
PikaStatusbar *statusbar = pika_display_shell_get_statusbar (shell);
|
||||
|
||||
pika_statusbar_pop (statusbar, "menu-tooltip");
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
gui_display_changed (PikaContext *context,
|
||||
PikaDisplay *display,
|
||||
Pika *pika)
|
||||
{
|
||||
if (! display)
|
||||
{
|
||||
PikaImage *image = pika_context_get_image (context);
|
||||
|
||||
if (image)
|
||||
{
|
||||
GList *list;
|
||||
|
||||
for (list = pika_get_display_iter (pika);
|
||||
list;
|
||||
list = g_list_next (list))
|
||||
{
|
||||
PikaDisplay *display2 = list->data;
|
||||
|
||||
if (pika_display_get_image (display2) == image)
|
||||
{
|
||||
pika_context_set_display (context, display2);
|
||||
|
||||
/* stop the emission of the original signal
|
||||
* (the emission of the recursive signal is finished)
|
||||
*/
|
||||
g_signal_stop_emission_by_name (context, "display-changed");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
pika_context_set_image (context, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
pika_ui_manager_update (menus_get_image_manager_singleton (pika),
|
||||
display);
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const gchar *path;
|
||||
guint key;
|
||||
GdkModifierType mods;
|
||||
}
|
||||
accelData;
|
||||
|
||||
static void
|
||||
gui_check_unique_accelerators (Pika *pika)
|
||||
{
|
||||
gchar **actions;
|
||||
|
||||
actions = g_action_group_list_actions (G_ACTION_GROUP (pika->app));
|
||||
|
||||
for (gint i = 0; actions[i] != NULL; i++)
|
||||
{
|
||||
gchar **accels;
|
||||
gchar *detailed_name;
|
||||
PikaAction *action;
|
||||
gint value;
|
||||
|
||||
action = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (pika->app), actions[i]);
|
||||
|
||||
if (PIKA_IS_RADIO_ACTION (action))
|
||||
{
|
||||
g_object_get ((GObject *) action,
|
||||
"value", &value,
|
||||
NULL);
|
||||
detailed_name = g_strdup_printf ("app.%s(%i)", actions[i],
|
||||
value);
|
||||
}
|
||||
else
|
||||
{
|
||||
detailed_name = g_strdup_printf ("app.%s", actions[i]);
|
||||
}
|
||||
|
||||
accels = gtk_application_get_accels_for_action (GTK_APPLICATION (pika->app),
|
||||
detailed_name);
|
||||
g_free (detailed_name);
|
||||
|
||||
for (gint j = 0; accels[j] != NULL; j++)
|
||||
{
|
||||
for (gint k = i + 1; actions[k] != NULL; k++)
|
||||
{
|
||||
gchar **accels2;
|
||||
gchar *detailed_name2;
|
||||
PikaAction *action2;
|
||||
|
||||
action2 = (PikaAction *) g_action_map_lookup_action (G_ACTION_MAP (pika->app), actions[k]);
|
||||
|
||||
if (PIKA_IS_RADIO_ACTION (action2))
|
||||
{
|
||||
g_object_get ((GObject *) action2,
|
||||
"value", &value,
|
||||
NULL);
|
||||
detailed_name2 = g_strdup_printf ("app.%s(%i)", actions[k],
|
||||
value);
|
||||
}
|
||||
else
|
||||
{
|
||||
detailed_name2 = g_strdup_printf ("app.%s", actions[k]);
|
||||
}
|
||||
|
||||
accels2 = gtk_application_get_accels_for_action (GTK_APPLICATION (pika->app),
|
||||
detailed_name2);
|
||||
g_free (detailed_name2);
|
||||
|
||||
for (gint l = 0; accels2[l] != NULL; l++)
|
||||
{
|
||||
if (g_strcmp0 (accels[j], accels2[l]) == 0)
|
||||
{
|
||||
GAction *action;
|
||||
gchar *disabled_action;
|
||||
gchar **disabled_accels;
|
||||
gint len;
|
||||
gint remove;
|
||||
|
||||
/* Just keep the first one (no reason other than we have
|
||||
* to choose), unless it's a secondary shortcut, and the
|
||||
* second is a primary shortcut.
|
||||
*/
|
||||
if (l == 0 && j != 0)
|
||||
{
|
||||
disabled_action = actions[i];
|
||||
disabled_accels = accels;
|
||||
remove = j;
|
||||
}
|
||||
else
|
||||
{
|
||||
disabled_action = actions[k];
|
||||
disabled_accels = accels2;
|
||||
remove = l;
|
||||
}
|
||||
/* Remove only the duplicate shortcut but keep others. */
|
||||
len = g_strv_length (disabled_accels);
|
||||
g_free (disabled_accels[remove]);
|
||||
memmove (&disabled_accels[remove],
|
||||
&disabled_accels[remove + 1],
|
||||
sizeof (char *) * (len - remove));
|
||||
|
||||
g_printerr ("Actions \"%s\" and \"%s\" use the same accelerator.\n"
|
||||
" Disabling the accelerator on \"%s\".\n",
|
||||
actions[i], actions[k], disabled_action);
|
||||
|
||||
action = g_action_map_lookup_action (G_ACTION_MAP (pika->app),
|
||||
disabled_action);
|
||||
pika_action_set_accels (PIKA_ACTION (action),
|
||||
(const gchar **) disabled_accels);
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (accels2);
|
||||
}
|
||||
}
|
||||
|
||||
g_strfreev (accels);
|
||||
}
|
||||
|
||||
g_strfreev (actions);
|
||||
}
|
36
app/gui/gui.h
Normal file
36
app/gui/gui.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __GUI_H__
|
||||
#define __GUI_H__
|
||||
|
||||
|
||||
void gui_libs_init (GOptionContext *context);
|
||||
void gui_abort (const gchar *abort_message);
|
||||
|
||||
PikaInitStatusFunc gui_init (Pika *pika,
|
||||
gboolean no_splash,
|
||||
PikaApp *app,
|
||||
const gchar *test_base_dir);
|
||||
|
||||
gboolean gui_recover (gint n_recoveries);
|
||||
|
||||
#endif /* __GUI_H__ */
|
271
app/gui/icon-themes.c
Normal file
271
app/gui/icon-themes.c
Normal file
@ -0,0 +1,271 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* icon-themes.c
|
||||
* Copyright (C) 2015 Benoit Touchette
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikaconfig/pikaconfig.h"
|
||||
#include "libpikawidgets/pikawidgets.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
|
||||
#include "icon-themes.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
static void icons_apply_theme (Pika *pika,
|
||||
const gchar *icon_theme_name);
|
||||
static void icons_list_icons_foreach (gpointer key,
|
||||
gpointer value,
|
||||
gpointer data);
|
||||
static gint icons_name_compare (const void *p1,
|
||||
const void *p2);
|
||||
static void icons_theme_change_notify (PikaGuiConfig *config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika);
|
||||
|
||||
|
||||
static GHashTable *icon_themes_hash = NULL;
|
||||
|
||||
|
||||
void
|
||||
icon_themes_init (Pika *pika)
|
||||
{
|
||||
PikaGuiConfig *config;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
config = PIKA_GUI_CONFIG (pika->config);
|
||||
|
||||
icon_themes_hash = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
g_object_unref);
|
||||
|
||||
if (config->icon_theme_path)
|
||||
{
|
||||
GList *path;
|
||||
GList *list;
|
||||
|
||||
path = pika_config_path_expand_to_files (config->icon_theme_path, NULL);
|
||||
|
||||
for (list = path; list; list = g_list_next (list))
|
||||
{
|
||||
GFile *dir = list->data;
|
||||
GFileEnumerator *enumerator;
|
||||
|
||||
enumerator =
|
||||
g_file_enumerate_children (dir,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
NULL, NULL);
|
||||
|
||||
if (enumerator)
|
||||
{
|
||||
GFileInfo *info;
|
||||
|
||||
while ((info = g_file_enumerator_next_file (enumerator,
|
||||
NULL, NULL)))
|
||||
{
|
||||
if (! g_file_info_get_is_hidden (info) &&
|
||||
g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||||
{
|
||||
GFile *file;
|
||||
GFile *index_theme;
|
||||
|
||||
file = g_file_enumerator_get_child (enumerator, info);
|
||||
|
||||
/* make sure there is a hicolor/index.theme file */
|
||||
index_theme = g_file_get_child (file, "index.theme");
|
||||
|
||||
if (g_file_query_exists (index_theme, NULL))
|
||||
{
|
||||
const gchar *name;
|
||||
gchar *basename;
|
||||
|
||||
name = pika_file_get_utf8_name (file);
|
||||
basename = g_path_get_basename (name);
|
||||
|
||||
if (strcmp ("hicolor", basename))
|
||||
{
|
||||
if (pika->be_verbose)
|
||||
g_print ("Adding icon theme '%s' (%s)\n",
|
||||
basename, name);
|
||||
|
||||
g_hash_table_insert (icon_themes_hash, basename,
|
||||
g_object_ref (file));
|
||||
}
|
||||
else
|
||||
{
|
||||
g_free (basename);
|
||||
}
|
||||
}
|
||||
|
||||
g_object_unref (index_theme);
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
g_object_unref (enumerator);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full (path, (GDestroyNotify) g_object_unref);
|
||||
}
|
||||
|
||||
g_signal_connect (config, "notify::icon-theme",
|
||||
G_CALLBACK (icons_theme_change_notify),
|
||||
pika);
|
||||
|
||||
icons_theme_change_notify (config, NULL, pika);
|
||||
}
|
||||
|
||||
void
|
||||
icon_themes_exit (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (icon_themes_hash)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (pika->config,
|
||||
icons_theme_change_notify,
|
||||
pika);
|
||||
|
||||
g_hash_table_destroy (icon_themes_hash);
|
||||
icon_themes_hash = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
gchar **
|
||||
icon_themes_list_themes (Pika *pika,
|
||||
gint *n_icon_themes)
|
||||
{
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
g_return_val_if_fail (n_icon_themes != NULL, NULL);
|
||||
|
||||
*n_icon_themes = g_hash_table_size (icon_themes_hash);
|
||||
|
||||
if (*n_icon_themes > 0)
|
||||
{
|
||||
gchar **icon_themes;
|
||||
gchar **index;
|
||||
|
||||
icon_themes = g_new0 (gchar *, *n_icon_themes + 1);
|
||||
|
||||
index = icon_themes;
|
||||
|
||||
g_hash_table_foreach (icon_themes_hash, icons_list_icons_foreach, &index);
|
||||
|
||||
qsort (icon_themes, *n_icon_themes, sizeof (gchar *), icons_name_compare);
|
||||
|
||||
return icon_themes;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GFile *
|
||||
icon_themes_get_theme_dir (Pika *pika,
|
||||
const gchar *icon_theme_name)
|
||||
{
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
|
||||
if (! icon_theme_name)
|
||||
icon_theme_name = PIKA_CONFIG_DEFAULT_ICON_THEME;
|
||||
|
||||
return g_hash_table_lookup (icon_themes_hash, icon_theme_name);
|
||||
}
|
||||
|
||||
static void
|
||||
icons_apply_theme (Pika *pika,
|
||||
const gchar *icon_theme_name)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (! icon_theme_name)
|
||||
icon_theme_name = PIKA_CONFIG_DEFAULT_ICON_THEME;
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Loading icon theme '%s'\n", icon_theme_name);
|
||||
|
||||
if (g_getenv ("PIKA_TESTING_ABS_TOP_SRCDIR"))
|
||||
{
|
||||
GFile *file;
|
||||
gchar *path;
|
||||
|
||||
path = g_build_filename (g_getenv ("PIKA_TESTING_ABS_TOP_SRCDIR"),
|
||||
"icons", icon_theme_name, NULL);
|
||||
file = g_file_new_for_path (path);
|
||||
|
||||
pika_icons_set_icon_theme (file);
|
||||
|
||||
g_object_unref (file);
|
||||
g_free (path);
|
||||
}
|
||||
else
|
||||
{
|
||||
pika_icons_set_icon_theme (icon_themes_get_theme_dir (pika, icon_theme_name));
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
icons_list_icons_foreach (gpointer key,
|
||||
gpointer value,
|
||||
gpointer data)
|
||||
{
|
||||
gchar ***index = data;
|
||||
|
||||
**index = g_strdup ((gchar *) key);
|
||||
|
||||
(*index)++;
|
||||
}
|
||||
|
||||
static gint
|
||||
icons_name_compare (const void *p1,
|
||||
const void *p2)
|
||||
{
|
||||
return strcmp (* (char **) p1, * (char **) p2);
|
||||
}
|
||||
|
||||
static void
|
||||
icons_theme_change_notify (PikaGuiConfig *config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika)
|
||||
{
|
||||
icons_apply_theme (pika, config->icon_theme);
|
||||
}
|
38
app/gui/icon-themes.h
Normal file
38
app/gui/icon-themes.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* icon-themes.h
|
||||
* Copyright (C) 2015 Benoit Touchette
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __ICONS_THEMES_H__
|
||||
#define __ICONS_THEMES_H__
|
||||
|
||||
|
||||
void icon_themes_init (Pika *pika);
|
||||
void icon_themes_exit (Pika *pika);
|
||||
|
||||
gchar ** icon_themes_list_themes (Pika *pika,
|
||||
gint *n_themes);
|
||||
GFile * icon_themes_get_theme_dir (Pika *pika,
|
||||
const gchar *theme_name);
|
||||
|
||||
|
||||
#endif /* __ICONS_THEMES_H__ */
|
37
app/gui/meson.build
Normal file
37
app/gui/meson.build
Normal file
@ -0,0 +1,37 @@
|
||||
|
||||
pikadbusservice_gen = gnome.gdbus_codegen(
|
||||
'pikadbusservice-generated',
|
||||
'dbus-service.xml',
|
||||
interface_prefix: 'technology.heckin.PIKA.',
|
||||
namespace: 'PikaDBusService',
|
||||
)
|
||||
|
||||
libappgui_sources = [
|
||||
'pikaapp.c',
|
||||
'pikadbusservice.c',
|
||||
'pikauiconfigurer.c',
|
||||
'gui-message.c',
|
||||
'gui-unique.c',
|
||||
'gui-vtable.c',
|
||||
'gui.c',
|
||||
'icon-themes.c',
|
||||
'modifiers.c',
|
||||
'session.c',
|
||||
'splash.c',
|
||||
'themes.c',
|
||||
pikadbusservice_gen,
|
||||
]
|
||||
|
||||
# Workaround for generated header included in other directories.
|
||||
configInclude = include_directories('.')
|
||||
|
||||
|
||||
libappgui = static_library('appgui',
|
||||
libappgui_sources,
|
||||
include_directories: [ rootInclude, rootAppInclude, ],
|
||||
c_args: '-DG_LOG_DOMAIN="Pika-GUI"',
|
||||
dependencies: [
|
||||
cairo, gegl, gdk_pixbuf, gio_specific, gtk3
|
||||
],
|
||||
install: false,
|
||||
)
|
222
app/gui/modifiers.c
Normal file
222
app/gui/modifiers.c
Normal file
@ -0,0 +1,222 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* modifiers.c
|
||||
* Copyright (C) 2022 Jehan
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikaconfig/pikaconfig.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "config/pikaconfig-file.h"
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikaerror.h"
|
||||
|
||||
#include "display/pikamodifiersmanager.h"
|
||||
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
|
||||
#include "dialogs/dialogs.h"
|
||||
|
||||
#include "modifiers.h"
|
||||
#include "pika-log.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
MODIFIERS_INFO = 1,
|
||||
HIDE_DOCKS,
|
||||
SINGLE_WINDOW_MODE,
|
||||
SHOW_TABS,
|
||||
TABS_POSITION,
|
||||
LAST_TIP_SHOWN
|
||||
};
|
||||
|
||||
|
||||
static GFile * modifiers_file (Pika *pika);
|
||||
|
||||
|
||||
/* private variables */
|
||||
|
||||
static gboolean modifiersrc_deleted = FALSE;
|
||||
|
||||
|
||||
/* public functions */
|
||||
|
||||
void
|
||||
modifiers_init (Pika *pika)
|
||||
{
|
||||
PikaDisplayConfig *display_config;
|
||||
GFile *file;
|
||||
PikaModifiersManager *manager = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
display_config = PIKA_DISPLAY_CONFIG (pika->config);
|
||||
if (display_config->modifiers_manager != NULL)
|
||||
return;
|
||||
|
||||
manager = pika_modifiers_manager_new ();
|
||||
g_object_set (display_config, "modifiers-manager", manager, NULL);
|
||||
g_object_unref (manager);
|
||||
|
||||
file = modifiers_file (pika);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Parsing '%s'\n", pika_file_get_utf8_name (file));
|
||||
|
||||
pika_config_deserialize_file (PIKA_CONFIG (manager), file, NULL, &error);
|
||||
|
||||
if (error)
|
||||
{
|
||||
/* File not existing is considered a normal event, not an error.
|
||||
* It can happen for instance the first time you run PIKA. When
|
||||
* this happens, we ignore the error. The PikaModifiersManager
|
||||
* object will simply use default modifiers.
|
||||
*/
|
||||
if (error->domain != PIKA_CONFIG_ERROR ||
|
||||
error->code != PIKA_CONFIG_ERROR_OPEN_ENOENT)
|
||||
{
|
||||
pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
||||
pika_config_file_backup_on_error (file, "modifiersrc", NULL);
|
||||
}
|
||||
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_object_unref (file);
|
||||
}
|
||||
|
||||
void
|
||||
modifiers_exit (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
}
|
||||
|
||||
void
|
||||
modifiers_restore (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
}
|
||||
|
||||
void
|
||||
modifiers_save (Pika *pika,
|
||||
gboolean always_save)
|
||||
{
|
||||
PikaDisplayConfig *display_config;
|
||||
GFile *file;
|
||||
PikaModifiersManager *manager = NULL;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (modifiersrc_deleted && ! always_save)
|
||||
return;
|
||||
|
||||
display_config = PIKA_DISPLAY_CONFIG (pika->config);
|
||||
g_return_if_fail (PIKA_IS_DISPLAY_CONFIG (display_config));
|
||||
|
||||
manager = PIKA_MODIFIERS_MANAGER (display_config->modifiers_manager);
|
||||
g_return_if_fail (manager != NULL);
|
||||
g_return_if_fail (PIKA_IS_MODIFIERS_MANAGER (manager));
|
||||
file = modifiers_file (pika);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Writing '%s'\n", pika_file_get_utf8_name (file));
|
||||
|
||||
pika_config_serialize_to_file (PIKA_CONFIG (manager), file,
|
||||
"PIKA modifiersrc\n\n"
|
||||
"This file stores modifiers configuration. "
|
||||
"You are not supposed to edit it manually, "
|
||||
"but of course you can do. The modifiersrc "
|
||||
"will be entirely rewritten every time you "
|
||||
"quit PIKA. If this file isn't found, "
|
||||
"defaults are used.",
|
||||
NULL, NULL, &error);
|
||||
if (error != NULL)
|
||||
{
|
||||
pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_object_unref (file);
|
||||
|
||||
modifiersrc_deleted = FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
modifiers_clear (Pika *pika,
|
||||
GError **error)
|
||||
{
|
||||
GFile *file;
|
||||
GError *my_error = NULL;
|
||||
gboolean success = TRUE;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
|
||||
file = modifiers_file (pika);
|
||||
|
||||
if (! g_file_delete (file, NULL, &my_error) &&
|
||||
my_error->code != G_IO_ERROR_NOT_FOUND)
|
||||
{
|
||||
success = FALSE;
|
||||
|
||||
g_set_error (error, PIKA_ERROR, PIKA_FAILED,
|
||||
_("Deleting \"%s\" failed: %s"),
|
||||
pika_file_get_utf8_name (file), my_error->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
modifiersrc_deleted = TRUE;
|
||||
}
|
||||
|
||||
g_clear_error (&my_error);
|
||||
g_object_unref (file);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static GFile *
|
||||
modifiers_file (Pika *pika)
|
||||
{
|
||||
const gchar *basename;
|
||||
GFile *file;
|
||||
|
||||
basename = g_getenv ("PIKA_TESTING_MODIFIERSRC_NAME");
|
||||
if (! basename)
|
||||
basename = "modifiersrc";
|
||||
|
||||
file = pika_directory_file (basename, NULL);
|
||||
|
||||
return file;
|
||||
}
|
40
app/gui/modifiers.h
Normal file
40
app/gui/modifiers.h
Normal file
@ -0,0 +1,40 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* modifiers.h
|
||||
* Copyright (C) 2022 Jehan
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __MODIFIERS_H__
|
||||
#define __MODIFIERS_H__
|
||||
|
||||
|
||||
void modifiers_init (Pika *pika);
|
||||
void modifiers_exit (Pika *pika);
|
||||
|
||||
void modifiers_restore (Pika *pika);
|
||||
void modifiers_save (Pika *pika,
|
||||
gboolean always_save);
|
||||
|
||||
gboolean modifiers_clear (Pika *pika,
|
||||
GError **error);
|
||||
|
||||
|
||||
#endif /* __MODIFIERS_H__ */
|
173
app/gui/pikaapp.c
Normal file
173
app/gui/pikaapp.c
Normal file
@ -0,0 +1,173 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
||||
*
|
||||
* pikaapp.c
|
||||
* Copyright (C) 2021 Niels De Graef <nielsdegraef@gmail.com>
|
||||
*
|
||||
* 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
|
||||
* Library 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 <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
|
||||
#include "core/core-types.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
|
||||
#include "pikacoreapp.h"
|
||||
|
||||
#include "pikaapp.h"
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_NO_SPLASH = PIKA_CORE_APP_PROP_LAST + 1,
|
||||
};
|
||||
|
||||
struct _PikaApp
|
||||
{
|
||||
GtkApplication parent_instance;
|
||||
|
||||
gboolean no_splash;
|
||||
};
|
||||
|
||||
|
||||
static void pika_app_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_app_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (PikaApp, pika_app, GTK_TYPE_APPLICATION,
|
||||
G_IMPLEMENT_INTERFACE (PIKA_TYPE_CORE_APP,
|
||||
NULL))
|
||||
|
||||
|
||||
static void
|
||||
pika_app_class_init (PikaAppClass *klass)
|
||||
{
|
||||
GObjectClass *gobj_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
gobj_class->get_property = pika_app_get_property;
|
||||
gobj_class->set_property = pika_app_set_property;
|
||||
|
||||
pika_core_app_install_properties (gobj_class);
|
||||
|
||||
g_object_class_install_property (gobj_class, PROP_NO_SPLASH,
|
||||
g_param_spec_boolean ("no-splash", NULL, NULL,
|
||||
FALSE,
|
||||
PIKA_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static void
|
||||
pika_app_init (PikaApp *self)
|
||||
{
|
||||
}
|
||||
|
||||
static void
|
||||
pika_app_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_NO_SPLASH:
|
||||
g_value_set_boolean (value, PIKA_APP (object)->no_splash);
|
||||
break;
|
||||
|
||||
default:
|
||||
pika_core_app_get_property (object, property_id, value, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pika_app_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_NO_SPLASH:
|
||||
PIKA_APP (object)->no_splash = g_value_get_boolean (value);
|
||||
break;
|
||||
|
||||
default:
|
||||
pika_core_app_set_property (object, property_id, value, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* public functions */
|
||||
|
||||
GApplication *
|
||||
pika_app_new (Pika *pika,
|
||||
gboolean no_splash,
|
||||
gboolean quit,
|
||||
gboolean as_new,
|
||||
const char **filenames,
|
||||
const char *batch_interpreter,
|
||||
const char **batch_commands)
|
||||
{
|
||||
PikaApp *app;
|
||||
|
||||
app = g_object_new (PIKA_TYPE_APP,
|
||||
"application-id", PIKA_APPLICATION_ID,
|
||||
/* We have our own code to handle process uniqueness, so
|
||||
* when we reached this code, we are already passed this
|
||||
* (it means that either this is the first process, or we
|
||||
* don't want uniqueness). See bugs #9598 and #9599 for
|
||||
* what happens when we let GIO try to handle uniqueness.
|
||||
*
|
||||
* TODO: since GApplication has code to pass over files
|
||||
* and command line arguments, we may eventually want to
|
||||
* remove our own code for uniqueness and batch command
|
||||
* inter-process communication. This should be tested.
|
||||
*/
|
||||
#if GLIB_CHECK_VERSION(2,74,0)
|
||||
"flags", G_APPLICATION_DEFAULT_FLAGS | G_APPLICATION_NON_UNIQUE,
|
||||
#else
|
||||
"flags", G_APPLICATION_FLAGS_NONE | G_APPLICATION_NON_UNIQUE,
|
||||
#endif
|
||||
"pika", pika,
|
||||
"filenames", filenames,
|
||||
"as-new", as_new,
|
||||
|
||||
"quit", quit,
|
||||
"batch-interpreter", batch_interpreter,
|
||||
"batch-commands", batch_commands,
|
||||
|
||||
"no-splash", no_splash,
|
||||
NULL);
|
||||
|
||||
return G_APPLICATION (app);
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_app_get_no_splash (PikaApp *self)
|
||||
{
|
||||
g_return_val_if_fail (PIKA_IS_APP (self), FALSE);
|
||||
return PIKA_APP (self)->no_splash;
|
||||
}
|
38
app/gui/pikaapp.h
Normal file
38
app/gui/pikaapp.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball
|
||||
*
|
||||
* pikaapp.h
|
||||
* Copyright (C) 2021 Niels De Graef <nielsdegraef@gmail.com>
|
||||
*
|
||||
* 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
|
||||
* Library 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/>.
|
||||
*/
|
||||
|
||||
#ifndef __PIKA_APP_H__
|
||||
#define __PIKA_APP_H__
|
||||
|
||||
|
||||
#define PIKA_TYPE_APP (pika_app_get_type ())
|
||||
G_DECLARE_FINAL_TYPE (PikaApp, pika_app, PIKA, APP, GtkApplication)
|
||||
|
||||
GApplication * pika_app_new (Pika *pika,
|
||||
gboolean no_splash,
|
||||
gboolean quit,
|
||||
gboolean as_new,
|
||||
const char **filenames,
|
||||
const char *batch_interpreter,
|
||||
const char **batch_commands);
|
||||
|
||||
gboolean pika_app_get_no_splash (PikaApp *self);
|
||||
|
||||
#endif /* __PIKA_APP_H__ */
|
461
app/gui/pikadbusservice.c
Normal file
461
app/gui/pikadbusservice.c
Normal file
@ -0,0 +1,461 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* PikaDBusService
|
||||
* Copyright (C) 2007, 2008 Sven Neumann <sven@gimp.org>
|
||||
* Copyright (C) 2013 Michael Natterer <mitch@gimp.org>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pika-batch.h"
|
||||
#include "core/pikacontainer.h"
|
||||
|
||||
#include "file/file-open.h"
|
||||
|
||||
#include "display/pikadisplay.h"
|
||||
#include "display/pikadisplayshell.h"
|
||||
|
||||
#include "pikadbusservice.h"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GFile *file;
|
||||
gboolean as_new;
|
||||
|
||||
gchar *interpreter;
|
||||
gchar *command;
|
||||
} IdleData;
|
||||
|
||||
static void pika_dbus_service_ui_iface_init (PikaDBusServiceUIIface *iface);
|
||||
|
||||
static void pika_dbus_service_dispose (GObject *object);
|
||||
static void pika_dbus_service_finalize (GObject *object);
|
||||
|
||||
static gboolean pika_dbus_service_activate (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation);
|
||||
static gboolean pika_dbus_service_open (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *uri);
|
||||
|
||||
static gboolean pika_dbus_service_open_as_new (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *uri);
|
||||
|
||||
static gboolean pika_dbus_service_batch_run (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *batch_interpreter,
|
||||
const gchar *batch_command);
|
||||
|
||||
static void pika_dbus_service_pika_opened (Pika *pika,
|
||||
GFile *file,
|
||||
PikaDBusService *service);
|
||||
|
||||
static gboolean pika_dbus_service_queue_open (PikaDBusService *service,
|
||||
const gchar *uri,
|
||||
gboolean as_new);
|
||||
static gboolean pika_dbus_service_queue_batch (PikaDBusService *service,
|
||||
const gchar *interpreter,
|
||||
const gchar *command);
|
||||
|
||||
static gboolean pika_dbus_service_process_idle (PikaDBusService *service);
|
||||
static IdleData * pika_dbus_service_open_data_new (PikaDBusService *service,
|
||||
const gchar *uri,
|
||||
gboolean as_new);
|
||||
static IdleData * pika_dbus_service_batch_data_new (PikaDBusService *service,
|
||||
const gchar *interpreter,
|
||||
const gchar *command);
|
||||
static void pika_dbus_service_idle_data_free (IdleData *data);
|
||||
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (PikaDBusService, pika_dbus_service,
|
||||
PIKA_DBUS_SERVICE_TYPE_UI_SKELETON,
|
||||
G_IMPLEMENT_INTERFACE (PIKA_DBUS_SERVICE_TYPE_UI,
|
||||
pika_dbus_service_ui_iface_init))
|
||||
|
||||
#define parent_class pika_dbus_service_parent_class
|
||||
|
||||
|
||||
static void
|
||||
pika_dbus_service_class_init (PikaDBusServiceClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->dispose = pika_dbus_service_dispose;
|
||||
object_class->finalize = pika_dbus_service_finalize;
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_init (PikaDBusService *service)
|
||||
{
|
||||
service->queue = g_queue_new ();
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_ui_iface_init (PikaDBusServiceUIIface *iface)
|
||||
{
|
||||
iface->handle_activate = pika_dbus_service_activate;
|
||||
iface->handle_open = pika_dbus_service_open;
|
||||
iface->handle_open_as_new = pika_dbus_service_open_as_new;
|
||||
iface->handle_batch_run = pika_dbus_service_batch_run;
|
||||
}
|
||||
|
||||
GObject *
|
||||
pika_dbus_service_new (Pika *pika)
|
||||
{
|
||||
PikaDBusService *service;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
|
||||
service = g_object_new (PIKA_TYPE_DBUS_SERVICE, NULL);
|
||||
|
||||
service->pika = pika;
|
||||
|
||||
g_signal_connect_object (pika, "image-opened",
|
||||
G_CALLBACK (pika_dbus_service_pika_opened),
|
||||
service, 0);
|
||||
|
||||
return G_OBJECT (service);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_dispose (GObject *object)
|
||||
{
|
||||
PikaDBusService *service = PIKA_DBUS_SERVICE (object);
|
||||
|
||||
if (service->source)
|
||||
{
|
||||
g_source_remove (g_source_get_id (service->source));
|
||||
service->source = NULL;
|
||||
service->timeout_source = FALSE;
|
||||
}
|
||||
|
||||
while (! g_queue_is_empty (service->queue))
|
||||
{
|
||||
pika_dbus_service_idle_data_free (g_queue_pop_head (service->queue));
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->dispose (object);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_finalize (GObject *object)
|
||||
{
|
||||
PikaDBusService *service = PIKA_DBUS_SERVICE (object);
|
||||
|
||||
if (service->queue)
|
||||
{
|
||||
g_queue_free (service->queue);
|
||||
service->queue = NULL;
|
||||
}
|
||||
|
||||
G_OBJECT_CLASS (parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_dbus_service_activate (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation)
|
||||
{
|
||||
Pika *pika = PIKA_DBUS_SERVICE (service)->pika;
|
||||
|
||||
/* We want to be called again later in case that PIKA is not fully
|
||||
* started yet.
|
||||
*/
|
||||
if (pika_is_restored (pika))
|
||||
{
|
||||
PikaObject *display;
|
||||
|
||||
display = pika_container_get_first_child (pika->displays);
|
||||
|
||||
if (display)
|
||||
pika_display_shell_present (pika_display_get_shell (PIKA_DISPLAY (display)));
|
||||
}
|
||||
|
||||
pika_dbus_service_ui_complete_activate (service, invocation);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_dbus_service_open (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *uri)
|
||||
{
|
||||
gboolean success;
|
||||
|
||||
success = pika_dbus_service_queue_open (PIKA_DBUS_SERVICE (service),
|
||||
uri, FALSE);
|
||||
|
||||
pika_dbus_service_ui_complete_open (service, invocation, success);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_dbus_service_open_as_new (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *uri)
|
||||
{
|
||||
gboolean success;
|
||||
|
||||
success = pika_dbus_service_queue_open (PIKA_DBUS_SERVICE (service),
|
||||
uri, TRUE);
|
||||
|
||||
pika_dbus_service_ui_complete_open_as_new (service, invocation, success);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
pika_dbus_service_batch_run (PikaDBusServiceUI *service,
|
||||
GDBusMethodInvocation *invocation,
|
||||
const gchar *batch_interpreter,
|
||||
const gchar *batch_command)
|
||||
{
|
||||
gboolean success;
|
||||
|
||||
success = pika_dbus_service_queue_batch (PIKA_DBUS_SERVICE (service),
|
||||
batch_interpreter,
|
||||
batch_command);
|
||||
|
||||
pika_dbus_service_ui_complete_batch_run (service, invocation, success);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_pika_opened (Pika *pika,
|
||||
GFile *file,
|
||||
PikaDBusService *service)
|
||||
{
|
||||
gchar *uri = g_file_get_uri (file);
|
||||
|
||||
g_signal_emit_by_name (service, "opened", uri);
|
||||
|
||||
g_free (uri);
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds a request to open a file to the end of the queue and
|
||||
* starts an idle source if it is not already running.
|
||||
*/
|
||||
static gboolean
|
||||
pika_dbus_service_queue_open (PikaDBusService *service,
|
||||
const gchar *uri,
|
||||
gboolean as_new)
|
||||
{
|
||||
g_queue_push_tail (service->queue,
|
||||
pika_dbus_service_open_data_new (service, uri, as_new));
|
||||
|
||||
if (! service->source)
|
||||
{
|
||||
service->source = g_idle_source_new ();
|
||||
service->timeout_source = FALSE;
|
||||
|
||||
g_source_set_priority (service->source, G_PRIORITY_LOW);
|
||||
g_source_set_callback (service->source,
|
||||
(GSourceFunc) pika_dbus_service_process_idle,
|
||||
service,
|
||||
NULL);
|
||||
g_source_attach (service->source, NULL);
|
||||
g_source_unref (service->source);
|
||||
}
|
||||
|
||||
/* The call always succeeds as it is handled in one way or another.
|
||||
* Even presenting an error message is considered success ;-)
|
||||
*/
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Adds a request to run a batch command to the end of the queue and
|
||||
* starts an idle source if it is not already running.
|
||||
*/
|
||||
static gboolean
|
||||
pika_dbus_service_queue_batch (PikaDBusService *service,
|
||||
const gchar *interpreter,
|
||||
const gchar *command)
|
||||
{
|
||||
g_queue_push_tail (service->queue,
|
||||
pika_dbus_service_batch_data_new (service,
|
||||
interpreter,
|
||||
command));
|
||||
|
||||
if (! service->source)
|
||||
{
|
||||
service->source = g_idle_source_new ();
|
||||
service->timeout_source = FALSE;
|
||||
|
||||
g_source_set_priority (service->source, G_PRIORITY_LOW);
|
||||
g_source_set_callback (service->source,
|
||||
(GSourceFunc) pika_dbus_service_process_idle,
|
||||
service,
|
||||
NULL);
|
||||
g_source_attach (service->source, NULL);
|
||||
g_source_unref (service->source);
|
||||
}
|
||||
|
||||
/* The call always succeeds as it is handled in one way or another.
|
||||
* Even presenting an error message is considered success ;-)
|
||||
*/
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/*
|
||||
* Idle callback that removes the first request from the queue and
|
||||
* handles it. If there are no more requests, the idle source is
|
||||
* removed.
|
||||
*/
|
||||
static gboolean
|
||||
pika_dbus_service_process_idle (PikaDBusService *service)
|
||||
{
|
||||
IdleData *data;
|
||||
|
||||
if (! pika_is_restored (service->pika))
|
||||
{
|
||||
if (! service->timeout_source)
|
||||
{
|
||||
/* We are probably starting the program. No need to spam PIKA with
|
||||
* an idle handler (which might make PIKA slower to start even
|
||||
* with low priority).
|
||||
* Instead let's add a timeout of half a second.
|
||||
*/
|
||||
service->source = g_timeout_source_new (500);
|
||||
service->timeout_source = TRUE;
|
||||
|
||||
g_source_set_priority (service->source, G_PRIORITY_LOW);
|
||||
g_source_set_callback (service->source,
|
||||
(GSourceFunc) pika_dbus_service_process_idle,
|
||||
service,
|
||||
NULL);
|
||||
g_source_attach (service->source, NULL);
|
||||
g_source_unref (service->source);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process data as a FIFO. */
|
||||
data = g_queue_pop_head (service->queue);
|
||||
|
||||
if (data)
|
||||
{
|
||||
if (data->file)
|
||||
file_open_from_command_line (service->pika, data->file, data->as_new,
|
||||
NULL /* FIXME monitor */);
|
||||
|
||||
if (data->command)
|
||||
{
|
||||
const gchar *commands[2] = {data->command, 0};
|
||||
|
||||
pika_batch_run (service->pika, data->interpreter,
|
||||
commands);
|
||||
}
|
||||
|
||||
pika_dbus_service_idle_data_free (data);
|
||||
|
||||
if (service->timeout_source)
|
||||
{
|
||||
/* Now PIKA is fully functional and can respond quickly to
|
||||
* DBus calls. Switch to a usual idle source.
|
||||
*/
|
||||
service->source = g_idle_source_new ();
|
||||
service->timeout_source = FALSE;
|
||||
|
||||
g_source_set_priority (service->source, G_PRIORITY_LOW);
|
||||
g_source_set_callback (service->source,
|
||||
(GSourceFunc) pika_dbus_service_process_idle,
|
||||
service,
|
||||
NULL);
|
||||
g_source_attach (service->source, NULL);
|
||||
g_source_unref (service->source);
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
}
|
||||
|
||||
service->source = NULL;
|
||||
|
||||
return G_SOURCE_REMOVE;
|
||||
}
|
||||
|
||||
static IdleData *
|
||||
pika_dbus_service_open_data_new (PikaDBusService *service,
|
||||
const gchar *uri,
|
||||
gboolean as_new)
|
||||
{
|
||||
IdleData *data = g_slice_new (IdleData);
|
||||
|
||||
data->file = g_file_new_for_uri (uri);
|
||||
data->as_new = as_new;
|
||||
data->interpreter = NULL;
|
||||
data->command = NULL;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static IdleData *
|
||||
pika_dbus_service_batch_data_new (PikaDBusService *service,
|
||||
const gchar *interpreter,
|
||||
const gchar *command)
|
||||
{
|
||||
IdleData *data = g_slice_new (IdleData);
|
||||
|
||||
data->file = NULL;
|
||||
data->as_new = FALSE;
|
||||
|
||||
data->command = g_strdup (command);
|
||||
|
||||
if (g_strcmp0 (interpreter, "") == 0)
|
||||
data->interpreter = NULL;
|
||||
else
|
||||
data->interpreter = g_strdup (interpreter);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static void
|
||||
pika_dbus_service_idle_data_free (IdleData *data)
|
||||
{
|
||||
if (data->file)
|
||||
g_object_unref (data->file);
|
||||
|
||||
if (data->command)
|
||||
g_free (data->command);
|
||||
if (data->interpreter)
|
||||
g_free (data->interpreter);
|
||||
|
||||
g_slice_free (IdleData, data);
|
||||
}
|
73
app/gui/pikadbusservice.h
Normal file
73
app/gui/pikadbusservice.h
Normal file
@ -0,0 +1,73 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* PikaDBusService
|
||||
* Copyright (C) 2007, 2008 Sven Neumann <sven@gimp.org>
|
||||
* Copyright (C) 2013 Michael Natterer <mitch@gimp.org>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __PIKA_DBUS_SERVICE_H__
|
||||
#define __PIKA_DBUS_SERVICE_H__
|
||||
|
||||
|
||||
#include "pikadbusservice-generated.h"
|
||||
|
||||
/* service name and path should really be technology.heckin.PIKA and
|
||||
* /technology.heckin/PIKA and only the interface be called UI.
|
||||
*/
|
||||
#define PIKA_DBUS_SERVICE_NAME "technology.heckin.PIKA.UI"
|
||||
#define PIKA_DBUS_SERVICE_PATH "/technology.heckin/PIKA/UI"
|
||||
#define PIKA_DBUS_INTERFACE_NAME "technology.heckin.PIKA.UI"
|
||||
#define PIKA_DBUS_INTERFACE_PATH "/technology.heckin/PIKA/UI"
|
||||
|
||||
|
||||
#define PIKA_TYPE_DBUS_SERVICE (pika_dbus_service_get_type ())
|
||||
#define PIKA_DBUS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_DBUS_SERVICE, PikaDBusService))
|
||||
#define PIKA_DBUS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), PIKA_TYPE_DBUS_SERVICE, PikaDBusServiceClass))
|
||||
#define PIKA_IS_DBUS_SERVICE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_DBUS_SERVICE))
|
||||
#define PIKA_IS_DBUS_SERVICE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), PIKA_TYPE_DBUS_SERVICE))
|
||||
#define PIKA_DBUS_SERVICE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), PIKA_TYPE_DBUS_SERVICE, PikaDBusServiceClass))
|
||||
|
||||
|
||||
typedef struct _PikaDBusService PikaDBusService;
|
||||
typedef struct _PikaDBusServiceClass PikaDBusServiceClass;
|
||||
|
||||
struct _PikaDBusService
|
||||
{
|
||||
PikaDBusServiceUISkeleton parent_instance;
|
||||
|
||||
Pika *pika;
|
||||
GQueue *queue;
|
||||
GSource *source;
|
||||
gboolean timeout_source;
|
||||
};
|
||||
|
||||
struct _PikaDBusServiceClass
|
||||
{
|
||||
PikaDBusServiceUISkeletonClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
GType pika_dbus_service_get_type (void) G_GNUC_CONST;
|
||||
|
||||
GObject * pika_dbus_service_new (Pika *pika);
|
||||
|
||||
|
||||
#endif /* __PIKA_DBUS_SERVICE_H__ */
|
629
app/gui/pikauiconfigurer.c
Normal file
629
app/gui/pikauiconfigurer.c
Normal file
@ -0,0 +1,629 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* pikauiconfigurer.c
|
||||
* Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikawidgets/pikawidgets.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikacontext.h"
|
||||
|
||||
#include "widgets/pikadialogfactory.h"
|
||||
#include "widgets/pikadock.h"
|
||||
#include "widgets/pikadockcolumns.h"
|
||||
#include "widgets/pikadockcontainer.h"
|
||||
#include "widgets/pikadockwindow.h"
|
||||
#include "widgets/pikatoolbox.h"
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
|
||||
#include "display/pikadisplay.h"
|
||||
#include "display/pikadisplayshell.h"
|
||||
#include "display/pikadisplayshell-appearance.h"
|
||||
#include "display/pikaimagewindow.h"
|
||||
|
||||
#include "pikauiconfigurer.h"
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
PROP_0,
|
||||
PROP_PIKA
|
||||
};
|
||||
|
||||
|
||||
struct _PikaUIConfigurerPrivate
|
||||
{
|
||||
Pika *pika;
|
||||
};
|
||||
|
||||
|
||||
static void pika_ui_configurer_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_ui_configurer_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec);
|
||||
static void pika_ui_configurer_move_docks_to_columns (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *uber_image_window);
|
||||
static void pika_ui_configurer_move_shells (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *source_image_window,
|
||||
PikaImageWindow *target_image_window);
|
||||
static void pika_ui_configurer_separate_docks (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *source_image_window);
|
||||
static void pika_ui_configurer_move_docks_to_window (PikaUIConfigurer *ui_configurer,
|
||||
PikaDockColumns *dock_columns,
|
||||
PikaAlignmentType screen_side);
|
||||
static void pika_ui_configurer_separate_shells (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *source_image_window);
|
||||
static void pika_ui_configurer_configure_for_single_window (PikaUIConfigurer *ui_configurer);
|
||||
static void pika_ui_configurer_configure_for_multi_window (PikaUIConfigurer *ui_configurer);
|
||||
static PikaImageWindow * pika_ui_configurer_get_uber_window (PikaUIConfigurer *ui_configurer);
|
||||
|
||||
|
||||
G_DEFINE_TYPE_WITH_PRIVATE (PikaUIConfigurer, pika_ui_configurer,
|
||||
PIKA_TYPE_OBJECT)
|
||||
|
||||
#define parent_class pika_ui_configurer_parent_class
|
||||
|
||||
|
||||
static void
|
||||
pika_ui_configurer_class_init (PikaUIConfigurerClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->set_property = pika_ui_configurer_set_property;
|
||||
object_class->get_property = pika_ui_configurer_get_property;
|
||||
|
||||
g_object_class_install_property (object_class, PROP_PIKA,
|
||||
g_param_spec_object ("pika", NULL, NULL,
|
||||
PIKA_TYPE_PIKA,
|
||||
PIKA_PARAM_READWRITE |
|
||||
G_PARAM_CONSTRUCT_ONLY));
|
||||
}
|
||||
|
||||
static void
|
||||
pika_ui_configurer_init (PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
ui_configurer->p = pika_ui_configurer_get_instance_private (ui_configurer);
|
||||
}
|
||||
|
||||
static void
|
||||
pika_ui_configurer_set_property (GObject *object,
|
||||
guint property_id,
|
||||
const GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
PikaUIConfigurer *ui_configurer = PIKA_UI_CONFIGURER (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_PIKA:
|
||||
ui_configurer->p->pika = g_value_get_object (value); /* don't ref */
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
pika_ui_configurer_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
PikaUIConfigurer *ui_configurer = PIKA_UI_CONFIGURER (object);
|
||||
|
||||
switch (property_id)
|
||||
{
|
||||
case PROP_PIKA:
|
||||
g_value_set_object (value, ui_configurer->p->pika);
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
pika_ui_configurer_get_window_center_pos (GtkWindow *window,
|
||||
gint *out_x,
|
||||
gint *out_y)
|
||||
{
|
||||
gint x, y, w, h;
|
||||
gtk_window_get_position (window, &x, &y);
|
||||
gtk_window_get_size (window, &w, &h);
|
||||
|
||||
if (out_x)
|
||||
*out_x = x + w / 2;
|
||||
if (out_y)
|
||||
*out_y = y + h / 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_get_relative_window_pos:
|
||||
* @window_a:
|
||||
* @window_b:
|
||||
*
|
||||
* Returns: At what side @window_b is relative to @window_a. Either
|
||||
* PIKA_ALIGN_LEFT or PIKA_ALIGN_RIGHT.
|
||||
**/
|
||||
static PikaAlignmentType
|
||||
pika_ui_configurer_get_relative_window_pos (GtkWindow *window_a,
|
||||
GtkWindow *window_b)
|
||||
{
|
||||
gint a_x, b_x;
|
||||
|
||||
pika_ui_configurer_get_window_center_pos (window_a, &a_x, NULL);
|
||||
pika_ui_configurer_get_window_center_pos (window_b, &b_x, NULL);
|
||||
|
||||
return b_x < a_x ? PIKA_ALIGN_LEFT : PIKA_ALIGN_RIGHT;
|
||||
}
|
||||
|
||||
static void
|
||||
pika_ui_configurer_move_docks_to_columns (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *uber_image_window)
|
||||
{
|
||||
GList *dialogs = NULL;
|
||||
GList *dialog_iter = NULL;
|
||||
|
||||
dialogs =
|
||||
g_list_copy (pika_dialog_factory_get_open_dialogs (pika_dialog_factory_get_singleton ()));
|
||||
|
||||
for (dialog_iter = dialogs; dialog_iter; dialog_iter = dialog_iter->next)
|
||||
{
|
||||
PikaDockWindow *dock_window;
|
||||
PikaDockContainer *dock_container;
|
||||
PikaDockColumns *dock_columns;
|
||||
GList *docks;
|
||||
GList *dock_iter;
|
||||
|
||||
if (!PIKA_IS_DOCK_WINDOW (dialog_iter->data))
|
||||
continue;
|
||||
|
||||
dock_window = PIKA_DOCK_WINDOW (dialog_iter->data);
|
||||
|
||||
/* If the dock window is on the left side of the image window,
|
||||
* move the docks to the left side. If the dock window is on the
|
||||
* right side, move the docks to the right side of the image
|
||||
* window.
|
||||
*/
|
||||
if (pika_ui_configurer_get_relative_window_pos (GTK_WINDOW (uber_image_window),
|
||||
GTK_WINDOW (dock_window)) == PIKA_ALIGN_LEFT)
|
||||
dock_columns = pika_image_window_get_left_docks (uber_image_window);
|
||||
else
|
||||
dock_columns = pika_image_window_get_right_docks (uber_image_window);
|
||||
|
||||
dock_container = PIKA_DOCK_CONTAINER (dock_window);
|
||||
|
||||
g_object_add_weak_pointer (G_OBJECT (dock_window),
|
||||
(gpointer) &dock_window);
|
||||
|
||||
docks = pika_dock_container_get_docks (dock_container);
|
||||
for (dock_iter = docks; dock_iter; dock_iter = dock_iter->next)
|
||||
{
|
||||
PikaDock *dock = PIKA_DOCK (dock_iter->data);
|
||||
|
||||
/* Move the dock from the image window to the dock columns
|
||||
* widget. Note that we need a ref while the dock is parentless
|
||||
*/
|
||||
g_object_ref (dock);
|
||||
pika_dock_window_remove_dock (dock_window, dock);
|
||||
pika_dock_columns_add_dock (dock_columns, dock, -1);
|
||||
g_object_unref (dock);
|
||||
}
|
||||
g_list_free (docks);
|
||||
|
||||
if (dock_window)
|
||||
g_object_remove_weak_pointer (G_OBJECT (dock_window),
|
||||
(gpointer) &dock_window);
|
||||
|
||||
/* Kill the window if removing the dock didn't destroy it
|
||||
* already. This will be the case for the toolbox dock window
|
||||
*/
|
||||
if (GTK_IS_WIDGET (dock_window))
|
||||
{
|
||||
guint docks_len;
|
||||
|
||||
docks = pika_dock_container_get_docks (dock_container);
|
||||
docks_len = g_list_length (docks);
|
||||
|
||||
if (docks_len == 0)
|
||||
{
|
||||
pika_dialog_factory_remove_dialog (pika_dialog_factory_get_singleton (),
|
||||
GTK_WIDGET (dock_window));
|
||||
gtk_widget_destroy (GTK_WIDGET (dock_window));
|
||||
}
|
||||
|
||||
g_list_free (docks);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free (dialogs);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_move_shells:
|
||||
* @ui_configurer:
|
||||
* @source_image_window:
|
||||
* @target_image_window:
|
||||
*
|
||||
* Move all display shells from one image window to the another.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_move_shells (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *source_image_window,
|
||||
PikaImageWindow *target_image_window)
|
||||
{
|
||||
while (pika_image_window_get_n_shells (source_image_window) > 0)
|
||||
{
|
||||
PikaDisplayShell *shell;
|
||||
|
||||
shell = pika_image_window_get_shell (source_image_window, 0);
|
||||
|
||||
g_object_ref (shell);
|
||||
pika_image_window_remove_shell (source_image_window, shell);
|
||||
pika_image_window_add_shell (target_image_window, shell);
|
||||
g_object_unref (shell);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_separate_docks:
|
||||
* @ui_configurer:
|
||||
* @image_window:
|
||||
*
|
||||
* Move out the docks from the image window.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_separate_docks (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *image_window)
|
||||
{
|
||||
PikaDockColumns *left_docks = NULL;
|
||||
PikaDockColumns *right_docks = NULL;
|
||||
|
||||
left_docks = pika_image_window_get_left_docks (image_window);
|
||||
right_docks = pika_image_window_get_right_docks (image_window);
|
||||
|
||||
pika_ui_configurer_move_docks_to_window (ui_configurer, left_docks, PIKA_ALIGN_LEFT);
|
||||
pika_ui_configurer_move_docks_to_window (ui_configurer, right_docks, PIKA_ALIGN_RIGHT);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_move_docks_to_window:
|
||||
* @dock_columns:
|
||||
* @screen_side: At what side of the screen the dock window should be put.
|
||||
*
|
||||
* Moves docks in @dock_columns into a new #PikaDockWindow and
|
||||
* position it on the screen in a non-overlapping manner.
|
||||
*/
|
||||
static void
|
||||
pika_ui_configurer_move_docks_to_window (PikaUIConfigurer *ui_configurer,
|
||||
PikaDockColumns *dock_columns,
|
||||
PikaAlignmentType screen_side)
|
||||
{
|
||||
GdkMonitor *monitor;
|
||||
GdkRectangle monitor_rect;
|
||||
GdkGravity gravity;
|
||||
GList *docks;
|
||||
GList *iter;
|
||||
gboolean contains_toolbox = FALSE;
|
||||
GtkWidget *dock_window;
|
||||
GtkAllocation original_size;
|
||||
gint x, y;
|
||||
|
||||
docks = g_list_copy (pika_dock_columns_get_docks (dock_columns));
|
||||
if (! docks)
|
||||
return;
|
||||
|
||||
monitor = pika_widget_get_monitor (GTK_WIDGET (dock_columns));
|
||||
|
||||
gdk_monitor_get_workarea (monitor, &monitor_rect);
|
||||
|
||||
/* Remember the size so we can set the new dock window to the same
|
||||
* size
|
||||
*/
|
||||
gtk_widget_get_allocation (GTK_WIDGET (dock_columns), &original_size);
|
||||
|
||||
/* Do we need a toolbox window? */
|
||||
for (iter = docks; iter; iter = g_list_next (iter))
|
||||
{
|
||||
PikaDock *dock = PIKA_DOCK (iter->data);
|
||||
|
||||
if (PIKA_IS_TOOLBOX (dock))
|
||||
{
|
||||
contains_toolbox = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a dock window to put the dock in. Checking for
|
||||
* PIKA_IS_TOOLBOX() is kind of ugly but not a disaster. We need
|
||||
* the dock window correctly configured if we create it for the
|
||||
* toolbox
|
||||
*/
|
||||
dock_window =
|
||||
pika_dialog_factory_dialog_new (pika_dialog_factory_get_singleton (),
|
||||
monitor,
|
||||
NULL /*ui_manager*/,
|
||||
GTK_WIDGET (dock_columns),
|
||||
(contains_toolbox ?
|
||||
"pika-toolbox-window" :
|
||||
"pika-dock-window"),
|
||||
-1 /*view_size*/,
|
||||
FALSE /*present*/);
|
||||
|
||||
for (iter = docks; iter; iter = g_list_next (iter))
|
||||
{
|
||||
PikaDock *dock = PIKA_DOCK (iter->data);
|
||||
|
||||
/* Move the dock to the window */
|
||||
g_object_ref (dock);
|
||||
pika_dock_columns_remove_dock (dock_columns, dock);
|
||||
pika_dock_window_add_dock (PIKA_DOCK_WINDOW (dock_window), dock, -1);
|
||||
g_object_unref (dock);
|
||||
}
|
||||
|
||||
/* Position the window */
|
||||
if (screen_side == PIKA_ALIGN_LEFT)
|
||||
{
|
||||
gravity = GDK_GRAVITY_NORTH_WEST;
|
||||
|
||||
x = monitor_rect.x;
|
||||
y = monitor_rect.y;
|
||||
}
|
||||
else if (screen_side == PIKA_ALIGN_RIGHT)
|
||||
{
|
||||
gravity = GDK_GRAVITY_NORTH_EAST;
|
||||
|
||||
x = monitor_rect.x + monitor_rect.width - original_size.width;
|
||||
y = monitor_rect.y;
|
||||
}
|
||||
else
|
||||
{
|
||||
pika_assert_not_reached ();
|
||||
}
|
||||
|
||||
gtk_window_set_gravity (GTK_WINDOW (dock_window), gravity);
|
||||
gtk_window_move (GTK_WINDOW (dock_window), x, y);
|
||||
|
||||
/* Try to keep the same size */
|
||||
gtk_window_set_default_size (GTK_WINDOW (dock_window),
|
||||
original_size.width,
|
||||
original_size.height);
|
||||
|
||||
/* Don't forget to show the window */
|
||||
gtk_widget_show (dock_window);
|
||||
|
||||
g_list_free (docks);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_separate_shells:
|
||||
* @ui_configurer:
|
||||
* @source_image_window:
|
||||
*
|
||||
* Create one image window per display shell and move it there.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_separate_shells (PikaUIConfigurer *ui_configurer,
|
||||
PikaImageWindow *source_image_window)
|
||||
{
|
||||
PikaDisplayShell *active_shell = pika_image_window_get_active_shell (source_image_window);
|
||||
PikaImageWindow *active_window = NULL;
|
||||
|
||||
/* The last display shell remains in its window */
|
||||
while (pika_image_window_get_n_shells (source_image_window) > 1)
|
||||
{
|
||||
PikaImageWindow *new_image_window;
|
||||
PikaDisplayShell *shell;
|
||||
|
||||
/* Create a new image window */
|
||||
new_image_window = pika_image_window_new (ui_configurer->p->pika,
|
||||
NULL,
|
||||
pika_dialog_factory_get_singleton (),
|
||||
pika_widget_get_monitor (GTK_WIDGET (source_image_window)));
|
||||
/* Move the shell there */
|
||||
shell = pika_image_window_get_shell (source_image_window, 1);
|
||||
|
||||
if (shell == active_shell)
|
||||
active_window = new_image_window;
|
||||
|
||||
g_object_ref (shell);
|
||||
pika_image_window_remove_shell (source_image_window, shell);
|
||||
pika_image_window_add_shell (new_image_window, shell);
|
||||
g_object_unref (shell);
|
||||
|
||||
/* FIXME: If we don't set a size request here the window will be
|
||||
* too small. Get rid of this hack and fix it the proper way
|
||||
*/
|
||||
gtk_widget_set_size_request (GTK_WIDGET (new_image_window), 640, 480);
|
||||
|
||||
/* Show after we have added the shell */
|
||||
gtk_widget_show (GTK_WIDGET (new_image_window));
|
||||
}
|
||||
|
||||
/* If none of the shells were active, I assume the first one is. */
|
||||
if (active_window == NULL)
|
||||
active_window = source_image_window;
|
||||
|
||||
/* The active tab must stay at the top of the windows stack. */
|
||||
gtk_window_present (GTK_WINDOW (active_window));
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_configure_for_single_window:
|
||||
* @ui_configurer:
|
||||
*
|
||||
* Move docks and display shells into a single window.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_configure_for_single_window (PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
Pika *pika = ui_configurer->p->pika;
|
||||
GList *windows = pika_get_image_windows (pika);
|
||||
GList *iter = NULL;
|
||||
PikaImageWindow *uber_image_window = NULL;
|
||||
PikaDisplay *active_display = pika_context_get_display (pika_get_user_context (pika));
|
||||
PikaDisplayShell *active_shell = pika_display_get_shell (active_display);
|
||||
|
||||
/* Get and setup the window to put everything in */
|
||||
uber_image_window = pika_ui_configurer_get_uber_window (ui_configurer);
|
||||
|
||||
/* Mve docks to the left and right side of the image window */
|
||||
pika_ui_configurer_move_docks_to_columns (ui_configurer,
|
||||
uber_image_window);
|
||||
|
||||
/* Move image shells from other windows to the uber image window */
|
||||
for (iter = windows; iter; iter = g_list_next (iter))
|
||||
{
|
||||
PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (iter->data);
|
||||
|
||||
/* Don't move stuff to itself */
|
||||
if (image_window == uber_image_window)
|
||||
continue;
|
||||
|
||||
/* Put the displays in the rest of the image windows into
|
||||
* the uber image window
|
||||
*/
|
||||
pika_ui_configurer_move_shells (ui_configurer,
|
||||
image_window,
|
||||
uber_image_window);
|
||||
/* Destroy the window */
|
||||
pika_image_window_destroy (image_window);
|
||||
}
|
||||
|
||||
/* Ensure the context shell remains active after mode switch. */
|
||||
pika_image_window_set_active_shell (uber_image_window, active_shell);
|
||||
|
||||
g_list_free (windows);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_configure_for_multi_window:
|
||||
* @ui_configurer:
|
||||
*
|
||||
* Moves all display shells into their own image window.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_configure_for_multi_window (PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
Pika *pika = ui_configurer->p->pika;
|
||||
GList *windows = pika_get_image_windows (pika);
|
||||
GList *iter = NULL;
|
||||
|
||||
for (iter = windows; iter; iter = g_list_next (iter))
|
||||
{
|
||||
PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (iter->data);
|
||||
|
||||
pika_ui_configurer_separate_docks (ui_configurer, image_window);
|
||||
|
||||
pika_ui_configurer_separate_shells (ui_configurer, image_window);
|
||||
}
|
||||
|
||||
g_list_free (windows);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_get_uber_window:
|
||||
* @ui_configurer:
|
||||
*
|
||||
* Returns: The window to be used as the main window for single-window
|
||||
* mode.
|
||||
**/
|
||||
static PikaImageWindow *
|
||||
pika_ui_configurer_get_uber_window (PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
Pika *pika = ui_configurer->p->pika;
|
||||
PikaDisplay *display = pika_get_display_iter (pika)->data;
|
||||
PikaDisplayShell *shell = pika_display_get_shell (display);
|
||||
PikaImageWindow *image_window = pika_display_shell_get_window (shell);
|
||||
|
||||
return image_window;
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_update_appearance:
|
||||
* @ui_configurer:
|
||||
*
|
||||
* Updates the appearance of all shells in all image windows, so they
|
||||
* do whatever they deem necessary to fit the new UI mode mode.
|
||||
**/
|
||||
static void
|
||||
pika_ui_configurer_update_appearance (PikaUIConfigurer *ui_configurer)
|
||||
{
|
||||
Pika *pika = ui_configurer->p->pika;
|
||||
GList *windows = pika_get_image_windows (pika);
|
||||
GList *list;
|
||||
|
||||
for (list = windows; list; list = g_list_next (list))
|
||||
{
|
||||
PikaImageWindow *image_window = PIKA_IMAGE_WINDOW (list->data);
|
||||
gint n_shells;
|
||||
gint i;
|
||||
|
||||
n_shells = pika_image_window_get_n_shells (image_window);
|
||||
|
||||
for (i = 0; i < n_shells; i++)
|
||||
{
|
||||
PikaDisplayShell *shell;
|
||||
|
||||
shell = pika_image_window_get_shell (image_window, i);
|
||||
|
||||
pika_display_shell_appearance_update (shell);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free (windows);
|
||||
}
|
||||
|
||||
/**
|
||||
* pika_ui_configurer_configure:
|
||||
* @ui_configurer:
|
||||
* @single_window_mode:
|
||||
*
|
||||
* Configure the UI.
|
||||
**/
|
||||
void
|
||||
pika_ui_configurer_configure (PikaUIConfigurer *ui_configurer,
|
||||
gboolean single_window_mode)
|
||||
{
|
||||
if (single_window_mode)
|
||||
pika_ui_configurer_configure_for_single_window (ui_configurer);
|
||||
else
|
||||
pika_ui_configurer_configure_for_multi_window (ui_configurer);
|
||||
|
||||
pika_ui_configurer_update_appearance (ui_configurer);
|
||||
}
|
61
app/gui/pikauiconfigurer.h
Normal file
61
app/gui/pikauiconfigurer.h
Normal file
@ -0,0 +1,61 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* pikauiconfigurer.h
|
||||
* Copyright (C) 2009 Martin Nordholts <martinn@src.gnome.org>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __PIKA_UI_CONFIGURER_H__
|
||||
#define __PIKA_UI_CONFIGURER_H__
|
||||
|
||||
|
||||
#include "core/pikaobject.h"
|
||||
|
||||
|
||||
#define PIKA_TYPE_UI_CONFIGURER (pika_ui_configurer_get_type ())
|
||||
#define PIKA_UI_CONFIGURER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_TYPE_UI_CONFIGURER, PikaUIConfigurer))
|
||||
#define PIKA_UI_CONFIGURER_CLASS(vtable) (G_TYPE_CHECK_CLASS_CAST ((vtable), PIKA_TYPE_UI_CONFIGURER, PikaUIConfigurerClass))
|
||||
#define PIKA_IS_UI_CONFIGURER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), PIKA_TYPE_UI_CONFIGURER))
|
||||
#define PIKA_IS_UI_CONFIGURER_CLASS(vtable) (G_TYPE_CHECK_CLASS_TYPE ((vtable), PIKA_TYPE_UI_CONFIGURER))
|
||||
#define PIKA_UI_CONFIGURER_GET_CLASS(inst) (G_TYPE_INSTANCE_GET_CLASS ((inst), PIKA_TYPE_UI_CONFIGURER, PikaUIConfigurerClass))
|
||||
|
||||
|
||||
typedef struct _PikaUIConfigurerClass PikaUIConfigurerClass;
|
||||
typedef struct _PikaUIConfigurerPrivate PikaUIConfigurerPrivate;
|
||||
|
||||
struct _PikaUIConfigurer
|
||||
{
|
||||
PikaObject parent_instance;
|
||||
|
||||
PikaUIConfigurerPrivate *p;
|
||||
};
|
||||
|
||||
struct _PikaUIConfigurerClass
|
||||
{
|
||||
PikaObjectClass parent_class;
|
||||
};
|
||||
|
||||
|
||||
GType pika_ui_configurer_get_type (void) G_GNUC_CONST;
|
||||
void pika_ui_configurer_configure (PikaUIConfigurer *ui_configurer,
|
||||
gboolean single_window_mode);
|
||||
|
||||
|
||||
#endif /* __PIKA_UI_CONFIGURER_H__ */
|
489
app/gui/session.c
Normal file
489
app/gui/session.c
Normal file
@ -0,0 +1,489 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* Session-managment stuff
|
||||
* Copyright (C) 1998 Sven Neumann <sven@gimp.org>
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikaconfig/pikaconfig.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "config/pikaconfig-file.h"
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
#include "core/pikaerror.h"
|
||||
|
||||
#include "widgets/pikadialogfactory.h"
|
||||
#include "widgets/pikasessioninfo.h"
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
|
||||
#include "dialogs/dialogs.h"
|
||||
|
||||
#include "session.h"
|
||||
#include "pika-log.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
enum
|
||||
{
|
||||
SESSION_INFO = 1,
|
||||
HIDE_DOCKS,
|
||||
SINGLE_WINDOW_MODE,
|
||||
SHOW_TABS,
|
||||
TABS_POSITION,
|
||||
LAST_TIP_SHOWN
|
||||
};
|
||||
|
||||
|
||||
static GFile * session_file (Pika *pika);
|
||||
|
||||
|
||||
/* private variables */
|
||||
|
||||
static gboolean sessionrc_deleted = FALSE;
|
||||
|
||||
|
||||
/* public functions */
|
||||
|
||||
void
|
||||
session_init (Pika *pika)
|
||||
{
|
||||
GFile *file;
|
||||
GScanner *scanner;
|
||||
GTokenType token;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
file = session_file (pika);
|
||||
|
||||
scanner = pika_scanner_new_file (file, &error);
|
||||
|
||||
if (! scanner && error->code == PIKA_CONFIG_ERROR_OPEN_ENOENT)
|
||||
{
|
||||
g_clear_error (&error);
|
||||
g_object_unref (file);
|
||||
|
||||
file = pika_sysconf_directory_file ("sessionrc", NULL);
|
||||
|
||||
scanner = pika_scanner_new_file (file, NULL);
|
||||
}
|
||||
|
||||
if (! scanner)
|
||||
{
|
||||
g_clear_error (&error);
|
||||
g_object_unref (file);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Parsing '%s'\n", pika_file_get_utf8_name (file));
|
||||
|
||||
g_scanner_scope_add_symbol (scanner, 0, "session-info",
|
||||
GINT_TO_POINTER (SESSION_INFO));
|
||||
g_scanner_scope_add_symbol (scanner, 0, "hide-docks",
|
||||
GINT_TO_POINTER (HIDE_DOCKS));
|
||||
g_scanner_scope_add_symbol (scanner, 0, "single-window-mode",
|
||||
GINT_TO_POINTER (SINGLE_WINDOW_MODE));
|
||||
g_scanner_scope_add_symbol (scanner, 0, "show-tabs",
|
||||
GINT_TO_POINTER (SHOW_TABS));
|
||||
g_scanner_scope_add_symbol (scanner, 0, "tabs-position",
|
||||
GINT_TO_POINTER (TABS_POSITION));
|
||||
g_scanner_scope_add_symbol (scanner, 0, "last-tip-shown",
|
||||
GINT_TO_POINTER (LAST_TIP_SHOWN));
|
||||
|
||||
token = G_TOKEN_LEFT_PAREN;
|
||||
|
||||
while (g_scanner_peek_next_token (scanner) == token)
|
||||
{
|
||||
token = g_scanner_get_next_token (scanner);
|
||||
|
||||
switch (token)
|
||||
{
|
||||
case G_TOKEN_LEFT_PAREN:
|
||||
token = G_TOKEN_SYMBOL;
|
||||
break;
|
||||
|
||||
case G_TOKEN_SYMBOL:
|
||||
if (scanner->value.v_symbol == GINT_TO_POINTER (SESSION_INFO))
|
||||
{
|
||||
PikaDialogFactory *factory = NULL;
|
||||
PikaSessionInfo *info = NULL;
|
||||
gchar *factory_name = NULL;
|
||||
gchar *entry_name = NULL;
|
||||
PikaDialogFactoryEntry *entry = NULL;
|
||||
|
||||
token = G_TOKEN_STRING;
|
||||
|
||||
if (! pika_scanner_parse_string (scanner, &factory_name))
|
||||
break;
|
||||
|
||||
/* In versions <= PIKA 2.6 there was a "toolbox", a
|
||||
* "dock", a "display" and a "toplevel" factory. These
|
||||
* are now merged to a single pika_dialog_factory_get_singleton (). We
|
||||
* need the legacy name though, so keep it around.
|
||||
*/
|
||||
factory = pika_dialog_factory_get_singleton ();
|
||||
|
||||
info = pika_session_info_new ();
|
||||
|
||||
/* PIKA 2.6 has the entry name as part of the
|
||||
* session-info header, so try to get it
|
||||
*/
|
||||
pika_scanner_parse_string (scanner, &entry_name);
|
||||
if (entry_name)
|
||||
{
|
||||
/* Previously, PikaDock was a toplevel. That is why
|
||||
* versions <= PIKA 2.6 has "dock" as the entry name. We
|
||||
* want "dock" to be interpreted as 'dock window'
|
||||
* however so have some special-casing for that. When
|
||||
* the entry name is "dock" the factory name is either
|
||||
* "dock" or "toolbox".
|
||||
*/
|
||||
if (strcmp (entry_name, "dock") == 0)
|
||||
{
|
||||
entry =
|
||||
pika_dialog_factory_find_entry (factory,
|
||||
(strcmp (factory_name, "toolbox") == 0 ?
|
||||
"pika-toolbox-window" :
|
||||
"pika-dock-window"));
|
||||
}
|
||||
else
|
||||
{
|
||||
entry = pika_dialog_factory_find_entry (factory,
|
||||
entry_name);
|
||||
}
|
||||
}
|
||||
|
||||
/* We're done with these now */
|
||||
g_free (factory_name);
|
||||
g_free (entry_name);
|
||||
|
||||
/* We can get the factory entry either now (the PIKA <=
|
||||
* 2.6 way), or when we deserialize (the PIKA 2.8 way)
|
||||
*/
|
||||
if (entry)
|
||||
{
|
||||
pika_session_info_set_factory_entry (info, entry);
|
||||
}
|
||||
|
||||
/* Always try to deserialize */
|
||||
if (pika_config_deserialize (PIKA_CONFIG (info), scanner, 1, NULL))
|
||||
{
|
||||
/* Make sure we got a factory entry either the 2.6
|
||||
* or 2.8 way
|
||||
*/
|
||||
if (pika_session_info_get_factory_entry (info))
|
||||
{
|
||||
PIKA_LOG (DIALOG_FACTORY,
|
||||
"successfully parsed and added session info %p",
|
||||
info);
|
||||
|
||||
pika_dialog_factory_add_session_info (factory, info);
|
||||
}
|
||||
else
|
||||
{
|
||||
PIKA_LOG (DIALOG_FACTORY,
|
||||
"failed to parse session info %p, not adding",
|
||||
info);
|
||||
}
|
||||
|
||||
g_object_unref (info);
|
||||
}
|
||||
else
|
||||
{
|
||||
g_object_unref (info);
|
||||
|
||||
/* set token to left paren so we won't set another
|
||||
* error below, pika_config_deserialize() already did
|
||||
*/
|
||||
token = G_TOKEN_LEFT_PAREN;
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
else if (scanner->value.v_symbol == GINT_TO_POINTER (HIDE_DOCKS))
|
||||
{
|
||||
gboolean hide_docks;
|
||||
|
||||
token = G_TOKEN_IDENTIFIER;
|
||||
|
||||
if (! pika_scanner_parse_boolean (scanner, &hide_docks))
|
||||
break;
|
||||
|
||||
g_object_set (pika->config,
|
||||
"hide-docks", hide_docks,
|
||||
NULL);
|
||||
}
|
||||
else if (scanner->value.v_symbol == GINT_TO_POINTER (SINGLE_WINDOW_MODE))
|
||||
{
|
||||
gboolean single_window_mode;
|
||||
|
||||
token = G_TOKEN_IDENTIFIER;
|
||||
|
||||
if (! pika_scanner_parse_boolean (scanner, &single_window_mode))
|
||||
break;
|
||||
|
||||
g_object_set (pika->config,
|
||||
"single-window-mode", single_window_mode,
|
||||
NULL);
|
||||
}
|
||||
else if (scanner->value.v_symbol == GINT_TO_POINTER (SHOW_TABS))
|
||||
{
|
||||
gboolean show_tabs;
|
||||
|
||||
token = G_TOKEN_IDENTIFIER;
|
||||
|
||||
if (! pika_scanner_parse_boolean (scanner, &show_tabs))
|
||||
break;
|
||||
|
||||
g_object_set (pika->config,
|
||||
"show-tabs", show_tabs,
|
||||
NULL);
|
||||
}
|
||||
else if (scanner->value.v_symbol == GINT_TO_POINTER (TABS_POSITION))
|
||||
{
|
||||
gint tabs_position;
|
||||
|
||||
token = G_TOKEN_INT;
|
||||
|
||||
if (! pika_scanner_parse_int (scanner, &tabs_position))
|
||||
break;
|
||||
|
||||
g_object_set (pika->config,
|
||||
"tabs-position", tabs_position,
|
||||
NULL);
|
||||
}
|
||||
else if (scanner->value.v_symbol == GINT_TO_POINTER (LAST_TIP_SHOWN))
|
||||
{
|
||||
gint last_tip_shown;
|
||||
|
||||
token = G_TOKEN_INT;
|
||||
|
||||
if (! pika_scanner_parse_int (scanner, &last_tip_shown))
|
||||
break;
|
||||
|
||||
g_object_set (pika->config,
|
||||
"last-tip-shown", last_tip_shown,
|
||||
NULL);
|
||||
}
|
||||
token = G_TOKEN_RIGHT_PAREN;
|
||||
break;
|
||||
|
||||
case G_TOKEN_RIGHT_PAREN:
|
||||
token = G_TOKEN_LEFT_PAREN;
|
||||
break;
|
||||
|
||||
default: /* do nothing */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
error:
|
||||
|
||||
if (token != G_TOKEN_LEFT_PAREN)
|
||||
{
|
||||
g_scanner_get_next_token (scanner);
|
||||
g_scanner_unexp_token (scanner, token, NULL, NULL, NULL,
|
||||
_("fatal parse error"), TRUE);
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
||||
g_clear_error (&error);
|
||||
|
||||
pika_config_file_backup_on_error (file, "sessionrc", NULL);
|
||||
}
|
||||
|
||||
pika_scanner_unref (scanner);
|
||||
g_object_unref (file);
|
||||
|
||||
dialogs_load_recent_docks (pika);
|
||||
}
|
||||
|
||||
void
|
||||
session_exit (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
}
|
||||
|
||||
void
|
||||
session_restore (Pika *pika,
|
||||
GdkMonitor *monitor)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
g_return_if_fail (GDK_IS_MONITOR (monitor));
|
||||
|
||||
pika_dialog_factory_restore (pika_dialog_factory_get_singleton (),
|
||||
monitor);
|
||||
|
||||
/* make sure PikaImageWindow acts upon hide-docks at the right time,
|
||||
* see bug #678043.
|
||||
*/
|
||||
if (PIKA_GUI_CONFIG (pika->config)->single_window_mode &&
|
||||
PIKA_GUI_CONFIG (pika->config)->hide_docks)
|
||||
{
|
||||
g_object_notify (G_OBJECT (pika->config), "hide-docks");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
session_save (Pika *pika,
|
||||
gboolean always_save)
|
||||
{
|
||||
PikaConfigWriter *writer;
|
||||
GFile *file;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (sessionrc_deleted && ! always_save)
|
||||
return;
|
||||
|
||||
file = session_file (pika);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Writing '%s'\n", pika_file_get_utf8_name (file));
|
||||
|
||||
writer =
|
||||
pika_config_writer_new_from_file (file,
|
||||
TRUE,
|
||||
"PIKA sessionrc\n\n"
|
||||
"This file takes session-specific info "
|
||||
"(that is info, you want to keep between "
|
||||
"two PIKA sessions). You are not supposed "
|
||||
"to edit it manually, but of course you "
|
||||
"can do. The sessionrc will be entirely "
|
||||
"rewritten every time you quit PIKA. "
|
||||
"If this file isn't found, defaults are "
|
||||
"used.",
|
||||
NULL);
|
||||
g_object_unref (file);
|
||||
|
||||
if (!writer)
|
||||
return;
|
||||
|
||||
pika_dialog_factory_save (pika_dialog_factory_get_singleton (), writer);
|
||||
pika_config_writer_linefeed (writer);
|
||||
|
||||
pika_config_writer_open (writer, "hide-docks");
|
||||
pika_config_writer_identifier (writer,
|
||||
PIKA_GUI_CONFIG (pika->config)->hide_docks ?
|
||||
"yes" : "no");
|
||||
pika_config_writer_close (writer);
|
||||
|
||||
pika_config_writer_open (writer, "single-window-mode");
|
||||
pika_config_writer_identifier (writer,
|
||||
PIKA_GUI_CONFIG (pika->config)->single_window_mode ?
|
||||
"yes" : "no");
|
||||
pika_config_writer_close (writer);
|
||||
|
||||
pika_config_writer_open (writer, "show-tabs");
|
||||
pika_config_writer_printf (writer,
|
||||
PIKA_GUI_CONFIG (pika->config)->show_tabs ?
|
||||
"yes" : "no");
|
||||
pika_config_writer_close (writer);
|
||||
|
||||
pika_config_writer_open (writer, "tabs-position");
|
||||
pika_config_writer_printf (writer, "%d",
|
||||
PIKA_GUI_CONFIG (pika->config)->tabs_position);
|
||||
pika_config_writer_close (writer);
|
||||
|
||||
pika_config_writer_open (writer, "last-tip-shown");
|
||||
pika_config_writer_printf (writer, "%d",
|
||||
PIKA_GUI_CONFIG (pika->config)->last_tip_shown);
|
||||
pika_config_writer_close (writer);
|
||||
|
||||
if (! pika_config_writer_finish (writer, "end of sessionrc", &error))
|
||||
{
|
||||
pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
dialogs_save_recent_docks (pika);
|
||||
|
||||
sessionrc_deleted = FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
session_clear (Pika *pika,
|
||||
GError **error)
|
||||
{
|
||||
GFile *file;
|
||||
GError *my_error = NULL;
|
||||
gboolean success = TRUE;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), FALSE);
|
||||
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
|
||||
|
||||
file = session_file (pika);
|
||||
|
||||
if (! g_file_delete (file, NULL, &my_error) &&
|
||||
my_error->code != G_IO_ERROR_NOT_FOUND)
|
||||
{
|
||||
success = FALSE;
|
||||
|
||||
g_set_error (error, PIKA_ERROR, PIKA_FAILED,
|
||||
_("Deleting \"%s\" failed: %s"),
|
||||
pika_file_get_utf8_name (file), my_error->message);
|
||||
}
|
||||
else
|
||||
{
|
||||
sessionrc_deleted = TRUE;
|
||||
}
|
||||
|
||||
g_clear_error (&my_error);
|
||||
g_object_unref (file);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
static GFile *
|
||||
session_file (Pika *pika)
|
||||
{
|
||||
const gchar *basename;
|
||||
gchar *filename;
|
||||
GFile *file;
|
||||
|
||||
basename = g_getenv ("PIKA_TESTING_SESSIONRC_NAME");
|
||||
if (! basename)
|
||||
basename = "sessionrc";
|
||||
|
||||
if (pika->session_name)
|
||||
filename = g_strconcat (basename, ".", pika->session_name, NULL);
|
||||
else
|
||||
filename = g_strdup (basename);
|
||||
|
||||
file = pika_directory_file (filename, NULL);
|
||||
|
||||
g_free (filename);
|
||||
|
||||
return file;
|
||||
}
|
38
app/gui/session.h
Normal file
38
app/gui/session.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SESSION_H__
|
||||
#define __SESSION_H__
|
||||
|
||||
|
||||
void session_init (Pika *pika);
|
||||
void session_exit (Pika *pika);
|
||||
|
||||
void session_restore (Pika *pika,
|
||||
GdkMonitor *monitor);
|
||||
void session_save (Pika *pika,
|
||||
gboolean always_save);
|
||||
|
||||
gboolean session_clear (Pika *pika,
|
||||
GError **error);
|
||||
|
||||
|
||||
#endif /* __SESSION_H__ */
|
742
app/gui/splash.c
Normal file
742
app/gui/splash.c
Normal file
@ -0,0 +1,742 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
#ifdef GDK_WINDOWING_WAYLAND
|
||||
#include <gdk/gdkwayland.h>
|
||||
#endif
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikamath/pikamath.h"
|
||||
#include "libpikacolor/pikacolor.h"
|
||||
#include "libpikawidgets/pikawidgets.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
|
||||
#include "widgets/pikawidgets-utils.h"
|
||||
|
||||
#include "splash.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
#define MEASURE_UPPER "1235678901234567890"
|
||||
#define MEASURE_LOWER "12356789012345678901234567890"
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *window;
|
||||
GtkWidget *area;
|
||||
gint width;
|
||||
gint height;
|
||||
GtkWidget *progress;
|
||||
GdkRGBA color;
|
||||
PangoLayout *upper;
|
||||
gint upper_x;
|
||||
gint upper_y;
|
||||
PangoLayout *lower;
|
||||
gint lower_x;
|
||||
gint lower_y;
|
||||
|
||||
gdouble percentage;
|
||||
gchar *text1;
|
||||
gchar *text2;
|
||||
|
||||
/* debug timer */
|
||||
GTimer *timer;
|
||||
gdouble last_time;
|
||||
} PikaSplash;
|
||||
|
||||
static PikaSplash *splash = NULL;
|
||||
|
||||
|
||||
static void splash_position_layouts (PikaSplash *splash,
|
||||
const gchar *text1,
|
||||
const gchar *text2,
|
||||
GdkRectangle *area);
|
||||
static gboolean splash_area_draw (GtkWidget *widget,
|
||||
cairo_t *cr,
|
||||
PikaSplash *splash);
|
||||
static void splash_rectangle_union (GdkRectangle *dest,
|
||||
PangoRectangle *pango_rect,
|
||||
gint offset_x,
|
||||
gint offset_y);
|
||||
static void splash_average_text_area (PikaSplash *splash,
|
||||
GdkPixbuf *pixbuf,
|
||||
GdkRGBA *rgba);
|
||||
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_load (Pika *pika,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose);
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_load_from_file (GFile *file,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose);
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_pick_from_dirs (GList *dirs,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose);
|
||||
|
||||
static void splash_timer_elapsed (void);
|
||||
|
||||
|
||||
/* public functions */
|
||||
|
||||
void
|
||||
splash_create (Pika *pika,
|
||||
gboolean be_verbose,
|
||||
GdkMonitor *monitor,
|
||||
PikaApp *app)
|
||||
{
|
||||
GtkWidget *frame;
|
||||
GtkWidget *vbox;
|
||||
GdkPixbufAnimation *pixbuf;
|
||||
PangoRectangle ink;
|
||||
GdkRectangle workarea;
|
||||
gint max_width;
|
||||
gint max_height;
|
||||
|
||||
g_return_if_fail (splash == NULL);
|
||||
g_return_if_fail (GDK_IS_MONITOR (monitor));
|
||||
g_return_if_fail (PIKA_IS_APP (app) || app == NULL);
|
||||
|
||||
gdk_monitor_get_workarea (monitor, &workarea);
|
||||
|
||||
#ifdef GDK_WINDOWING_WAYLAND
|
||||
if (GDK_IS_WAYLAND_DISPLAY (gdk_display_get_default ()))
|
||||
{
|
||||
/* This is completely extra ugly. Basically we cannot rely on
|
||||
* gdk_monitor_get_workarea() on Wayland because the application
|
||||
* cannot get trustworthy values. Indeed it is supposed to return
|
||||
* a value in "application pixels" not "device pixels", in other
|
||||
* words already inverse-scaled value. It turns out that on
|
||||
* Wayland, under some conditions (but maybe not even all the
|
||||
* time? I'm still unclear if there are conditions where returned
|
||||
* value is properly scaled), it returns the device dimensions.
|
||||
* E.g. on high-density display x2, making a splash for half the
|
||||
* value, we ended up actually with the size of the whole display.
|
||||
* This is why I special-case Wayland with a max of a third of the
|
||||
* work area so that it would end up 2/3 maximum (at least not
|
||||
* filling the screen!).
|
||||
* This is ugly but for now I don't see the right solution. FIXME!
|
||||
* See #5322.
|
||||
*/
|
||||
max_width = workarea.width / 3;
|
||||
max_height = workarea.height / 3;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
max_width = workarea.width / 2;
|
||||
max_height = workarea.height / 2;
|
||||
}
|
||||
pixbuf = splash_image_load (pika, max_width, max_height, be_verbose);
|
||||
|
||||
if (! pixbuf)
|
||||
return;
|
||||
|
||||
splash = g_slice_new0 (PikaSplash);
|
||||
|
||||
splash->window =
|
||||
g_object_new (GTK_TYPE_APPLICATION_WINDOW,
|
||||
"type", GTK_WINDOW_TOPLEVEL,
|
||||
"type-hint", GDK_WINDOW_TYPE_HINT_SPLASHSCREEN,
|
||||
"title", _("PIKA Startup"),
|
||||
"role", "pika-startup",
|
||||
"window-position", GTK_WIN_POS_CENTER,
|
||||
"resizable", FALSE,
|
||||
"application", GTK_APPLICATION (app),
|
||||
NULL);
|
||||
|
||||
/* Don't remove this call, it's necessary to remove decorations on Windows
|
||||
* (which is the natural state of splash-screens). Looks like the
|
||||
* GDK_WINDOW_TYPE_HINT_SPLASHSCREEN hint is not used on some platforms.
|
||||
*/
|
||||
gtk_window_set_decorated (GTK_WINDOW (splash->window), FALSE);
|
||||
|
||||
g_signal_connect_swapped (splash->window, "delete-event",
|
||||
G_CALLBACK (exit),
|
||||
GINT_TO_POINTER (0));
|
||||
|
||||
splash->width = MIN (gdk_pixbuf_animation_get_width (pixbuf),
|
||||
workarea.width);
|
||||
splash->height = MIN (gdk_pixbuf_animation_get_height (pixbuf),
|
||||
workarea.height);
|
||||
|
||||
frame = gtk_frame_new (NULL);
|
||||
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_OUT);
|
||||
gtk_container_add (GTK_CONTAINER (splash->window), frame);
|
||||
gtk_widget_show (frame);
|
||||
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||
gtk_container_add (GTK_CONTAINER (frame), vbox);
|
||||
gtk_widget_show (vbox);
|
||||
|
||||
splash->area = gtk_image_new_from_animation (pixbuf);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), splash->area, TRUE, TRUE, 0);
|
||||
gtk_widget_show (splash->area);
|
||||
|
||||
gtk_widget_set_size_request (splash->area, splash->width, splash->height);
|
||||
|
||||
/* create the pango layouts */
|
||||
splash->upper = gtk_widget_create_pango_layout (splash->area,
|
||||
MEASURE_UPPER);
|
||||
pango_layout_get_pixel_extents (splash->upper, &ink, NULL);
|
||||
|
||||
if (splash->width > 4 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->upper, PANGO_SCALE_X_LARGE);
|
||||
else if (splash->width > 3 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->upper, PANGO_SCALE_LARGE);
|
||||
else if (splash->width > 2 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->upper, PANGO_SCALE_MEDIUM);
|
||||
else
|
||||
pika_pango_layout_set_scale (splash->upper, PANGO_SCALE_SMALL);
|
||||
|
||||
splash->lower = gtk_widget_create_pango_layout (splash->area,
|
||||
MEASURE_LOWER);
|
||||
pango_layout_get_pixel_extents (splash->lower, &ink, NULL);
|
||||
|
||||
if (splash->width > 4 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->lower, PANGO_SCALE_LARGE);
|
||||
else if (splash->width > 3 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->lower, PANGO_SCALE_MEDIUM);
|
||||
else if (splash->width > 2 * ink.width)
|
||||
pika_pango_layout_set_scale (splash->lower, PANGO_SCALE_SMALL);
|
||||
else
|
||||
pika_pango_layout_set_scale (splash->lower, PANGO_SCALE_X_SMALL);
|
||||
|
||||
/* this sets the initial layout positions */
|
||||
splash_position_layouts (splash, "", "", NULL);
|
||||
|
||||
splash_average_text_area (splash,
|
||||
gdk_pixbuf_animation_get_static_image (pixbuf),
|
||||
&splash->color);
|
||||
|
||||
g_object_unref (pixbuf);
|
||||
|
||||
g_signal_connect_after (splash->area, "draw",
|
||||
G_CALLBACK (splash_area_draw),
|
||||
splash);
|
||||
|
||||
/* add a progress bar */
|
||||
splash->progress = gtk_progress_bar_new ();
|
||||
gtk_box_pack_end (GTK_BOX (vbox), splash->progress, FALSE, FALSE, 0);
|
||||
gtk_widget_show (splash->progress);
|
||||
|
||||
gtk_widget_show (splash->window);
|
||||
|
||||
if (FALSE)
|
||||
splash->timer = g_timer_new ();
|
||||
}
|
||||
|
||||
void
|
||||
splash_destroy (void)
|
||||
{
|
||||
if (! splash)
|
||||
return;
|
||||
|
||||
gtk_widget_destroy (splash->window);
|
||||
|
||||
g_object_unref (splash->upper);
|
||||
g_object_unref (splash->lower);
|
||||
|
||||
g_free (splash->text1);
|
||||
g_free (splash->text2);
|
||||
|
||||
if (splash->timer)
|
||||
g_timer_destroy (splash->timer);
|
||||
|
||||
g_slice_free (PikaSplash, splash);
|
||||
splash = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
splash_update (const gchar *text1,
|
||||
const gchar *text2,
|
||||
gdouble percentage)
|
||||
{
|
||||
static GdkRectangle prev_expose = { 0, 0, 0, 0 };
|
||||
GdkRectangle expose = { 0, 0, 0, 0 };
|
||||
|
||||
g_return_if_fail (percentage >= 0.0 && percentage <= 1.0);
|
||||
|
||||
if (! splash)
|
||||
return;
|
||||
|
||||
splash_position_layouts (splash, text1, text2, &expose);
|
||||
gdk_rectangle_union (&expose, &prev_expose, &expose);
|
||||
|
||||
if (expose.width > 0 && expose.height > 0)
|
||||
gtk_widget_queue_draw_area (splash->area,
|
||||
expose.x, expose.y,
|
||||
expose.width, expose.height);
|
||||
|
||||
prev_expose = expose;
|
||||
|
||||
if ((text1 == NULL || ! g_strcmp0 (text1, splash->text1)) &&
|
||||
(text2 == NULL || ! g_strcmp0 (text2, splash->text2)) &&
|
||||
percentage == splash->percentage)
|
||||
{
|
||||
if (text1)
|
||||
{
|
||||
g_free (splash->text1);
|
||||
splash->text1 = g_strdup (text1);
|
||||
}
|
||||
|
||||
if (text2)
|
||||
{
|
||||
g_free (splash->text2);
|
||||
splash->text2 = g_strdup (text2);
|
||||
}
|
||||
|
||||
gtk_progress_bar_pulse (GTK_PROGRESS_BAR (splash->progress));
|
||||
}
|
||||
else
|
||||
{
|
||||
gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (splash->progress),
|
||||
percentage);
|
||||
}
|
||||
|
||||
splash->percentage = percentage;
|
||||
|
||||
if (splash->timer)
|
||||
splash_timer_elapsed ();
|
||||
|
||||
if (gtk_events_pending ())
|
||||
gtk_main_iteration ();
|
||||
}
|
||||
|
||||
|
||||
/* private functions */
|
||||
|
||||
static gboolean
|
||||
splash_area_draw (GtkWidget *widget,
|
||||
cairo_t *cr,
|
||||
PikaSplash *splash)
|
||||
{
|
||||
gdk_cairo_set_source_rgba (cr, &splash->color);
|
||||
|
||||
cairo_move_to (cr, splash->upper_x, splash->upper_y);
|
||||
pango_cairo_show_layout (cr, splash->upper);
|
||||
|
||||
cairo_move_to (cr, splash->lower_x, splash->lower_y);
|
||||
pango_cairo_show_layout (cr, splash->lower);
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* area returns the union of the previous and new ink rectangles */
|
||||
static void
|
||||
splash_position_layouts (PikaSplash *splash,
|
||||
const gchar *text1,
|
||||
const gchar *text2,
|
||||
GdkRectangle *area)
|
||||
{
|
||||
PangoRectangle upper_ink;
|
||||
PangoRectangle lower_ink;
|
||||
gint text_height = 0;
|
||||
|
||||
if (text1)
|
||||
{
|
||||
pango_layout_get_pixel_extents (splash->upper, &upper_ink, NULL);
|
||||
|
||||
if (area)
|
||||
splash_rectangle_union (area, &upper_ink,
|
||||
splash->upper_x, splash->upper_y);
|
||||
|
||||
pango_layout_set_text (splash->upper, text1, -1);
|
||||
pango_layout_get_pixel_extents (splash->upper,
|
||||
&upper_ink, NULL);
|
||||
|
||||
splash->upper_x = (splash->width - upper_ink.width) / 2;
|
||||
text_height += upper_ink.height;
|
||||
}
|
||||
|
||||
if (text2)
|
||||
{
|
||||
pango_layout_get_pixel_extents (splash->lower, &lower_ink, NULL);
|
||||
|
||||
if (area)
|
||||
splash_rectangle_union (area, &lower_ink,
|
||||
splash->lower_x, splash->lower_y);
|
||||
|
||||
pango_layout_set_text (splash->lower, text2, -1);
|
||||
pango_layout_get_pixel_extents (splash->lower,
|
||||
&lower_ink, NULL);
|
||||
|
||||
splash->lower_x = (splash->width - lower_ink.width) / 2;
|
||||
text_height += lower_ink.height;
|
||||
}
|
||||
|
||||
/* For pretty printing, let's say we want at least double space. */
|
||||
text_height *= 2;
|
||||
|
||||
/* The ordinates are computed in 2 steps, because we are first
|
||||
* checking the minimal height needed for text (text_height).
|
||||
*
|
||||
* Ideally we are printing in the bottom quarter of the splash image,
|
||||
* with well centered positions. But if this zone appears to be too
|
||||
* small, we will end up using this previously computed text_height
|
||||
* instead. Since splash images are designed to have text in the lower
|
||||
* quarter, this may end up a bit uglier, but at least top and bottom
|
||||
* texts won't overlay each other.
|
||||
*/
|
||||
if (text1)
|
||||
{
|
||||
splash->upper_y = MIN (splash->height - text_height,
|
||||
splash->height * 13 / 16 -
|
||||
upper_ink.height / 2);
|
||||
|
||||
if (area)
|
||||
splash_rectangle_union (area, &upper_ink,
|
||||
splash->upper_x, splash->upper_y);
|
||||
}
|
||||
|
||||
if (text2)
|
||||
{
|
||||
splash->lower_y = ((splash->height + splash->upper_y) / 2 -
|
||||
lower_ink.height / 2);
|
||||
|
||||
if (area)
|
||||
splash_rectangle_union (area, &lower_ink,
|
||||
splash->lower_x, splash->lower_y);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
splash_rectangle_union (GdkRectangle *dest,
|
||||
PangoRectangle *pango_rect,
|
||||
gint offset_x,
|
||||
gint offset_y)
|
||||
{
|
||||
GdkRectangle rect;
|
||||
|
||||
rect.x = pango_rect->x + offset_x;
|
||||
rect.y = pango_rect->y + offset_y;
|
||||
rect.width = pango_rect->width;
|
||||
rect.height = pango_rect->height;
|
||||
|
||||
if (dest->width > 0 && dest->height > 0)
|
||||
gdk_rectangle_union (dest, &rect, dest);
|
||||
else
|
||||
*dest = rect;
|
||||
}
|
||||
|
||||
/* This function chooses a gray value for the text color, based on
|
||||
* the average luminance of the text area of the splash image.
|
||||
*/
|
||||
static void
|
||||
splash_average_text_area (PikaSplash *splash,
|
||||
GdkPixbuf *pixbuf,
|
||||
GdkRGBA *color)
|
||||
{
|
||||
const guchar *pixels;
|
||||
gint rowstride;
|
||||
gint channels;
|
||||
gint luminance = 0;
|
||||
guint sum[3] = { 0, 0, 0 };
|
||||
GdkRectangle image = { 0, 0, 0, 0 };
|
||||
GdkRectangle area = { 0, 0, 0, 0 };
|
||||
|
||||
g_return_if_fail (GDK_IS_PIXBUF (pixbuf));
|
||||
g_return_if_fail (gdk_pixbuf_get_bits_per_sample (pixbuf) == 8);
|
||||
|
||||
image.width = gdk_pixbuf_get_width (pixbuf);
|
||||
image.height = gdk_pixbuf_get_height (pixbuf);
|
||||
rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
||||
channels = gdk_pixbuf_get_n_channels (pixbuf);
|
||||
pixels = gdk_pixbuf_get_pixels (pixbuf);
|
||||
|
||||
splash_position_layouts (splash, MEASURE_UPPER, MEASURE_LOWER, &area);
|
||||
splash_position_layouts (splash, "", "", NULL);
|
||||
|
||||
if (gdk_rectangle_intersect (&image, &area, &area))
|
||||
{
|
||||
const gint count = area.width * area.height;
|
||||
gint x, y;
|
||||
|
||||
pixels += area.x * channels;
|
||||
pixels += area.y * rowstride;
|
||||
|
||||
for (y = 0; y < area.height; y++)
|
||||
{
|
||||
const guchar *src = pixels;
|
||||
|
||||
for (x = 0; x < area.width; x++)
|
||||
{
|
||||
sum[0] += src[0];
|
||||
sum[1] += src[1];
|
||||
sum[2] += src[2];
|
||||
|
||||
src += channels;
|
||||
}
|
||||
|
||||
pixels += rowstride;
|
||||
}
|
||||
|
||||
luminance = PIKA_RGB_LUMINANCE (sum[0] / count,
|
||||
sum[1] / count,
|
||||
sum[2] / count);
|
||||
|
||||
luminance = CLAMP0255 (luminance > 127 ?
|
||||
luminance - 223 : luminance + 223);
|
||||
|
||||
}
|
||||
|
||||
color->red = color->green = color->blue = luminance / 255.0;
|
||||
color->alpha = 1.0;
|
||||
}
|
||||
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_load (Pika *pika,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose)
|
||||
{
|
||||
GdkPixbufAnimation *animation = NULL;
|
||||
GFile *file;
|
||||
GList *list;
|
||||
|
||||
/* Random image in splash extensions. */
|
||||
g_object_get (pika->extension_manager,
|
||||
"splash-paths", &list,
|
||||
NULL);
|
||||
animation = splash_image_pick_from_dirs (list,
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
if (animation)
|
||||
return animation;
|
||||
|
||||
/* File "pika-splash.png" in personal configuration directory. */
|
||||
file = pika_directory_file ("pika-splash.png", NULL);
|
||||
animation = splash_image_load_from_file (file,
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
g_object_unref (file);
|
||||
if (animation)
|
||||
return animation;
|
||||
|
||||
/* Random image under splashes/ directory in personal config dir. */
|
||||
file = pika_directory_file ("splashes", NULL);
|
||||
list = NULL;
|
||||
list = g_list_prepend (list, file);
|
||||
animation = splash_image_pick_from_dirs (list,
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
g_list_free_full (list, g_object_unref);
|
||||
if (animation)
|
||||
return animation;
|
||||
|
||||
/* Release splash image. */
|
||||
file = pika_data_directory_file ("images", "pika-splash.png", NULL);
|
||||
animation = splash_image_load_from_file (file,
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
g_object_unref (file);
|
||||
if (animation)
|
||||
return animation;
|
||||
|
||||
/* Random release image in installed splashes/ directory. */
|
||||
file = pika_data_directory_file ("splashes", NULL);
|
||||
list = NULL;
|
||||
list = g_list_prepend (list, file);
|
||||
animation = splash_image_pick_from_dirs (list,
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
g_list_free_full (list, g_object_unref);
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_load_from_file (GFile *file,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose)
|
||||
{
|
||||
GdkPixbufAnimation *animation = NULL;
|
||||
GFileInfo *info;
|
||||
GFileInputStream *input;
|
||||
gboolean is_svg = FALSE;
|
||||
|
||||
if (be_verbose)
|
||||
g_printerr ("Trying splash '%s' ... ", g_file_peek_path (file));
|
||||
|
||||
info = g_file_query_info (file,
|
||||
G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
|
||||
G_FILE_QUERY_INFO_NONE, NULL, NULL);
|
||||
if (info)
|
||||
{
|
||||
const gchar *content_type;
|
||||
|
||||
content_type = g_file_info_get_content_type (info);
|
||||
if (content_type)
|
||||
{
|
||||
gchar *mime_type;
|
||||
|
||||
mime_type = g_content_type_get_mime_type (content_type);
|
||||
if (mime_type)
|
||||
{
|
||||
if (g_strcmp0 (mime_type, "image/svg+xml") == 0)
|
||||
{
|
||||
/* We want to treat vector images differently than
|
||||
* pixel images. We only scale down bitmaps, but we
|
||||
* don't try to scale them up.
|
||||
* On the other hand, we can always scale down and up
|
||||
* vector images so that they end up in an ideal size
|
||||
* in all cases.
|
||||
*/
|
||||
is_svg = TRUE;
|
||||
}
|
||||
g_free (mime_type);
|
||||
}
|
||||
}
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
input = g_file_read (file, NULL, NULL);
|
||||
if (input)
|
||||
{
|
||||
animation = gdk_pixbuf_animation_new_from_stream (G_INPUT_STREAM (input),
|
||||
NULL, NULL);
|
||||
g_object_unref (input);
|
||||
}
|
||||
|
||||
/* FIXME Right now, we only try to scale static images.
|
||||
* Animated images may end up bigger than the expected max dimensions.
|
||||
*/
|
||||
if (animation && gdk_pixbuf_animation_is_static_image (animation) &&
|
||||
(gdk_pixbuf_animation_get_width (animation) > max_width ||
|
||||
gdk_pixbuf_animation_get_height (animation) > max_height ||
|
||||
is_svg))
|
||||
{
|
||||
GdkPixbuf *pixbuf;
|
||||
|
||||
input = g_file_read (file, NULL, NULL);
|
||||
pixbuf = gdk_pixbuf_new_from_stream_at_scale (G_INPUT_STREAM (input),
|
||||
max_width, max_height,
|
||||
TRUE, NULL, NULL);
|
||||
g_object_unref (input);
|
||||
if (pixbuf)
|
||||
{
|
||||
GdkPixbufSimpleAnim *simple_anim = NULL;
|
||||
|
||||
simple_anim = gdk_pixbuf_simple_anim_new (gdk_pixbuf_get_width (pixbuf),
|
||||
gdk_pixbuf_get_height (pixbuf),
|
||||
1.0);
|
||||
if (simple_anim)
|
||||
{
|
||||
gdk_pixbuf_simple_anim_add_frame (simple_anim, pixbuf);
|
||||
|
||||
g_object_unref (animation);
|
||||
animation = GDK_PIXBUF_ANIMATION (simple_anim);
|
||||
}
|
||||
g_object_unref (pixbuf);
|
||||
}
|
||||
}
|
||||
|
||||
if (be_verbose)
|
||||
g_printerr (animation ? "OK\n" : "failed\n");
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
static GdkPixbufAnimation *
|
||||
splash_image_pick_from_dirs (GList *dirs,
|
||||
gint max_width,
|
||||
gint max_height,
|
||||
gboolean be_verbose)
|
||||
{
|
||||
GdkPixbufAnimation *animation = NULL;
|
||||
GList *splashes = NULL;
|
||||
GList *iter;
|
||||
|
||||
for (iter = dirs; iter; iter = iter->next)
|
||||
{
|
||||
GFileEnumerator *enumerator;
|
||||
|
||||
enumerator = g_file_enumerate_children (iter->data,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
||||
G_FILE_ATTRIBUTE_TIME_MODIFIED,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
NULL, NULL);
|
||||
if (enumerator)
|
||||
{
|
||||
GFileInfo *info;
|
||||
|
||||
while ((info = g_file_enumerator_next_file (enumerator, NULL, NULL)))
|
||||
{
|
||||
GFile *child;
|
||||
|
||||
child = g_file_enumerator_get_child (enumerator, info);
|
||||
if (g_file_query_file_type (child,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
NULL) == G_FILE_TYPE_REGULAR)
|
||||
splashes = g_list_prepend (splashes, child);
|
||||
else
|
||||
g_object_unref (child);
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
g_object_unref (enumerator);
|
||||
}
|
||||
}
|
||||
|
||||
if (splashes)
|
||||
{
|
||||
gint32 i = g_random_int_range (0, g_list_length (splashes));
|
||||
|
||||
animation = splash_image_load_from_file (g_list_nth_data (splashes, i),
|
||||
max_width, max_height,
|
||||
be_verbose);
|
||||
g_list_free_full (splashes, (GDestroyNotify) g_object_unref);
|
||||
}
|
||||
|
||||
return animation;
|
||||
}
|
||||
|
||||
static void
|
||||
splash_timer_elapsed (void)
|
||||
{
|
||||
gdouble elapsed = g_timer_elapsed (splash->timer, NULL);
|
||||
|
||||
g_printerr ("%8g %8g - %s %g%% - %s\n",
|
||||
elapsed,
|
||||
elapsed - splash->last_time,
|
||||
splash->text1 ? splash->text1 : "",
|
||||
splash->percentage * 100.0,
|
||||
splash->text2 ? splash->text2 : "");
|
||||
|
||||
splash->last_time = elapsed;
|
||||
}
|
36
app/gui/splash.h
Normal file
36
app/gui/splash.h
Normal file
@ -0,0 +1,36 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __SPLASH_H__
|
||||
#define __SPLASH_H__
|
||||
|
||||
void splash_create (Pika *pika,
|
||||
gboolean be_verbose,
|
||||
GdkMonitor *monitor,
|
||||
PikaApp *app);
|
||||
void splash_destroy (void);
|
||||
|
||||
void splash_update (const gchar *label1,
|
||||
const gchar *label2,
|
||||
gdouble percentage);
|
||||
|
||||
|
||||
#endif /* __SPLASH_H__ */
|
546
app/gui/themes.c
Normal file
546
app/gui/themes.c
Normal file
@ -0,0 +1,546 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <gegl.h>
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
#include "libpikabase/pikabase.h"
|
||||
#include "libpikaconfig/pikaconfig.h"
|
||||
|
||||
#include "gui-types.h"
|
||||
|
||||
#include "config/pikaguiconfig.h"
|
||||
|
||||
#include "core/pika.h"
|
||||
|
||||
#include "themes.h"
|
||||
|
||||
#include "pika-intl.h"
|
||||
|
||||
|
||||
/* local function prototypes */
|
||||
|
||||
static void themes_apply_theme (Pika *pika,
|
||||
PikaGuiConfig *config);
|
||||
static void themes_list_themes_foreach (gpointer key,
|
||||
gpointer value,
|
||||
gpointer data);
|
||||
static gint themes_name_compare (const void *p1,
|
||||
const void *p2);
|
||||
static void themes_theme_change_notify (PikaGuiConfig *config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika);
|
||||
static void themes_theme_paths_notify (PikaExtensionManager *manager,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika);
|
||||
|
||||
|
||||
/* private variables */
|
||||
|
||||
static GHashTable *themes_hash = NULL;
|
||||
static GtkStyleProvider *themes_style_provider = NULL;
|
||||
|
||||
|
||||
/* public functions */
|
||||
|
||||
void
|
||||
themes_init (Pika *pika)
|
||||
{
|
||||
PikaGuiConfig *config;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
config = PIKA_GUI_CONFIG (pika->config);
|
||||
|
||||
/* Check for theme extensions. */
|
||||
themes_theme_paths_notify (pika->extension_manager, NULL, pika);
|
||||
g_signal_connect (pika->extension_manager, "notify::theme-paths",
|
||||
G_CALLBACK (themes_theme_paths_notify),
|
||||
pika);
|
||||
|
||||
themes_style_provider = GTK_STYLE_PROVIDER (gtk_css_provider_new ());
|
||||
|
||||
/* Use GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1 so theme files
|
||||
* override default styles provided by widgets themselves.
|
||||
*/
|
||||
gtk_style_context_add_provider_for_screen (gdk_screen_get_default (),
|
||||
themes_style_provider,
|
||||
GTK_STYLE_PROVIDER_PRIORITY_APPLICATION + 1);
|
||||
|
||||
g_signal_connect (config, "notify::theme",
|
||||
G_CALLBACK (themes_theme_change_notify),
|
||||
pika);
|
||||
g_signal_connect (config, "notify::prefer-dark-theme",
|
||||
G_CALLBACK (themes_theme_change_notify),
|
||||
pika);
|
||||
g_signal_connect (config, "notify::prefer-symbolic-icons",
|
||||
G_CALLBACK (themes_theme_change_notify),
|
||||
pika);
|
||||
g_signal_connect (config, "notify::override-theme-icon-size",
|
||||
G_CALLBACK (themes_theme_change_notify),
|
||||
pika);
|
||||
g_signal_connect (config, "notify::custom-icon-size",
|
||||
G_CALLBACK (themes_theme_change_notify),
|
||||
pika);
|
||||
|
||||
themes_theme_change_notify (config, NULL, pika);
|
||||
}
|
||||
|
||||
void
|
||||
themes_exit (Pika *pika)
|
||||
{
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (themes_hash)
|
||||
{
|
||||
g_signal_handlers_disconnect_by_func (pika->config,
|
||||
themes_theme_change_notify,
|
||||
pika);
|
||||
|
||||
g_hash_table_destroy (themes_hash);
|
||||
themes_hash = NULL;
|
||||
}
|
||||
|
||||
g_clear_object (&themes_style_provider);
|
||||
}
|
||||
|
||||
gchar **
|
||||
themes_list_themes (Pika *pika,
|
||||
gint *n_themes)
|
||||
{
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
g_return_val_if_fail (n_themes != NULL, NULL);
|
||||
|
||||
*n_themes = g_hash_table_size (themes_hash);
|
||||
|
||||
if (*n_themes > 0)
|
||||
{
|
||||
gchar **themes;
|
||||
gchar **index;
|
||||
|
||||
themes = g_new0 (gchar *, *n_themes + 1);
|
||||
|
||||
index = themes;
|
||||
|
||||
g_hash_table_foreach (themes_hash, themes_list_themes_foreach, &index);
|
||||
|
||||
qsort (themes, *n_themes, sizeof (gchar *), themes_name_compare);
|
||||
|
||||
return themes;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
GFile *
|
||||
themes_get_theme_dir (Pika *pika,
|
||||
const gchar *theme_name)
|
||||
{
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
|
||||
if (! theme_name)
|
||||
theme_name = PIKA_CONFIG_DEFAULT_THEME;
|
||||
|
||||
return g_hash_table_lookup (themes_hash, theme_name);
|
||||
}
|
||||
|
||||
GFile *
|
||||
themes_get_theme_file (Pika *pika,
|
||||
const gchar *first_component,
|
||||
...)
|
||||
{
|
||||
PikaGuiConfig *gui_config;
|
||||
GFile *file;
|
||||
const gchar *component;
|
||||
va_list args;
|
||||
|
||||
g_return_val_if_fail (PIKA_IS_PIKA (pika), NULL);
|
||||
g_return_val_if_fail (first_component != NULL, NULL);
|
||||
|
||||
gui_config = PIKA_GUI_CONFIG (pika->config);
|
||||
|
||||
file = g_object_ref (themes_get_theme_dir (pika, gui_config->theme));
|
||||
component = first_component;
|
||||
|
||||
va_start (args, first_component);
|
||||
|
||||
do
|
||||
{
|
||||
GFile *tmp = g_file_get_child (file, component);
|
||||
g_object_unref (file);
|
||||
file = tmp;
|
||||
}
|
||||
while ((component = va_arg (args, gchar *)));
|
||||
|
||||
va_end (args);
|
||||
|
||||
if (! g_file_query_exists (file, NULL))
|
||||
{
|
||||
g_object_unref (file);
|
||||
|
||||
file = g_object_ref (themes_get_theme_dir (pika, NULL));
|
||||
component = first_component;
|
||||
|
||||
va_start (args, first_component);
|
||||
|
||||
do
|
||||
{
|
||||
GFile *tmp = g_file_get_child (file, component);
|
||||
g_object_unref (file);
|
||||
file = tmp;
|
||||
}
|
||||
while ((component = va_arg (args, gchar *)));
|
||||
|
||||
va_end (args);
|
||||
}
|
||||
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
/* private functions */
|
||||
|
||||
static void
|
||||
themes_apply_theme (Pika *pika,
|
||||
PikaGuiConfig *config)
|
||||
{
|
||||
GFile *theme_css;
|
||||
GOutputStream *output;
|
||||
GError *error = NULL;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
g_return_if_fail (PIKA_IS_GUI_CONFIG (config));
|
||||
|
||||
theme_css = pika_directory_file ("theme.css", NULL);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Writing '%s'\n", pika_file_get_utf8_name (theme_css));
|
||||
|
||||
output = G_OUTPUT_STREAM (g_file_replace (theme_css,
|
||||
NULL, FALSE, G_FILE_CREATE_NONE,
|
||||
NULL, &error));
|
||||
if (! output)
|
||||
{
|
||||
pika_message_literal (pika, NULL, PIKA_MESSAGE_ERROR, error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
else
|
||||
{
|
||||
GFile *theme_dir = themes_get_theme_dir (pika, config->theme);
|
||||
GFile *css_user;
|
||||
GSList *css_files = NULL;
|
||||
GSList *iter;
|
||||
|
||||
if (theme_dir)
|
||||
{
|
||||
css_files = g_slist_prepend (css_files, g_file_get_child (theme_dir,
|
||||
"pika.css"));
|
||||
if (config->prefer_dark_theme)
|
||||
css_files = g_slist_prepend (css_files, g_file_get_child (theme_dir,
|
||||
"pika-dark.css"));
|
||||
}
|
||||
else
|
||||
{
|
||||
gchar *tmp;
|
||||
|
||||
tmp = g_build_filename (pika_data_directory (),
|
||||
"themes", "Default", "pika.css",
|
||||
NULL);
|
||||
css_files = g_slist_prepend (
|
||||
css_files, g_file_new_for_path (tmp));
|
||||
g_free (tmp);
|
||||
|
||||
if (config->prefer_dark_theme)
|
||||
{
|
||||
tmp = g_build_filename (pika_data_directory (),
|
||||
"themes", "Default", "pika-dark.css",
|
||||
NULL);
|
||||
css_files = g_slist_prepend (css_files, g_file_new_for_path (tmp));
|
||||
g_free (tmp);
|
||||
}
|
||||
}
|
||||
|
||||
css_files = g_slist_prepend (
|
||||
css_files, pika_sysconf_directory_file ("pika.css", NULL));
|
||||
|
||||
css_user = pika_directory_file ("pika.css", NULL);
|
||||
css_files = g_slist_prepend (
|
||||
css_files, css_user);
|
||||
|
||||
css_files = g_slist_reverse (css_files);
|
||||
|
||||
g_output_stream_printf (
|
||||
output, NULL, NULL, &error,
|
||||
"/* PIKA theme.css\n"
|
||||
" *\n"
|
||||
" * This file is written on PIKA startup and on every theme change.\n"
|
||||
" * It is NOT supposed to be edited manually. Edit your personal\n"
|
||||
" * pika.css file instead (%s).\n"
|
||||
" */\n"
|
||||
"\n",
|
||||
pika_file_get_utf8_name (css_user));
|
||||
|
||||
for (iter = css_files; ! error && iter; iter = g_slist_next (iter))
|
||||
{
|
||||
GFile *file = iter->data;
|
||||
|
||||
if (g_file_query_exists (file, NULL))
|
||||
{
|
||||
gchar *path;
|
||||
|
||||
path = g_file_get_uri (file);
|
||||
|
||||
g_output_stream_printf (
|
||||
output, NULL, NULL, &error,
|
||||
"@import url(\"%s\");\n",
|
||||
path);
|
||||
|
||||
g_free (path);
|
||||
}
|
||||
}
|
||||
|
||||
if (! error)
|
||||
{
|
||||
g_output_stream_printf (
|
||||
output, NULL, NULL, &error,
|
||||
"\n"
|
||||
"* { -gtk-icon-style: %s; }\n"
|
||||
"\n"
|
||||
"%s",
|
||||
config->prefer_symbolic_icons ? "symbolic" : "regular",
|
||||
config->prefer_dark_theme ? "/* prefer-dark-theme */\n" : "");
|
||||
}
|
||||
|
||||
if (! error && config->override_icon_size)
|
||||
{
|
||||
const gchar *tool_icon_size = "large-toolbar";
|
||||
const gchar *tab_icon_size = "small-toolbar";
|
||||
const gchar *button_icon_size = "small-toolbar";
|
||||
|
||||
switch (config->custom_icon_size)
|
||||
{
|
||||
case PIKA_ICON_SIZE_SMALL:
|
||||
tool_icon_size = "small-toolbar";
|
||||
tab_icon_size = "small-toolbar";
|
||||
button_icon_size = "small-toolbar";
|
||||
break;
|
||||
case PIKA_ICON_SIZE_MEDIUM:
|
||||
tool_icon_size = "large-toolbar";
|
||||
tab_icon_size = "small-toolbar";
|
||||
button_icon_size = "small-toolbar";
|
||||
break;
|
||||
case PIKA_ICON_SIZE_LARGE:
|
||||
tool_icon_size = "dnd";
|
||||
tab_icon_size = "large-toolbar";
|
||||
button_icon_size = "large-toolbar";
|
||||
break;
|
||||
case PIKA_ICON_SIZE_HUGE:
|
||||
tool_icon_size = "dialog";
|
||||
tab_icon_size = "dnd";
|
||||
button_icon_size = "dnd";
|
||||
break;
|
||||
}
|
||||
|
||||
g_output_stream_printf (
|
||||
output, NULL, NULL, &error,
|
||||
"\n"
|
||||
"* { -PikaToolPalette-tool-icon-size: %s; }"
|
||||
"\n"
|
||||
"* { -PikaDockbook-tab-icon-size: %s; }"
|
||||
"\n"
|
||||
"* { -PikaEditor-button-icon-size: %s; }",
|
||||
tool_icon_size,
|
||||
tab_icon_size,
|
||||
button_icon_size);
|
||||
}
|
||||
|
||||
if (! error)
|
||||
{
|
||||
g_output_stream_printf (
|
||||
output, NULL, NULL, &error,
|
||||
"\n\n"
|
||||
"/* end of theme.css */\n");
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
GCancellable *cancellable = g_cancellable_new ();
|
||||
|
||||
pika_message (pika, NULL, PIKA_MESSAGE_ERROR,
|
||||
_("Error writing '%s': %s"),
|
||||
pika_file_get_utf8_name (theme_css), error->message);
|
||||
g_clear_error (&error);
|
||||
|
||||
/* Cancel the overwrite initiated by g_file_replace(). */
|
||||
g_cancellable_cancel (cancellable);
|
||||
g_output_stream_close (output, cancellable, NULL);
|
||||
g_object_unref (cancellable);
|
||||
}
|
||||
else if (! g_output_stream_close (output, NULL, &error))
|
||||
{
|
||||
pika_message (pika, NULL, PIKA_MESSAGE_ERROR,
|
||||
_("Error closing '%s': %s"),
|
||||
pika_file_get_utf8_name (theme_css), error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_slist_free_full (css_files, g_object_unref);
|
||||
g_object_unref (output);
|
||||
}
|
||||
|
||||
g_object_unref (theme_css);
|
||||
}
|
||||
|
||||
static void
|
||||
themes_list_themes_foreach (gpointer key,
|
||||
gpointer value,
|
||||
gpointer data)
|
||||
{
|
||||
gchar ***index = data;
|
||||
|
||||
**index = g_strdup ((gchar *) key);
|
||||
|
||||
(*index)++;
|
||||
}
|
||||
|
||||
static gint
|
||||
themes_name_compare (const void *p1,
|
||||
const void *p2)
|
||||
{
|
||||
return strcmp (* (char **) p1, * (char **) p2);
|
||||
}
|
||||
|
||||
static void
|
||||
themes_theme_change_notify (PikaGuiConfig *config,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika)
|
||||
{
|
||||
GFile *theme_css;
|
||||
GError *error = NULL;
|
||||
|
||||
g_object_set (gtk_settings_get_for_screen (gdk_screen_get_default ()),
|
||||
"gtk-application-prefer-dark-theme", config->prefer_dark_theme,
|
||||
NULL);
|
||||
|
||||
themes_apply_theme (pika, config);
|
||||
|
||||
theme_css = pika_directory_file ("theme.css", NULL);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Parsing '%s'\n",
|
||||
pika_file_get_utf8_name (theme_css));
|
||||
|
||||
if (! gtk_css_provider_load_from_file (GTK_CSS_PROVIDER (themes_style_provider),
|
||||
theme_css, &error))
|
||||
{
|
||||
g_printerr ("%s: error parsing %s: %s\n", G_STRFUNC,
|
||||
pika_file_get_utf8_name (theme_css), error->message);
|
||||
g_clear_error (&error);
|
||||
}
|
||||
|
||||
g_object_unref (theme_css);
|
||||
|
||||
gtk_style_context_reset_widgets (gdk_screen_get_default ());
|
||||
}
|
||||
|
||||
static void
|
||||
themes_theme_paths_notify (PikaExtensionManager *manager,
|
||||
GParamSpec *pspec,
|
||||
Pika *pika)
|
||||
{
|
||||
PikaGuiConfig *config;
|
||||
|
||||
g_return_if_fail (PIKA_IS_PIKA (pika));
|
||||
|
||||
if (themes_hash)
|
||||
g_hash_table_remove_all (themes_hash);
|
||||
else
|
||||
themes_hash = g_hash_table_new_full (g_str_hash,
|
||||
g_str_equal,
|
||||
g_free,
|
||||
g_object_unref);
|
||||
|
||||
config = PIKA_GUI_CONFIG (pika->config);
|
||||
if (config->theme_path)
|
||||
{
|
||||
GList *path;
|
||||
GList *list;
|
||||
|
||||
g_object_get (pika->extension_manager,
|
||||
"theme-paths", &path,
|
||||
NULL);
|
||||
path = g_list_copy_deep (path, (GCopyFunc) g_object_ref, NULL);
|
||||
path = g_list_concat (path, pika_config_path_expand_to_files (config->theme_path, NULL));
|
||||
|
||||
for (list = path; list; list = g_list_next (list))
|
||||
{
|
||||
GFile *dir = list->data;
|
||||
GFileEnumerator *enumerator;
|
||||
|
||||
enumerator =
|
||||
g_file_enumerate_children (dir,
|
||||
G_FILE_ATTRIBUTE_STANDARD_NAME ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","
|
||||
G_FILE_ATTRIBUTE_STANDARD_TYPE,
|
||||
G_FILE_QUERY_INFO_NONE,
|
||||
NULL, NULL);
|
||||
|
||||
if (enumerator)
|
||||
{
|
||||
GFileInfo *info;
|
||||
|
||||
while ((info = g_file_enumerator_next_file (enumerator,
|
||||
NULL, NULL)))
|
||||
{
|
||||
if (! g_file_info_get_is_hidden (info) &&
|
||||
g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY)
|
||||
{
|
||||
GFile *file;
|
||||
const gchar *name;
|
||||
gchar *basename;
|
||||
|
||||
file = g_file_enumerator_get_child (enumerator, info);
|
||||
name = pika_file_get_utf8_name (file);
|
||||
|
||||
basename = g_path_get_basename (name);
|
||||
|
||||
if (pika->be_verbose)
|
||||
g_print ("Adding theme '%s' (%s)\n",
|
||||
basename, name);
|
||||
|
||||
g_hash_table_insert (themes_hash, basename, file);
|
||||
}
|
||||
|
||||
g_object_unref (info);
|
||||
}
|
||||
|
||||
g_object_unref (enumerator);
|
||||
}
|
||||
}
|
||||
|
||||
g_list_free_full (path, (GDestroyNotify) g_object_unref);
|
||||
}
|
||||
}
|
38
app/gui/themes.h
Normal file
38
app/gui/themes.h
Normal file
@ -0,0 +1,38 @@
|
||||
/* PIKA - Photo and Image Kooker Application
|
||||
* a rebranding of The GNU Image Manipulation Program (created with heckimp)
|
||||
* A derived work which may be trivial. However, any changes may be (C)2023 by Aldercone Studio
|
||||
*
|
||||
* Original copyright, applying to most contents (license remains unchanged):
|
||||
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
|
||||
*
|
||||
* 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 3 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, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef __THEMES_H__
|
||||
#define __THEMES_H__
|
||||
|
||||
|
||||
void themes_init (Pika *pika);
|
||||
void themes_exit (Pika *pika);
|
||||
|
||||
gchar ** themes_list_themes (Pika *pika,
|
||||
gint *n_themes);
|
||||
GFile * themes_get_theme_dir (Pika *pika,
|
||||
const gchar *theme_name);
|
||||
GFile * themes_get_theme_file (Pika *pika,
|
||||
const gchar *first_component,
|
||||
...) G_GNUC_NULL_TERMINATED;
|
||||
|
||||
|
||||
#endif /* __THEMES_H__ */
|
Reference in New Issue
Block a user