Initial checkin of Pika from heckimp

This commit is contained in:
2023-09-25 15:35:21 -07:00
commit 891e999216
6761 changed files with 5240685 additions and 0 deletions

31
app/gui/dbus-service.xml Normal file
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

34
app/gui/gui-vtable.h Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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);
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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__ */