mirror of
https://github.com/ZoiteChat/zoitechat.git
synced 2026-10-25 13:55:38 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6bae96668a |
No files matched your search
@@ -29,7 +29,7 @@ jobs:
|
||||
build-essential pkg-config meson ninja-build cmake \
|
||||
gettext \
|
||||
libcanberra-dev libglib2.0-dev \
|
||||
libarchive-dev \
|
||||
libarchive-dev libxss-dev \
|
||||
libgtk-3-dev libfontconfig1-dev libpango1.0-dev xvfb xauth dbus-daemon at-spi2-core \
|
||||
libwayland-client0 libwayland-cursor0 libwayland-egl1 \
|
||||
libxkbcommon0 \
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
ninja -C build
|
||||
|
||||
- name: Test emoji catalog, font coverage and picker
|
||||
run: xvfb-run -a dbus-run-session -- meson test -C build --print-errorlogs "Emoji Data Tests" "Emoji Font Tests" "Emoji UI Tests"
|
||||
run: xvfb-run -a dbus-run-session -- meson test -C build --print-errorlogs "Autoaway Tests" "Idle Backend Tests" "Emoji Data Tests" "Emoji Font Tests" "Emoji UI Tests"
|
||||
|
||||
- name: Install to AppDir
|
||||
run: |
|
||||
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
)
|
||||
shell: cmd
|
||||
|
||||
- name: Test emoji catalog, font coverage and picker
|
||||
- name: Test autoaway, emoji catalog, font coverage and picker
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
@@ -213,6 +213,10 @@ jobs:
|
||||
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
|
||||
if errorlevel 1 exit /b 1
|
||||
)
|
||||
msbuild win32\autoaway-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64
|
||||
if errorlevel 1 exit /b 1
|
||||
..\zoitechat-build\x64\tests\autoaway-tests.exe --tap
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
- name: Preparing Artifacts
|
||||
run: |
|
||||
|
||||
@@ -209,7 +209,7 @@ jobs:
|
||||
|
||||
# Master added native emoji font staging and tests after the release
|
||||
# branch split. Run these before packaging so MSI/MSIX use verified files.
|
||||
- name: Test emoji catalog, font coverage and picker
|
||||
- name: Test autoaway, emoji catalog, font coverage and picker
|
||||
shell: cmd
|
||||
run: |
|
||||
call "C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Common7\Tools\VsDevCmd.bat"
|
||||
@@ -224,6 +224,10 @@ jobs:
|
||||
..\zoitechat-build\x64\tests\emoji-%%T-tests.exe --tap
|
||||
if errorlevel 1 exit /b 1
|
||||
)
|
||||
msbuild win32\autoaway-tests.vcxproj /m /verbosity:minimal /p:Configuration=Release /p:Platform=x64
|
||||
if errorlevel 1 exit /b 1
|
||||
..\zoitechat-build\x64\tests\autoaway-tests.exe --tap
|
||||
if errorlevel 1 exit /b 1
|
||||
|
||||
- name: Install WiX Toolset
|
||||
shell: pwsh
|
||||
|
||||
+4
-2
@@ -138,8 +138,10 @@ html_context = {
|
||||
# of the sidebar.
|
||||
html_logo = '_static/zoitechat-logo.svg'
|
||||
|
||||
# Use the ZoiteChat logo instead of the old HexChat favicon.
|
||||
html_favicon = '_static/zoitechat-logo.svg'
|
||||
# The name of an image file (within the static path) to use as favicon of the
|
||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||
# pixels large.
|
||||
html_favicon = '_static/favicon.ico'
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
|
||||
@@ -628,3 +628,38 @@ List of Settings
|
||||
+------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
| **url\_logging** | Toggle logging URLs to *<config>/url.log*. |
|
||||
+------------------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|
||||
|
||||
Autoaway
|
||||
--------
|
||||
|
||||
In **Preferences > Chatting > General > Away**, set **Autoaway after** to a
|
||||
number of minutes. The default is 0 (disabled). The separate **Autoaway
|
||||
message** defaults to "Idle". No plugin or scripting language is required.
|
||||
The same options are available immediately through ``/SET``::
|
||||
|
||||
/SET away_auto_timeout 10
|
||||
/SET away_auto_reason Idle
|
||||
/SET away_auto_system ON
|
||||
|
||||
With desktop activity enabled, Windows uses the current login session's last
|
||||
input time, macOS uses CoreGraphics input time, and native X11 uses the
|
||||
XScreenSaver extension if its development library was available at build time.
|
||||
Debian/Ubuntu builders can install ``libxss-dev``; Fedora builders can install
|
||||
``libXScrnSaver-devel``. Meson detects this optional dependency automatically.
|
||||
|
||||
On native Wayland, XWayland, unsupported desktops, and builds without XScreenSaver,
|
||||
autoaway falls back to keyboard, mouse, scroll and touch activity **inside
|
||||
ZoiteChat**. Activity in other applications does not reset that fallback timer.
|
||||
Set ``away_auto_system OFF`` to always use ZoiteChat activity on every platform.
|
||||
The text frontend uses submitted user input. Network traffic, redraws and window
|
||||
focus changes do not count as user activity.
|
||||
|
||||
Autoaway checks every five seconds, after IRC login finishes, independently on
|
||||
each connected network. Returning clears only away states set by autoaway.
|
||||
Manual ``/AWAY`` (including ``/AWAY`` while already automatically away) takes
|
||||
ownership, so activity does not clear it. ``/BACK`` resets the inactivity timer.
|
||||
The existing **Automatically unmark away** setting keeps its separate behaviour
|
||||
when sending messages. Do not run another autoaway script at the same time.
|
||||
Disabling autoaway clears its own away states. Reconnecting re-evaluates current
|
||||
inactivity instead of restoring an outdated automatic away state; manual away
|
||||
restoration is unchanged.
|
||||
@@ -55,7 +55,7 @@
|
||||
"run-tests": true,
|
||||
"test-rule": "",
|
||||
"test-commands": [
|
||||
"meson test --print-errorlogs 'Emoji Data Tests' 'Emoji Font Tests'"
|
||||
"meson test --print-errorlogs 'Autoaway Tests' 'Idle Backend Tests' 'Emoji Data Tests' 'Emoji Font Tests'"
|
||||
],
|
||||
"config-opts": [
|
||||
"-Ddbus-service-use-appid=true",
|
||||
|
||||
@@ -25,6 +25,7 @@ BuildRequires: pkgconfig(gmodule-2.0)
|
||||
BuildRequires: pkgconfig(gtk+-3.0) >= 3.22
|
||||
BuildRequires: pkgconfig(fontconfig) >= 2.13
|
||||
BuildRequires: pkgconfig(pangoft2) >= 1.44
|
||||
BuildRequires: pkgconfig(xscrnsaver)
|
||||
BuildRequires: pkgconfig(iso-codes)
|
||||
BuildRequires: pkgconfig(libarchive)
|
||||
BuildRequires: pkgconfig(libcanberra) >= 0.22
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "zoitechat.h"
|
||||
#include "zoitechatc.h"
|
||||
#include "server.h"
|
||||
#include "fe.h"
|
||||
#include "autoaway.h"
|
||||
|
||||
static gint64 last_activity;
|
||||
static int timer_tag;
|
||||
static int previous_timeout;
|
||||
static unsigned int previous_system;
|
||||
|
||||
void
|
||||
autoaway_activity (void)
|
||||
{
|
||||
last_activity = g_get_monotonic_time ();
|
||||
}
|
||||
|
||||
/* A manual /AWAY takes ownership even if the server already says away. */
|
||||
void
|
||||
autoaway_manual (server *serv)
|
||||
{
|
||||
serv->autoaway_owned = FALSE;
|
||||
autoaway_activity ();
|
||||
}
|
||||
|
||||
void
|
||||
autoaway_reset (server *serv)
|
||||
{
|
||||
if (!serv->autoaway_owned)
|
||||
return;
|
||||
|
||||
serv->autoaway_owned = FALSE;
|
||||
serv->reconnect_away = FALSE;
|
||||
g_clear_pointer (&serv->last_away_reason, g_free);
|
||||
}
|
||||
|
||||
static void
|
||||
autoaway_release (server *serv)
|
||||
{
|
||||
if (!serv->autoaway_owned)
|
||||
return;
|
||||
|
||||
/* Cancel even an unacknowledged AWAY. IRC replies may arrive later. */
|
||||
if (serv->connected && serv->end_of_motd)
|
||||
serv->p_set_back (serv);
|
||||
autoaway_reset (serv);
|
||||
}
|
||||
|
||||
void
|
||||
autoaway_check (gint64 now, gint64 desktop_idle)
|
||||
{
|
||||
GSList *list;
|
||||
gint64 idle = MAX ((gint64)0, now - last_activity);
|
||||
gint64 limit = (gint64)prefs.hex_away_auto_timeout * 60 * G_USEC_PER_SEC;
|
||||
|
||||
if (prefs.hex_away_auto_system && desktop_idle >= 0)
|
||||
idle = MIN (idle, desktop_idle);
|
||||
|
||||
for (list = serv_list; list; list = list->next)
|
||||
{
|
||||
server *serv = list->data;
|
||||
|
||||
if (!serv->connected)
|
||||
{
|
||||
/* Re-evaluate idle after login instead of restoring stale autoaway. */
|
||||
autoaway_reset (serv);
|
||||
continue;
|
||||
}
|
||||
if (!serv->end_of_motd)
|
||||
continue;
|
||||
|
||||
if (limit <= 0 || idle < limit)
|
||||
{
|
||||
autoaway_release (serv);
|
||||
continue;
|
||||
}
|
||||
if (serv->is_away || serv->reconnect_away)
|
||||
continue;
|
||||
|
||||
/* Retry a rejected/lost request slowly, never once per poll. */
|
||||
if (serv->autoaway_owned &&
|
||||
now - serv->autoaway_sent_at < 30 * G_USEC_PER_SEC)
|
||||
continue;
|
||||
|
||||
g_free (serv->last_away_reason);
|
||||
serv->last_away_reason = g_strdup (prefs.hex_away_auto_reason[0] ?
|
||||
prefs.hex_away_auto_reason : _("Idle"));
|
||||
serv->autoaway_owned = TRUE;
|
||||
serv->autoaway_sent_at = now;
|
||||
serv->p_set_away (serv, serv->last_away_reason);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
autoaway_timer (void)
|
||||
{
|
||||
autoaway_check (g_get_monotonic_time (),
|
||||
prefs.hex_away_auto_system ? fe_get_idle_time () : -1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void
|
||||
autoaway_reinit (void)
|
||||
{
|
||||
if (previous_timeout != prefs.hex_away_auto_timeout ||
|
||||
previous_system != prefs.hex_away_auto_system)
|
||||
{
|
||||
autoaway_activity ();
|
||||
previous_timeout = prefs.hex_away_auto_timeout;
|
||||
previous_system = prefs.hex_away_auto_system;
|
||||
}
|
||||
|
||||
if (prefs.hex_away_auto_timeout > 0)
|
||||
{
|
||||
if (!timer_tag)
|
||||
{
|
||||
autoaway_activity ();
|
||||
timer_tag = fe_timeout_add_seconds (5, autoaway_timer, NULL);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timer_tag)
|
||||
{
|
||||
fe_timeout_remove (timer_tag);
|
||||
timer_tag = 0;
|
||||
}
|
||||
/* Disabling autoaway must also work without another input event. */
|
||||
autoaway_check (g_get_monotonic_time (), -1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef ZOITECHAT_AUTOAWAY_H
|
||||
#define ZOITECHAT_AUTOAWAY_H
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
struct server;
|
||||
|
||||
void autoaway_activity (void);
|
||||
void autoaway_manual (struct server *serv);
|
||||
void autoaway_reset (struct server *serv);
|
||||
void autoaway_reinit (void);
|
||||
/* Both samples are in microseconds; -1 means desktop idle is unavailable. */
|
||||
void autoaway_check (gint64 now, gint64 desktop_idle);
|
||||
|
||||
#endif
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "fe.h"
|
||||
#include "text.h"
|
||||
#include "zoitechatc.h"
|
||||
#include "autoaway.h"
|
||||
#include "typedef.h"
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -359,6 +360,9 @@ default_file (void)
|
||||
|
||||
const struct prefs vars[] =
|
||||
{
|
||||
{"away_auto_reason", P_OFFSET (hex_away_auto_reason), TYPE_STR},
|
||||
{"away_auto_system", P_OFFINT (hex_away_auto_system), TYPE_BOOL, autoaway_reinit},
|
||||
{"away_auto_timeout", P_OFFINT (hex_away_auto_timeout), TYPE_INT, autoaway_reinit},
|
||||
{"away_auto_unmark", P_OFFINT (hex_away_auto_unmark), TYPE_BOOL},
|
||||
{"away_omit_alerts", P_OFFINT (hex_away_omit_alerts), TYPE_BOOL},
|
||||
{"away_reason", P_OFFSET (hex_away_reason), TYPE_STR},
|
||||
@@ -767,6 +771,7 @@ load_default_config(void)
|
||||
/* put in default values, anything left out is automatically zero */
|
||||
|
||||
/* BOOLEANS */
|
||||
prefs.hex_away_auto_system = 1;
|
||||
prefs.hex_away_show_once = 1;
|
||||
prefs.hex_away_track = 1;
|
||||
prefs.hex_dcc_auto_resume = 1;
|
||||
@@ -887,6 +892,7 @@ load_default_config(void)
|
||||
prefs.hex_url_grabber_limit = 100; /* 0 means unlimited */
|
||||
|
||||
/* STRINGS */
|
||||
strcpy (prefs.hex_away_auto_reason, _("Idle"));
|
||||
strcpy (prefs.hex_away_reason, _("I'm busy"));
|
||||
strcpy (prefs.hex_completion_suffix, ",");
|
||||
#ifdef WIN32
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="autoaway.h" />
|
||||
<ClInclude Include="cfgfiles.h" />
|
||||
<ClInclude Include="chanopt.h" />
|
||||
<ClInclude Include="ctcp.h" />
|
||||
@@ -52,6 +53,7 @@
|
||||
<ClInclude Include="gtk3-theme-service.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="autoaway.c" />
|
||||
<ClCompile Include="cfgfiles.c" />
|
||||
<ClCompile Include="chanopt.c" />
|
||||
<ClCompile Include="ctcp.c" />
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="autoaway.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="cfgfiles.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@@ -136,6 +139,9 @@
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="autoaway.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cfgfiles.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -142,6 +142,7 @@ void fe_lastlog (session *sess, session *lastlog_sess, char *sstr, gtk_xtext_sea
|
||||
void fe_set_lag (server *serv, long lag);
|
||||
void fe_set_throttle (server *serv);
|
||||
void fe_set_away (server *serv);
|
||||
gint64 fe_get_idle_time (void);
|
||||
void fe_serverlist_open (session *sess);
|
||||
void fe_get_bool (char *title, char *prompt, void *callback, void *userdata);
|
||||
void fe_get_str (char *prompt, char *def, void *callback, void *ud);
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
common_sources = [
|
||||
'autoaway.c',
|
||||
'cfgfiles.c',
|
||||
'chanopt.c',
|
||||
'ctcp.c',
|
||||
@@ -224,3 +225,10 @@ test('Hex Color Tests', hexcolor_tests,
|
||||
protocol: 'tap',
|
||||
timeout: 120,
|
||||
)
|
||||
|
||||
autoaway_tests = executable('autoaway_tests',
|
||||
['tests/test-autoaway.c', 'autoaway.c'],
|
||||
include_directories: [config_h_include, include_directories('.')],
|
||||
dependencies: zoitechat_common_header_deps,
|
||||
)
|
||||
test('Autoaway Tests', autoaway_tests, protocol: 'tap', timeout: 30)
|
||||
+12
-1
@@ -54,6 +54,7 @@
|
||||
#include "server.h"
|
||||
#include "tree.h"
|
||||
#include "outbound.h"
|
||||
#include "autoaway.h"
|
||||
#include "chanopt.h"
|
||||
|
||||
#define TBUFSIZE 4096
|
||||
@@ -360,6 +361,10 @@ cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||
{
|
||||
char *reason = word_eol[2];
|
||||
|
||||
/* Restoring a manual away after reconnect is not new user input. */
|
||||
if (!sess->server->reconnect_away)
|
||||
autoaway_manual (sess->server);
|
||||
|
||||
if (!(*reason))
|
||||
{
|
||||
if (sess->server->is_away)
|
||||
@@ -396,7 +401,10 @@ cmd_away (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||
static int
|
||||
cmd_back (struct session *sess, char *tbuf, char *word[], char *word_eol[])
|
||||
{
|
||||
if (sess->server->is_away)
|
||||
gboolean was_autoaway = sess->server->autoaway_owned;
|
||||
|
||||
autoaway_manual (sess->server);
|
||||
if (sess->server->is_away || was_autoaway)
|
||||
{
|
||||
sess->server->p_set_back (sess->server);
|
||||
}
|
||||
@@ -5051,6 +5059,9 @@ handle_user_input (session *sess, char *text, int history, int nocommand)
|
||||
if (*text == '\0')
|
||||
return 1;
|
||||
|
||||
if (history)
|
||||
autoaway_activity ();
|
||||
|
||||
if (history)
|
||||
history_add (&sess->history, text);
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
#include "zoitechat.h"
|
||||
#include "proto-irc.h"
|
||||
#include "autoaway.h"
|
||||
#include "ctcp.h"
|
||||
#include "fe.h"
|
||||
#include "ignore.h"
|
||||
@@ -438,6 +439,12 @@ irc_raw (server *serv, char *raw)
|
||||
{
|
||||
int len;
|
||||
char tbuf[8704];
|
||||
|
||||
/* /QUOTE AWAY and scripts sending raw AWAY also take manual ownership. */
|
||||
if (!g_ascii_strncasecmp (raw, "AWAY", 4) &&
|
||||
(raw[4] == 0 || raw[4] == ' '))
|
||||
autoaway_manual (serv);
|
||||
|
||||
if (*raw)
|
||||
{
|
||||
len = strlen (raw);
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
#include "proto-irc.h"
|
||||
#include "servlist.h"
|
||||
#include "server.h"
|
||||
#include "autoaway.h"
|
||||
#include "sts.h"
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
@@ -1873,6 +1874,7 @@ is_server (server *serv)
|
||||
void
|
||||
server_set_defaults (server *serv)
|
||||
{
|
||||
autoaway_reset (serv);
|
||||
g_free (serv->chantypes);
|
||||
g_clear_pointer (&serv->clienttagdeny, g_free);
|
||||
g_free (serv->chanmodes);
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include "../zoitechat.h"
|
||||
#include "../autoaway.h"
|
||||
|
||||
struct zoitechatprefs prefs;
|
||||
GSList *serv_list;
|
||||
static server servers[3];
|
||||
static int away_count, back_count, timer_adds, timer_removes;
|
||||
static gint64 base;
|
||||
static char *sent_reason;
|
||||
|
||||
/* Protocol stubs deliberately do not acknowledge requests. */
|
||||
static void
|
||||
send_away (server *serv, char *reason)
|
||||
{
|
||||
away_count++;
|
||||
g_free (sent_reason);
|
||||
sent_reason = g_strdup (reason);
|
||||
}
|
||||
|
||||
static void
|
||||
send_back (server *serv)
|
||||
{
|
||||
back_count++;
|
||||
}
|
||||
|
||||
int
|
||||
fe_timeout_add_seconds (int interval, void *callback, void *data)
|
||||
{
|
||||
g_assert_cmpint (interval, ==, 5);
|
||||
timer_adds++;
|
||||
return 42;
|
||||
}
|
||||
|
||||
void
|
||||
fe_timeout_remove (int tag)
|
||||
{
|
||||
g_assert_cmpint (tag, ==, 42);
|
||||
timer_removes++;
|
||||
}
|
||||
|
||||
gint64
|
||||
fe_get_idle_time (void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void
|
||||
setup (void)
|
||||
{
|
||||
int i;
|
||||
|
||||
prefs.hex_away_auto_timeout = 0;
|
||||
autoaway_reinit ();
|
||||
for (i = 0; i < 3; i++)
|
||||
g_free (servers[i].last_away_reason);
|
||||
g_slist_free (serv_list);
|
||||
serv_list = NULL;
|
||||
memset (servers, 0, sizeof servers);
|
||||
memset (&prefs, 0, sizeof prefs);
|
||||
away_count = back_count = timer_adds = timer_removes = 0;
|
||||
g_clear_pointer (&sent_reason, g_free);
|
||||
prefs.hex_away_auto_timeout = 10;
|
||||
prefs.hex_away_auto_system = TRUE;
|
||||
g_strlcpy (prefs.hex_away_auto_reason, "Auto idle", sizeof prefs.hex_away_auto_reason);
|
||||
for (i = 0; i < 3; i++)
|
||||
{
|
||||
servers[i].connected = TRUE;
|
||||
servers[i].end_of_motd = TRUE;
|
||||
servers[i].p_set_away = send_away;
|
||||
servers[i].p_set_back = send_back;
|
||||
}
|
||||
serv_list = g_slist_append (NULL, &servers[0]);
|
||||
autoaway_activity ();
|
||||
base = g_get_monotonic_time ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_fallback_boundary (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 599 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 0);
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
g_assert_true (servers[0].autoaway_owned);
|
||||
g_assert_cmpstr (sent_reason, ==, "Auto idle");
|
||||
/* An ACK is not required to avoid flooding the connection. */
|
||||
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
servers[0].is_away = TRUE;
|
||||
autoaway_check (base + 640 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
autoaway_activity ();
|
||||
autoaway_check (g_get_monotonic_time (), -1);
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
g_assert_false (servers[0].autoaway_owned);
|
||||
g_assert_null (servers[0].last_away_reason);
|
||||
}
|
||||
|
||||
static void
|
||||
test_desktop_activity (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 900 * G_USEC_PER_SEC, 5 * G_USEC_PER_SEC);
|
||||
g_assert_cmpint (away_count, ==, 0);
|
||||
autoaway_check (base + 900 * G_USEC_PER_SEC, 700 * G_USEC_PER_SEC);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
servers[0].is_away = TRUE;
|
||||
autoaway_check (base + 905 * G_USEC_PER_SEC, 0);
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
/* App input wins even if the native backend still has an old sample. */
|
||||
autoaway_activity ();
|
||||
autoaway_check (g_get_monotonic_time (), 900 * G_USEC_PER_SEC);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_app_mode (void)
|
||||
{
|
||||
setup ();
|
||||
prefs.hex_away_auto_system = FALSE;
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, 0);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_manual_away (void)
|
||||
{
|
||||
setup ();
|
||||
servers[0].is_away = TRUE;
|
||||
servers[0].last_away_reason = g_strdup ("Lunch");
|
||||
autoaway_check (base + 900 * G_USEC_PER_SEC, -1);
|
||||
autoaway_check (base + 905 * G_USEC_PER_SEC, 0);
|
||||
prefs.hex_away_auto_timeout = 0;
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (away_count, ==, 0);
|
||||
g_assert_cmpint (back_count, ==, 0);
|
||||
g_assert_cmpstr (servers[0].last_away_reason, ==, "Lunch");
|
||||
}
|
||||
|
||||
static void
|
||||
test_manual_takeover (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
servers[0].is_away = TRUE;
|
||||
/* This is also called for /AWAY with no reason, even if already away. */
|
||||
autoaway_manual (&servers[0]);
|
||||
autoaway_check (g_get_monotonic_time (), 0);
|
||||
prefs.hex_away_auto_timeout = 0;
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (back_count, ==, 0);
|
||||
g_assert_true (servers[0].is_away);
|
||||
}
|
||||
|
||||
static void
|
||||
test_pending_away_cancel (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
g_assert_false (servers[0].is_away);
|
||||
autoaway_activity ();
|
||||
autoaway_check (g_get_monotonic_time (), -1);
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
/* A late 306 must not make us own the away state again. */
|
||||
servers[0].is_away = TRUE;
|
||||
autoaway_check (g_get_monotonic_time (), -1);
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_login_and_multiple_servers (void)
|
||||
{
|
||||
setup ();
|
||||
serv_list = g_slist_append (serv_list, &servers[1]);
|
||||
serv_list = g_slist_append (serv_list, &servers[2]);
|
||||
servers[1].is_away = TRUE;
|
||||
servers[1].last_away_reason = g_strdup ("Manual");
|
||||
servers[2].end_of_motd = FALSE;
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
servers[2].end_of_motd = TRUE;
|
||||
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 2);
|
||||
autoaway_check (base + 610 * G_USEC_PER_SEC, 0);
|
||||
g_assert_cmpint (back_count, ==, 2);
|
||||
g_assert_cmpstr (servers[1].last_away_reason, ==, "Manual");
|
||||
}
|
||||
|
||||
static void
|
||||
test_disconnect_and_reconnect (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
servers[0].connected = FALSE;
|
||||
servers[0].is_away = TRUE;
|
||||
servers[0].reconnect_away = TRUE;
|
||||
autoaway_check (base + 605 * G_USEC_PER_SEC, -1);
|
||||
g_assert_false (servers[0].autoaway_owned);
|
||||
g_assert_false (servers[0].reconnect_away);
|
||||
g_assert_null (servers[0].last_away_reason);
|
||||
g_assert_cmpint (back_count, ==, 0);
|
||||
servers[0].is_away = FALSE;
|
||||
servers[0].connected = TRUE;
|
||||
servers[0].end_of_motd = FALSE;
|
||||
autoaway_check (base + 610 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
servers[0].end_of_motd = TRUE;
|
||||
autoaway_check (base + 615 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 2);
|
||||
/* server_set_defaults resets ownership even before the next timer tick. */
|
||||
servers[0].reconnect_away = TRUE;
|
||||
autoaway_reset (&servers[0]);
|
||||
g_assert_false (servers[0].reconnect_away);
|
||||
/* Existing manual reconnect-away must be left alone. */
|
||||
servers[0].reconnect_away = TRUE;
|
||||
servers[0].last_away_reason = g_strdup ("Manual");
|
||||
autoaway_reset (&servers[0]);
|
||||
g_assert_true (servers[0].reconnect_away);
|
||||
g_assert_cmpstr (servers[0].last_away_reason, ==, "Manual");
|
||||
}
|
||||
|
||||
static void
|
||||
test_retry (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
autoaway_check (base + 629 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
autoaway_check (base + 630 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 2);
|
||||
}
|
||||
|
||||
static void
|
||||
test_disable_and_timers (void)
|
||||
{
|
||||
setup ();
|
||||
autoaway_reinit ();
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (timer_adds, ==, 1);
|
||||
base = g_get_monotonic_time ();
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
prefs.hex_away_auto_timeout = 0;
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
g_assert_cmpint (timer_removes, ==, 1);
|
||||
g_assert_false (servers[0].autoaway_owned);
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (back_count, ==, 1);
|
||||
prefs.hex_away_auto_timeout = -1;
|
||||
autoaway_reinit ();
|
||||
g_assert_cmpint (away_count, ==, 1);
|
||||
}
|
||||
|
||||
static void
|
||||
test_empty_reason_and_large_timeout (void)
|
||||
{
|
||||
setup ();
|
||||
prefs.hex_away_auto_reason[0] = 0;
|
||||
autoaway_check (base + 600 * G_USEC_PER_SEC, -1);
|
||||
g_assert_nonnull (sent_reason);
|
||||
g_assert_cmpstr (sent_reason, !=, "");
|
||||
setup ();
|
||||
prefs.hex_away_auto_timeout = G_MAXINT;
|
||||
autoaway_check (base + 900 * G_USEC_PER_SEC, -1);
|
||||
g_assert_cmpint (away_count, ==, 0);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
int result, i;
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
g_test_add_func ("/autoaway/fallback-boundary", test_fallback_boundary);
|
||||
g_test_add_func ("/autoaway/desktop-activity", test_desktop_activity);
|
||||
g_test_add_func ("/autoaway/app-mode", test_app_mode);
|
||||
g_test_add_func ("/autoaway/manual-away", test_manual_away);
|
||||
g_test_add_func ("/autoaway/manual-takeover", test_manual_takeover);
|
||||
g_test_add_func ("/autoaway/pending-cancel", test_pending_away_cancel);
|
||||
g_test_add_func ("/autoaway/login-multiple-servers", test_login_and_multiple_servers);
|
||||
g_test_add_func ("/autoaway/reconnect", test_disconnect_and_reconnect);
|
||||
g_test_add_func ("/autoaway/retry", test_retry);
|
||||
g_test_add_func ("/autoaway/disable-timers", test_disable_and_timers);
|
||||
g_test_add_func ("/autoaway/empty-reason-large-timeout", test_empty_reason_and_large_timeout);
|
||||
result = g_test_run ();
|
||||
for (i = 0; i < 3; i++)
|
||||
g_free (servers[i].last_away_reason);
|
||||
g_free (sent_reason);
|
||||
g_slist_free (serv_list);
|
||||
return result;
|
||||
}
|
||||
+22
-48
@@ -68,7 +68,7 @@ static ca_context *ca_con;
|
||||
#define SCROLLBACK_MAX 32000
|
||||
|
||||
static void mkdir_p (char *filename);
|
||||
static char *log_create_filename (const char *channame);
|
||||
static char *log_create_filename (char *channame);
|
||||
|
||||
static char *
|
||||
scrollback_get_filename (session *sess)
|
||||
@@ -396,7 +396,7 @@ mkdir_p (char *filename)
|
||||
}
|
||||
|
||||
static char *
|
||||
log_create_filename (const char *channame)
|
||||
log_create_filename (char *channame)
|
||||
{
|
||||
char *tmp, *ret;
|
||||
int mbl;
|
||||
@@ -453,7 +453,7 @@ log_escape_strcpy (char *dest, char *src, char *end)
|
||||
/* substitutes %c %n %s into buffer */
|
||||
|
||||
static void
|
||||
log_insert_vars (char *buf, int bufsize, const char *fmt, char *c, char *n, char *s)
|
||||
log_insert_vars (char *buf, int bufsize, char *fmt, char *c, char *n, char *s)
|
||||
{
|
||||
char *end = buf + bufsize;
|
||||
|
||||
@@ -466,14 +466,6 @@ log_insert_vars (char *buf, int bufsize, const char *fmt, char *c, char *n, char
|
||||
return;
|
||||
|
||||
case '%':
|
||||
/* A partially typed custom format can end in a literal percent. */
|
||||
if (!fmt[1])
|
||||
{
|
||||
buf[0] = '%';
|
||||
if (buf + 1 < end) buf[1] = 0;
|
||||
else buf[0] = 0;
|
||||
return;
|
||||
}
|
||||
fmt++;
|
||||
switch (fmt[0])
|
||||
{
|
||||
@@ -487,11 +479,6 @@ log_insert_vars (char *buf, int bufsize, const char *fmt, char *c, char *n, char
|
||||
buf = log_escape_strcpy (buf, s, end);
|
||||
break;
|
||||
default:
|
||||
if (buf + 1 == end)
|
||||
{
|
||||
buf[0] = 0;
|
||||
return;
|
||||
}
|
||||
buf[0] = '%';
|
||||
buf++;
|
||||
buf[0] = fmt[0];
|
||||
@@ -513,72 +500,59 @@ log_insert_vars (char *buf, int bufsize, const char *fmt, char *c, char *n, char
|
||||
}
|
||||
}
|
||||
|
||||
char *
|
||||
log_build_pathname (const char *mask, const char *config_dir,
|
||||
const char *servname, const char *channame,
|
||||
const char *netname, time_t now)
|
||||
static char *
|
||||
log_create_pathname (char *servname, char *channame, char *netname)
|
||||
{
|
||||
char fname[384];
|
||||
char fnametime[384];
|
||||
char *safe_channel, *safe_network, *safe_server;
|
||||
|
||||
if (!mask || !*mask || !config_dir || !servname || !channame ||
|
||||
!g_utf8_validate (mask, -1, NULL))
|
||||
return NULL;
|
||||
time_t now;
|
||||
|
||||
if (!netname)
|
||||
{
|
||||
safe_network = g_strdup ("NETWORK");
|
||||
netname = g_strdup ("NETWORK");
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_network = log_create_filename (netname);
|
||||
netname = log_create_filename (netname);
|
||||
}
|
||||
|
||||
/* first, everything is in UTF-8 */
|
||||
if (!rfc_casecmp (channame, servname))
|
||||
{
|
||||
safe_channel = g_strdup ("server");
|
||||
channame = g_strdup ("server");
|
||||
}
|
||||
else
|
||||
{
|
||||
safe_channel = log_create_filename (channame);
|
||||
channame = log_create_filename (channame);
|
||||
}
|
||||
|
||||
safe_server = log_create_filename (servname);
|
||||
servname = log_create_filename (servname);
|
||||
|
||||
log_insert_vars (fname, sizeof (fname), mask, safe_channel, safe_network, safe_server);
|
||||
g_free (safe_channel);
|
||||
g_free (safe_network);
|
||||
g_free (safe_server);
|
||||
log_insert_vars (fname, sizeof (fname), prefs.hex_irc_logmask, channame, netname, servname);
|
||||
g_free (channame);
|
||||
g_free (netname);
|
||||
g_free (servname);
|
||||
|
||||
/* insert time/date */
|
||||
if (!g_utf8_validate (fname, -1, NULL) ||
|
||||
!strftime_utf8 (fnametime, sizeof (fnametime), fname, now))
|
||||
return NULL;
|
||||
now = time (NULL);
|
||||
strftime_utf8 (fnametime, sizeof (fnametime), fname, now);
|
||||
|
||||
/* If one uses log mask variables, such as "%c/...", %c will be empty upon
|
||||
* connecting since there's no channel name yet, so we have to make sure
|
||||
* we won't try to write to the FS root. */
|
||||
if (g_path_is_absolute (mask))
|
||||
if (g_path_is_absolute (prefs.hex_irc_logmask))
|
||||
{
|
||||
g_snprintf (fname, sizeof (fname), "%s", fnametime);
|
||||
}
|
||||
else /* relative path */
|
||||
{
|
||||
g_snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", config_dir, fnametime);
|
||||
g_snprintf (fname, sizeof (fname), "%s" G_DIR_SEPARATOR_S "logs" G_DIR_SEPARATOR_S "%s", get_xdir (), fnametime);
|
||||
}
|
||||
|
||||
return g_strdup (fname);
|
||||
}
|
||||
/* create all the subdirectories */
|
||||
mkdir_p (fname);
|
||||
|
||||
static char *
|
||||
log_create_pathname (char *servname, char *channame, char *netname)
|
||||
{
|
||||
char *filename = log_build_pathname (prefs.hex_irc_logmask, get_xdir (),
|
||||
servname, channame, netname, time (NULL));
|
||||
if (filename) mkdir_p (filename);
|
||||
return filename;
|
||||
return g_strdup (fname);
|
||||
}
|
||||
|
||||
static int
|
||||
|
||||
@@ -47,11 +47,6 @@ void PrintTextf (session *sess, const char *format, ...) G_GNUC_PRINTF (2, 3);
|
||||
void PrintTextTimeStampf (session *sess, time_t timestamp, const char *format, ...) G_GNUC_PRINTF (3, 4);
|
||||
void log_close (session *sess);
|
||||
void log_open_or_close (session *sess);
|
||||
/* Resolve a log filename without creating directories or changing preferences.
|
||||
* The caller owns the UTF-8 result; NULL means the format could not be expanded. */
|
||||
char *log_build_pathname (const char *mask, const char *config_dir,
|
||||
const char *servname, const char *channame,
|
||||
const char *netname, time_t now);
|
||||
void load_text_events (void);
|
||||
void pevent_save (char *fn);
|
||||
int pevt_build_string (const char *input, char **output, int *max_arg);
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "plugin.h"
|
||||
#include "plugin-identd.h"
|
||||
#include "plugin-timer.h"
|
||||
#include "autoaway.h"
|
||||
#include "notify.h"
|
||||
#include "server.h"
|
||||
#include "servlist.h"
|
||||
@@ -499,6 +500,8 @@ zoitechat_reinit_timers (void)
|
||||
static int lag_check_tag = 0;
|
||||
static int away_tag = 0;
|
||||
|
||||
autoaway_reinit ();
|
||||
|
||||
if (prefs.hex_notify_timeout && notify_tag == 0)
|
||||
{
|
||||
notify_tag = fe_timeout_add_seconds (prefs.hex_notify_timeout,
|
||||
|
||||
@@ -105,6 +105,7 @@ struct zoitechatprefs
|
||||
/* these are the rebranded, consistent, sorted zoitechat variables */
|
||||
|
||||
/* BOOLEANS */
|
||||
unsigned int hex_away_auto_system;
|
||||
unsigned int hex_away_auto_unmark;
|
||||
unsigned int hex_away_omit_alerts;
|
||||
unsigned int hex_away_show_once;
|
||||
@@ -233,6 +234,7 @@ struct zoitechatprefs
|
||||
unsigned int hex_url_logging;
|
||||
|
||||
/* NUMBERS */
|
||||
int hex_away_auto_timeout;
|
||||
int hex_away_size_max;
|
||||
int hex_away_timeout;
|
||||
int hex_completion_amount;
|
||||
@@ -311,6 +313,7 @@ struct zoitechatprefs
|
||||
int hex_url_grabber_limit;
|
||||
|
||||
/* STRINGS */
|
||||
char hex_away_auto_reason[256];
|
||||
char hex_away_reason[256];
|
||||
char hex_completion_suffix[4]; /* Only ever holds a one-character string. */
|
||||
char hex_dcc_completed_dir[PATHLEN + 1];
|
||||
@@ -597,6 +600,7 @@ typedef struct server
|
||||
/*time_t connect_time;*/ /* when did it connect? */
|
||||
unsigned long lag_sent; /* we are still waiting for this ping response*/
|
||||
time_t ping_recv; /* when we last got a ping reply */
|
||||
gint64 autoaway_sent_at;
|
||||
time_t away_time; /* when we were marked away */
|
||||
|
||||
char *encoding;
|
||||
@@ -616,6 +620,7 @@ typedef struct server
|
||||
unsigned int end_of_motd:1; /* end of motd reached (logged in) */
|
||||
unsigned int sent_quit:1; /* sent a QUIT already? */
|
||||
unsigned int use_listargs:1; /* undernet and dalnet need /list >0,<10000 */
|
||||
unsigned int autoaway_owned:1;
|
||||
unsigned int is_away:1;
|
||||
unsigned int reconnect_away:1; /* whether to reconnect in is_away state */
|
||||
unsigned int dont_use_proxy:1; /* to proxy or not to proxy */
|
||||
|
||||
@@ -41,8 +41,6 @@
|
||||
#include "gtkutil.h"
|
||||
#include "maingui.h"
|
||||
#include "editlist.h"
|
||||
#include "preferences-persistence.h"
|
||||
#include "setup.h"
|
||||
|
||||
#define ICON_EDITLIST_NEW "document-new"
|
||||
#define ICON_EDITLIST_DELETE "edit-delete"
|
||||
@@ -375,301 +373,3 @@ editlist_gui_open (char *title1, char *title2, GSList *list, char *title, char *
|
||||
|
||||
gtk_widget_show (editlist_win);
|
||||
}
|
||||
|
||||
/* Preferences editors are independent instances: several lists may be open
|
||||
* at once, and their models must never share the legacy dialog globals. */
|
||||
typedef struct
|
||||
{
|
||||
GtkWidget *view;
|
||||
GtkWidget *delete_button;
|
||||
GtkWidget *up_button;
|
||||
GtkWidget *down_button;
|
||||
GSList **target;
|
||||
const char *file;
|
||||
char *original;
|
||||
char *pending;
|
||||
} PreferencesList;
|
||||
|
||||
static GSList *preferences_lists;
|
||||
|
||||
static void
|
||||
preferences_list_update_buttons (PreferencesList *editor)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter, next;
|
||||
GtkTreePath *path;
|
||||
gboolean selected = gtk_tree_selection_get_selected (
|
||||
gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter);
|
||||
gboolean up = FALSE, down = FALSE;
|
||||
if (selected)
|
||||
{
|
||||
path = gtk_tree_model_get_path (model, &iter);
|
||||
up = gtk_tree_path_get_indices (path)[0] > 0;
|
||||
gtk_tree_path_free (path);
|
||||
next = iter;
|
||||
down = gtk_tree_model_iter_next (model, &next);
|
||||
}
|
||||
gtk_widget_set_sensitive (editor->delete_button, selected);
|
||||
gtk_widget_set_sensitive (editor->up_button, up);
|
||||
gtk_widget_set_sensitive (editor->down_button, down);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_selection_changed (GtkTreeSelection *selection, PreferencesList *editor)
|
||||
{
|
||||
preferences_list_update_buttons (editor);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_rows_reordered (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
|
||||
gint *order, PreferencesList *editor)
|
||||
{
|
||||
preferences_list_update_buttons (editor);
|
||||
}
|
||||
|
||||
static char *
|
||||
preferences_list_serialize (GSList *list)
|
||||
{
|
||||
GString *text = g_string_new ("");
|
||||
for (; list; list = list->next)
|
||||
{
|
||||
struct popup *item = list->data;
|
||||
g_string_append_printf (text, "NAME %s\nCMD %s\n\n", item->name, item->cmd);
|
||||
}
|
||||
return g_string_free (text, FALSE);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_free (gpointer data)
|
||||
{
|
||||
PreferencesList *editor = data;
|
||||
preferences_lists = g_slist_remove (preferences_lists, editor);
|
||||
g_free (editor->original);
|
||||
g_free (editor->pending);
|
||||
g_free (editor);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_edited (GtkCellRenderer *renderer, char *path, char *text, PreferencesList *editor)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view));
|
||||
if (gtk_tree_model_get_iter_from_string (model, &iter, path))
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||
GPOINTER_TO_INT (g_object_get_data (G_OBJECT (renderer), "column")), text, -1);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_add (GtkButton *button, PreferencesList *editor)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GtkTreePath *path;
|
||||
GtkTreeView *view = GTK_TREE_VIEW (editor->view);
|
||||
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
||||
(void)button;
|
||||
gtk_list_store_insert_with_values (store, &iter, -1, NAME_COLUMN, "", CMD_COLUMN, "", -1);
|
||||
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
||||
gtk_tree_view_set_cursor (view, path, gtk_tree_view_get_column (view, 0), TRUE);
|
||||
gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0, 0);
|
||||
gtk_tree_path_free (path);
|
||||
preferences_list_update_buttons (editor);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_delete (GtkButton *button, PreferencesList *editor)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GtkTreeModel *model;
|
||||
(void)button;
|
||||
if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter))
|
||||
gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
|
||||
}
|
||||
|
||||
static void
|
||||
preferences_list_move (GtkButton *button, PreferencesList *editor)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter, other;
|
||||
GtkTreePath *path;
|
||||
gboolean valid;
|
||||
if (!gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), &model, &iter))
|
||||
return;
|
||||
path = gtk_tree_model_get_path (model, &iter);
|
||||
if (GPOINTER_TO_INT (g_object_get_data (G_OBJECT (button), "direction")) < 0)
|
||||
valid = gtk_tree_path_prev (path);
|
||||
else { gtk_tree_path_next (path); valid = TRUE; }
|
||||
if (valid && gtk_tree_model_get_iter (model, &other, path))
|
||||
{
|
||||
gtk_list_store_swap (GTK_LIST_STORE (model), &iter, &other);
|
||||
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (editor->view), path, NULL, FALSE, 0, 0);
|
||||
}
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preferences_list_key (GtkWidget *view, GdkEventKey *event, PreferencesList *editor)
|
||||
{
|
||||
GtkWidget *button;
|
||||
int direction;
|
||||
(void)view;
|
||||
if (!(event->state & GDK_SHIFT_MASK) || (event->keyval != GDK_KEY_Up && event->keyval != GDK_KEY_Down))
|
||||
return FALSE;
|
||||
direction = event->keyval == GDK_KEY_Up ? -1 : 1;
|
||||
button = gtk_button_new ();
|
||||
g_object_ref_sink (button);
|
||||
g_object_set_data (G_OBJECT (button), "direction", GINT_TO_POINTER (direction));
|
||||
preferences_list_move (GTK_BUTTON (button), editor);
|
||||
g_object_unref (button);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
editlist_preferences_create (GSList **list, const char *file, const char *title1,
|
||||
const char *title2, const char *help)
|
||||
{
|
||||
PreferencesList *editor = g_new0 (PreferencesList, 1);
|
||||
GtkWidget *box, *scroll, *buttons, *button;
|
||||
GtkListStore *store;
|
||||
GtkCellRenderer *renderer;
|
||||
int column;
|
||||
box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
editor->target = list;
|
||||
editor->file = file;
|
||||
editor->original = preferences_list_serialize (*list);
|
||||
preferences_lists = g_slist_append (preferences_lists, editor);
|
||||
g_object_set_data_full (G_OBJECT (box), "preferences-list", editor, preferences_list_free);
|
||||
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING);
|
||||
editor->view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
||||
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (editor->view), TRUE);
|
||||
gtk_tree_view_set_search_column (GTK_TREE_VIEW (editor->view), NAME_COLUMN);
|
||||
for (column = 0; column < N_COLUMNS; column++)
|
||||
{
|
||||
GtkTreeViewColumn *col;
|
||||
renderer = gtk_cell_renderer_text_new ();
|
||||
g_object_set (renderer, "editable", TRUE, NULL);
|
||||
g_object_set_data (G_OBJECT (renderer), "column", GINT_TO_POINTER (column));
|
||||
g_signal_connect (renderer, "edited", G_CALLBACK (preferences_list_edited), editor);
|
||||
col = gtk_tree_view_column_new_with_attributes (column ? title2 : title1, renderer, "text", column, NULL);
|
||||
gtk_tree_view_column_set_resizable (col, TRUE);
|
||||
gtk_tree_view_column_set_min_width (col, column ? 240 : 140);
|
||||
gtk_tree_view_column_set_expand (col, column != 0);
|
||||
gtk_tree_view_append_column (GTK_TREE_VIEW (editor->view), col);
|
||||
}
|
||||
editlist_load (store, *list);
|
||||
g_object_unref (store);
|
||||
g_signal_connect (editor->view, "key-press-event", G_CALLBACK (preferences_list_key), editor);
|
||||
scroll = gtk_scrolled_window_new (NULL, NULL);
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scroll), 220);
|
||||
gtk_container_add (GTK_CONTAINER (scroll), editor->view);
|
||||
gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0);
|
||||
buttons = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 8);
|
||||
gtk_box_pack_start (GTK_BOX (box), buttons, FALSE, FALSE, 0);
|
||||
button = gtk_button_new_with_mnemonic (_("_Add"));
|
||||
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_add), editor);
|
||||
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
|
||||
button = gtk_button_new_with_mnemonic (_("_Delete"));
|
||||
editor->delete_button = button;
|
||||
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_delete), editor);
|
||||
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
|
||||
for (column = -1; column <= 1; column += 2)
|
||||
{
|
||||
button = gtk_button_new_with_label (column < 0 ? _("Move up") : _("Move down"));
|
||||
if (column < 0) editor->up_button = button;
|
||||
else editor->down_button = button;
|
||||
g_object_set_data (G_OBJECT (button), "direction", GINT_TO_POINTER (column));
|
||||
g_signal_connect (button, "clicked", G_CALLBACK (preferences_list_move), editor);
|
||||
gtk_box_pack_start (GTK_BOX (buttons), button, FALSE, FALSE, 0);
|
||||
}
|
||||
g_signal_connect (gtk_tree_view_get_selection (GTK_TREE_VIEW (editor->view)), "changed",
|
||||
G_CALLBACK (preferences_list_selection_changed), editor);
|
||||
g_signal_connect (gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view)), "rows-reordered",
|
||||
G_CALLBACK (preferences_list_rows_reordered), editor);
|
||||
preferences_list_update_buttons (editor);
|
||||
if (help)
|
||||
{
|
||||
GtkWidget *expander = gtk_expander_new (_("Formatting codes and examples"));
|
||||
GtkWidget *label = gtk_label_new (help);
|
||||
gtk_label_set_xalign (GTK_LABEL (label), 0);
|
||||
gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
|
||||
gtk_label_set_selectable (GTK_LABEL (label), TRUE);
|
||||
gtk_label_set_max_width_chars (GTK_LABEL (label), 60);
|
||||
gtk_widget_set_margin_top (label, 10);
|
||||
gtk_container_add (GTK_CONTAINER (expander), label);
|
||||
gtk_box_pack_start (GTK_BOX (box), expander, FALSE, FALSE, 0);
|
||||
}
|
||||
return box;
|
||||
}
|
||||
|
||||
gboolean
|
||||
editlist_preferences_stage (GError **error)
|
||||
{
|
||||
GSList *entry;
|
||||
for (entry = preferences_lists; entry; entry = entry->next)
|
||||
{
|
||||
PreferencesList *editor = entry->data;
|
||||
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (editor->view));
|
||||
GtkTreeIter iter;
|
||||
GString *text = g_string_new ("");
|
||||
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||
char *live;
|
||||
g_clear_pointer (&editor->pending, g_free);
|
||||
while (valid)
|
||||
{
|
||||
char *name, *cmd;
|
||||
gtk_tree_model_get (model, &iter, NAME_COLUMN, &name, CMD_COLUMN, &cmd, -1);
|
||||
if ((name && strpbrk (name, "\r\n")) || (cmd && strpbrk (cmd, "\r\n")) ||
|
||||
((!name || !*name) && cmd && *cmd))
|
||||
{
|
||||
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
|
||||
int column = !name || !*name || strpbrk (name, "\r\n") ? NAME_COLUMN : CMD_COLUMN;
|
||||
setup_reveal_widget (editor->view);
|
||||
gtk_widget_grab_focus (editor->view);
|
||||
gtk_tree_view_set_cursor (GTK_TREE_VIEW (editor->view), path,
|
||||
gtk_tree_view_get_column (GTK_TREE_VIEW (editor->view), column), TRUE);
|
||||
gtk_tree_path_free (path);
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||
_("Give this entry a name and keep each field on one line."));
|
||||
g_free (name); g_free (cmd); g_string_free (text, TRUE);
|
||||
return FALSE;
|
||||
}
|
||||
if (name && *name)
|
||||
g_string_append_printf (text, "NAME %s\nCMD %s\n\n", name, cmd ? cmd : "");
|
||||
g_free (name); g_free (cmd);
|
||||
valid = gtk_tree_model_iter_next (model, &iter);
|
||||
}
|
||||
if (strcmp (text->str, editor->original) == 0) { g_string_free (text, TRUE); continue; }
|
||||
live = preferences_list_serialize (*editor->target);
|
||||
if (strcmp (live, editor->original) != 0)
|
||||
{
|
||||
setup_reveal_widget (editor->view);
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_AGAIN,
|
||||
_("These entries changed outside Preferences. Reopen Preferences before editing them."));
|
||||
g_free (live); g_string_free (text, TRUE);
|
||||
return FALSE;
|
||||
}
|
||||
g_free (live);
|
||||
editor->pending = g_string_free (text, FALSE);
|
||||
preferences_persistence_stage_file (editor->file, editor->pending);
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
editlist_preferences_commit (void)
|
||||
{
|
||||
GSList *entry, *sessions;
|
||||
for (entry = preferences_lists; entry; entry = entry->next)
|
||||
{
|
||||
PreferencesList *editor = entry->data;
|
||||
if (!editor->pending) continue;
|
||||
list_free (editor->target);
|
||||
list_loadconf ((char *)editor->file, editor->target, NULL);
|
||||
if (editor->target == &usermenu_list) usermenu_update ();
|
||||
for (sessions = sess_list; sessions; sessions = sessions->next)
|
||||
{
|
||||
if (editor->target == &button_list) fe_buttons_update (sessions->data);
|
||||
if (editor->target == &dlgbutton_list) fe_dlgbuttons_update (sessions->data);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,10 +21,5 @@
|
||||
#define ZOITECHAT_EDITLIST_H
|
||||
|
||||
void editlist_gui_open (char *title1, char *title2, GSList * list, char *title, char *wmclass, char *file, char *help);
|
||||
GtkWidget *editlist_preferences_create (GSList **list, const char *file,
|
||||
const char *title1, const char *title2,
|
||||
const char *help);
|
||||
gboolean editlist_preferences_stage (GError **error);
|
||||
void editlist_preferences_commit (void);
|
||||
|
||||
#endif
|
||||
@@ -59,6 +59,7 @@
|
||||
#include "notifygui.h"
|
||||
#include "textgui.h"
|
||||
#include "fkeys.h"
|
||||
#include "idle.h"
|
||||
#include "plugin-tray.h"
|
||||
#include "urlgrab.h"
|
||||
#include "setup.h"
|
||||
@@ -638,6 +639,7 @@ fe_init (void)
|
||||
{
|
||||
theme_manager_init ();
|
||||
key_init ();
|
||||
idle_init ();
|
||||
pixmaps_init ();
|
||||
|
||||
#ifdef HAVE_GTK_MAC
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<AdditionalLibraryDirectories>$(ArchiveLibDir);$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;pangoft2-1.0.lib;fontconfig.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>$(DepLibs);$(ZoiteChatLib)common.lib;wbemuuid.lib;dwmapi.lib;user32.lib;pangoft2-1.0.lib;fontconfig.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<EntryPointSymbol>mainCRTStartup</EntryPointSymbol>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
@@ -49,6 +49,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="idle.h" />
|
||||
<ClInclude Include="ascii.h" />
|
||||
<ClInclude Include="banlist.h" />
|
||||
<ClInclude Include="chanlist.h" />
|
||||
@@ -63,7 +64,6 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
||||
<ClInclude Include="joind.h" />
|
||||
<ClInclude Include="maingui.h" />
|
||||
<ClInclude Include="preferences-persistence.h" />
|
||||
<ClInclude Include="preferences-pages.h" />
|
||||
<ClInclude Include="menu.h" />
|
||||
<ClInclude Include="notifications\notification-backend.h" />
|
||||
<ClInclude Include="notifygui.h" />
|
||||
@@ -92,6 +92,7 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
||||
<ClInclude Include="theme\theme-preferences.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="idle.c" />
|
||||
<ClCompile Include="ascii.c" />
|
||||
<ClCompile Include="banlist.c" />
|
||||
<ClCompile Include="chanlist.c" />
|
||||
@@ -114,7 +115,6 @@ powershell "Get-Content -Encoding UTF8 '$(ZoiteChatLib)zoitechat.rc.utf8' | Out-
|
||||
<ClCompile Include="notifygui.c" />
|
||||
<ClCompile Include="pixmaps.c" />
|
||||
<ClCompile Include="preferences-persistence.c" />
|
||||
<ClCompile Include="preferences-pages.c" />
|
||||
<ClCompile Include="plugin-notification.c" />
|
||||
<ClCompile Include="plugin-tray.c" />
|
||||
<ClCompile Include="plugingui.c" />
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="idle.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ascii.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@@ -57,9 +60,6 @@
|
||||
<ClInclude Include="preferences-persistence.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="preferences-pages.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="menu.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
@@ -140,6 +140,9 @@
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="idle.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ascii.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
@@ -197,9 +200,6 @@
|
||||
<ClCompile Include="preferences-persistence.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="preferences-pages.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="plugingui.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
|
||||
+670
-141
@@ -58,9 +58,6 @@
|
||||
#ifdef __APPLE__
|
||||
#include "macos-shortcuts.h"
|
||||
#endif
|
||||
#include "setup.h"
|
||||
#include "preferences-persistence.h"
|
||||
#include "preferences-pages.h"
|
||||
|
||||
#define ICON_FKEYS_NEW "document-new"
|
||||
#define ICON_FKEYS_DELETE "edit-delete"
|
||||
@@ -113,7 +110,7 @@ struct gcomp_data
|
||||
|
||||
static int key_load_kbs (void);
|
||||
static int key_load_kbs_from_buffer (char *ibuf, off_t size, GSList **out_list);
|
||||
|
||||
static int key_save_kbs (void);
|
||||
static void key_dialog_load (GtkListStore *store);
|
||||
static void key_dialog_reset (GtkWidget *wid, gpointer userdata);
|
||||
static int key_action_handle_command (GtkWidget * wid, GdkEventKey * evt,
|
||||
@@ -162,6 +159,53 @@ static int key_action_reopen_closed_tab (GtkWidget * wid, GdkEventKey * evt,
|
||||
|
||||
static GSList *keybind_list = NULL;
|
||||
|
||||
#define KEY_DIALOG_THEME_LISTENER_ID_KEY "fkeys.theme-listener-id"
|
||||
|
||||
static void
|
||||
key_dialog_theme_apply (GtkWidget *window)
|
||||
{
|
||||
GtkWidget *xtext;
|
||||
XTextColor xtext_palette[XTEXT_COLS];
|
||||
|
||||
if (!window)
|
||||
return;
|
||||
|
||||
xtext = g_object_get_data (G_OBJECT (window), "xtext");
|
||||
if (!xtext)
|
||||
return;
|
||||
|
||||
theme_get_xtext_colors (xtext_palette, XTEXT_COLS);
|
||||
gtk_xtext_set_palette (GTK_XTEXT (xtext), xtext_palette);
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_theme_changed (const ThemeChangedEvent *event, gpointer userdata)
|
||||
{
|
||||
GtkWidget *window = userdata;
|
||||
|
||||
if (!theme_changed_event_has_reason (event, THEME_CHANGED_REASON_PALETTE) &&
|
||||
!theme_changed_event_has_reason (event, THEME_CHANGED_REASON_THEME_PACK) &&
|
||||
!theme_changed_event_has_reason (event, THEME_CHANGED_REASON_MODE))
|
||||
return;
|
||||
|
||||
key_dialog_theme_apply (window);
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_theme_destroy_cb (GtkWidget *widget, gpointer userdata)
|
||||
{
|
||||
guint listener_id;
|
||||
|
||||
(void) userdata;
|
||||
|
||||
listener_id = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (widget), KEY_DIALOG_THEME_LISTENER_ID_KEY));
|
||||
if (listener_id)
|
||||
{
|
||||
theme_listener_unregister (listener_id);
|
||||
g_object_set_data (G_OBJECT (widget), KEY_DIALOG_THEME_LISTENER_ID_KEY, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static gsize
|
||||
key_action_decode_escapes (const char *input, char *output)
|
||||
{
|
||||
@@ -321,6 +365,69 @@ static const struct key_action key_actions[KEY_MAX_ACTIONS + 1] = {
|
||||
"ACCEL=F1\nMenu Shortcut\nD1:contents\nD2!\n\n"\
|
||||
"ACCEL=<Primary><Shift>T\nReopen Closed Tab\nD1!\nD2!\n\n"
|
||||
|
||||
static gboolean
|
||||
key_builtin_data_match (char *line, char *data)
|
||||
{
|
||||
if (line[2] == '!')
|
||||
return data == NULL || data[0] == 0;
|
||||
|
||||
if (line[2] == ':')
|
||||
return !strcmp (&line[3], data ? data : "");
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
key_binding_is_builtin (struct key_binding *kb)
|
||||
{
|
||||
char *buf, *ibuf;
|
||||
char *action;
|
||||
int pnt = 0;
|
||||
int state = 0;
|
||||
gboolean match = FALSE;
|
||||
gboolean d1_match = FALSE;
|
||||
off_t size;
|
||||
|
||||
if (kb->action < 0 || kb->action > KEY_MAX_ACTIONS)
|
||||
return FALSE;
|
||||
|
||||
action = key_actions[kb->action].name;
|
||||
ibuf = g_strdup (default_kb_cfg);
|
||||
size = strlen (default_kb_cfg);
|
||||
|
||||
while (buf_get_line (ibuf, &buf, &pnt, size))
|
||||
{
|
||||
if (strlen (buf) == 0)
|
||||
continue;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case 0:
|
||||
state = 1;
|
||||
break;
|
||||
case 1:
|
||||
match = !strcmp (buf, action);
|
||||
state = 2;
|
||||
break;
|
||||
case 2:
|
||||
d1_match = match && key_builtin_data_match (buf, kb->data1);
|
||||
state = 3;
|
||||
break;
|
||||
case 3:
|
||||
if (d1_match && key_builtin_data_match (buf, kb->data2))
|
||||
{
|
||||
g_free (ibuf);
|
||||
return TRUE;
|
||||
}
|
||||
state = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
g_free (ibuf);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
void
|
||||
key_init (void)
|
||||
{
|
||||
@@ -512,13 +619,13 @@ key_get_menu_accel (const char *name, guint *keyval, GdkModifierType *mod)
|
||||
|
||||
enum
|
||||
{
|
||||
KEY_COLUMN = PREFERENCES_SHORTCUT_LABEL,
|
||||
ACCEL_COLUMN = PREFERENCES_SHORTCUT_ACCEL,
|
||||
ACTION_COLUMN = PREFERENCES_SHORTCUT_ACTION,
|
||||
D1_COLUMN = PREFERENCES_SHORTCUT_DATA1,
|
||||
D2_COLUMN = PREFERENCES_SHORTCUT_DATA2,
|
||||
CUSTOM_COLUMN = PREFERENCES_SHORTCUT_CUSTOM,
|
||||
N_COLUMNS = PREFERENCES_SHORTCUT_N_COLUMNS
|
||||
KEY_COLUMN,
|
||||
ACCEL_COLUMN,
|
||||
ACTION_COLUMN,
|
||||
D1_COLUMN,
|
||||
D2_COLUMN,
|
||||
CUSTOM_COLUMN,
|
||||
N_COLUMNS
|
||||
};
|
||||
|
||||
static GtkWidget *key_dialog = NULL;
|
||||
@@ -526,15 +633,280 @@ static GtkWidget *key_dialog = NULL;
|
||||
static inline GtkTreeModel *
|
||||
get_store (void)
|
||||
{
|
||||
return g_object_get_data (G_OBJECT (key_dialog), "bindings-store");
|
||||
return gtk_tree_view_get_model (g_object_get_data (G_OBJECT (key_dialog), "view"));
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_print_text (GtkXText *xtext, char *text)
|
||||
{
|
||||
unsigned int old = prefs.hex_stamp_text;
|
||||
prefs.hex_stamp_text = 0; /* temporarily disable stamps */
|
||||
gtk_xtext_clear (GTK_XTEXT (xtext)->buffer, 0);
|
||||
PrintTextRaw (GTK_XTEXT (xtext)->buffer, text, 0, 0);
|
||||
prefs.hex_stamp_text = old;
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_set_key (GtkCellRendererAccel *accel, gchar *pathstr, guint accel_key,
|
||||
GdkModifierType accel_mods, guint hardware_keycode, gpointer userdata)
|
||||
{
|
||||
GtkTreeModel *model = get_store ();
|
||||
GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
|
||||
GtkTreeIter iter;
|
||||
gchar *label_name, *accel_name;
|
||||
|
||||
/* Shift tab requires an exception, hopefully that list ends here.. */
|
||||
if (accel_key == GDK_KEY_Tab && accel_mods & GDK_SHIFT_MASK)
|
||||
accel_key = GDK_KEY_ISO_Left_Tab;
|
||||
|
||||
label_name = gtk_accelerator_get_label (accel_key, key_modifier_get_valid (accel_mods));
|
||||
accel_name = gtk_accelerator_name (accel_key, key_modifier_get_valid (accel_mods));
|
||||
|
||||
gtk_tree_model_get_iter (model, &iter, path);
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter, KEY_COLUMN, label_name,
|
||||
ACCEL_COLUMN, accel_name, -1);
|
||||
|
||||
gtk_tree_path_free (path);
|
||||
g_free (label_name);
|
||||
g_free (accel_name);
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_combo_changed (GtkCellRendererCombo *combo, gchar *pathstr,
|
||||
GtkTreeIter *new_iter, gpointer data)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkXText *xtext;
|
||||
gchar *actiontext = NULL;
|
||||
gint action;
|
||||
|
||||
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
||||
model = GTK_TREE_MODEL (data);
|
||||
|
||||
gtk_tree_model_get (model, new_iter, 0, &actiontext, -1);
|
||||
|
||||
if (actiontext)
|
||||
{
|
||||
#ifdef WIN32
|
||||
/* We need to manually update the store */
|
||||
GtkTreePath *path;
|
||||
GtkTreeIter iter;
|
||||
|
||||
path = gtk_tree_path_new_from_string (pathstr);
|
||||
model = get_store ();
|
||||
|
||||
gtk_tree_model_get_iter (model, &iter, path);
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter, ACTION_COLUMN, actiontext, -1);
|
||||
|
||||
gtk_tree_path_free (path);
|
||||
#endif
|
||||
|
||||
action = key_get_action_from_string (actiontext);
|
||||
key_dialog_print_text (xtext, key_actions[action].help);
|
||||
|
||||
g_free (actiontext);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_entry_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpointer data)
|
||||
{
|
||||
GtkTreeModel *model = get_store ();
|
||||
GtkTreePath *path = gtk_tree_path_new_from_string (pathstr);
|
||||
GtkTreeIter iter;
|
||||
gint column = GPOINTER_TO_INT (data);
|
||||
|
||||
gtk_tree_model_get_iter (model, &iter, path);
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter, column, new_text, -1);
|
||||
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
key_dialog_keypress (GtkWidget *wid, GdkEventKey *evt, gpointer userdata)
|
||||
{
|
||||
GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
|
||||
GtkTreeModel *store;
|
||||
GtkTreeIter iter1, iter2;
|
||||
GtkTreeSelection *sel;
|
||||
GtkTreePath *path;
|
||||
gboolean handled = FALSE;
|
||||
int delta;
|
||||
|
||||
if (evt->state & GDK_SHIFT_MASK)
|
||||
{
|
||||
if (evt->keyval == GDK_KEY_Up)
|
||||
{
|
||||
handled = TRUE;
|
||||
delta = -1;
|
||||
}
|
||||
else if (evt->keyval == GDK_KEY_Down)
|
||||
{
|
||||
handled = TRUE;
|
||||
delta = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (handled)
|
||||
{
|
||||
gboolean custom1, custom2;
|
||||
|
||||
sel = gtk_tree_view_get_selection (view);
|
||||
if (!gtk_tree_selection_get_selected (sel, &store, &iter1))
|
||||
return FALSE;
|
||||
|
||||
path = gtk_tree_model_get_path (store, &iter1);
|
||||
if (delta == 1)
|
||||
gtk_tree_path_next (path);
|
||||
else if (!gtk_tree_path_prev (path))
|
||||
{
|
||||
gtk_tree_path_free (path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (!gtk_tree_model_get_iter (store, &iter2, path))
|
||||
{
|
||||
gtk_tree_path_free (path);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gtk_tree_path_free (path);
|
||||
gtk_tree_model_get (store, &iter1, CUSTOM_COLUMN, &custom1, -1);
|
||||
gtk_tree_model_get (store, &iter2, CUSTOM_COLUMN, &custom2, -1);
|
||||
if (custom1 && custom2)
|
||||
gtk_list_store_swap (GTK_LIST_STORE (store), &iter1, &iter2);
|
||||
}
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_selection_changed (GtkTreeSelection *sel, gpointer userdata)
|
||||
{
|
||||
GtkTreeModel *model;
|
||||
GtkTreeIter iter;
|
||||
GtkXText *xtext;
|
||||
GtkWidget *delete_button;
|
||||
char *actiontext;
|
||||
gboolean custom;
|
||||
int action;
|
||||
|
||||
delete_button = g_object_get_data (G_OBJECT (key_dialog), "delete_button");
|
||||
if (!gtk_tree_selection_get_selected (sel, &model, &iter) || model == NULL)
|
||||
{
|
||||
if (delete_button)
|
||||
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||
return;
|
||||
}
|
||||
|
||||
xtext = GTK_XTEXT (g_object_get_data (G_OBJECT (key_dialog), "xtext"));
|
||||
gtk_tree_model_get (model, &iter, ACTION_COLUMN, &actiontext, CUSTOM_COLUMN, &custom, -1);
|
||||
if (delete_button)
|
||||
gtk_widget_set_sensitive (delete_button, custom);
|
||||
|
||||
if (actiontext)
|
||||
{
|
||||
action = key_get_action_from_string (actiontext);
|
||||
key_dialog_print_text (xtext, key_actions[action].help);
|
||||
g_free (actiontext);
|
||||
}
|
||||
else
|
||||
key_dialog_print_text (xtext, _("Select a row to get help information on its Action."));
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_close (GtkWidget *wid, gpointer userdata)
|
||||
{
|
||||
gtk_widget_destroy (key_dialog);
|
||||
key_dialog = NULL;
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_save (GtkWidget *wid, gpointer userdata)
|
||||
{
|
||||
GtkTreeModel *store = get_store ();
|
||||
GtkTreeIter iter;
|
||||
struct key_binding *kb;
|
||||
char *data1, *data2, *accel, *actiontext;
|
||||
guint keyval;
|
||||
GdkModifierType mod;
|
||||
|
||||
if (keybind_list)
|
||||
{
|
||||
g_slist_free_full (keybind_list, key_free);
|
||||
keybind_list = NULL;
|
||||
}
|
||||
|
||||
if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
|
||||
{
|
||||
do
|
||||
{
|
||||
kb = g_new0 (struct key_binding, 1);
|
||||
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, ACCEL_COLUMN, &accel,
|
||||
ACTION_COLUMN, &actiontext,
|
||||
D1_COLUMN, &data1,
|
||||
D2_COLUMN, &data2,
|
||||
-1);
|
||||
kb->data1 = data1;
|
||||
kb->data2 = data2;
|
||||
|
||||
if (accel)
|
||||
{
|
||||
gtk_accelerator_parse (accel, &keyval, &mod);
|
||||
|
||||
kb->keyval = keyval;
|
||||
kb->mod = key_modifier_get_valid (mod);
|
||||
|
||||
g_free (accel);
|
||||
}
|
||||
|
||||
if (actiontext)
|
||||
{
|
||||
kb->action = key_get_action_from_string (actiontext);
|
||||
g_free (actiontext);
|
||||
}
|
||||
|
||||
if (!accel || !actiontext)
|
||||
key_free (kb);
|
||||
else
|
||||
keybind_list = g_slist_append (keybind_list, kb);
|
||||
|
||||
}
|
||||
while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
|
||||
}
|
||||
|
||||
if (key_save_kbs () == 0)
|
||||
{
|
||||
menu_update_quit_accel ();
|
||||
key_dialog_close (wid, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_add (GtkWidget *wid, gpointer userdata)
|
||||
{
|
||||
GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
|
||||
GtkTreeViewColumn *col;
|
||||
GtkListStore *store = GTK_LIST_STORE (get_store ());
|
||||
GtkTreeIter iter;
|
||||
GtkTreePath *path;
|
||||
|
||||
gtk_list_store_append (store, &iter);
|
||||
gtk_list_store_set (store, &iter, CUSTOM_COLUMN, TRUE, -1);
|
||||
|
||||
/* make sure the new row is visible and selected */
|
||||
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
||||
col = gtk_tree_view_get_column (view, ACTION_COLUMN);
|
||||
gtk_tree_view_scroll_to_cell (view, path, NULL, FALSE, 0.0, 0.0);
|
||||
gtk_tree_view_set_cursor (view, path, col, TRUE);
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
|
||||
static char *
|
||||
key_binding_signature (const char *action, const char *data1, const char *data2)
|
||||
{
|
||||
const char *a = action ? action : "", *b = data1 ? data1 : "", *c = data2 ? data2 : "";
|
||||
return g_strdup_printf ("%" G_GSIZE_FORMAT ":%s%" G_GSIZE_FORMAT ":%s%" G_GSIZE_FORMAT ":%s",
|
||||
strlen (a), a, strlen (b), b, strlen (c), c);
|
||||
return g_strdup_printf ("%s\\n%s\\n%s", action ? action : "", data1 ? data1 : "", data2 ? data2 : "");
|
||||
}
|
||||
|
||||
static int
|
||||
@@ -660,151 +1032,308 @@ key_dialog_reset (GtkWidget *wid, gpointer userdata)
|
||||
}
|
||||
|
||||
static void
|
||||
key_dialog_load (GtkListStore *store)
|
||||
key_dialog_delete (GtkWidget *wid, gpointer userdata)
|
||||
{
|
||||
GHashTable *defaults = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
GHashTable *seen = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
|
||||
GSList *reference = NULL;
|
||||
key_load_kbs_from_buffer (g_strdup (default_kb_cfg), strlen (default_kb_cfg), &reference);
|
||||
for (GSList *item = reference; item; item = item->next)
|
||||
{
|
||||
struct key_binding *kb = item->data;
|
||||
key_dialog_reset_increment (defaults, key_binding_signature (key_actions[kb->action].name, kb->data1, kb->data2));
|
||||
}
|
||||
/* Protect only the built-in number of occurrences of an action and its
|
||||
* parameters. Extra copies remain custom after save/reload, including
|
||||
* copies whose only difference is their key. No runtime order changes. */
|
||||
for (GSList *item = keybind_list; item; item = item->next)
|
||||
{
|
||||
struct key_binding *kb = item->data;
|
||||
GtkTreeIter iter;
|
||||
char *signature = key_binding_signature (key_actions[kb->action].name, kb->data1, kb->data2);
|
||||
gboolean custom = key_dialog_reset_count (seen, signature) >= key_dialog_reset_count (defaults, signature);
|
||||
key_dialog_reset_increment (seen, signature);
|
||||
char *label = gtk_accelerator_get_label (kb->keyval, kb->mod);
|
||||
char *accel = gtk_accelerator_name (kb->keyval, kb->mod);
|
||||
gtk_list_store_append (store, &iter);
|
||||
gtk_list_store_set (store, &iter, KEY_COLUMN, label, ACCEL_COLUMN, accel,
|
||||
ACTION_COLUMN, key_actions[kb->action].name, D1_COLUMN, kb->data1,
|
||||
D2_COLUMN, kb->data2, CUSTOM_COLUMN, custom, -1);
|
||||
g_free (label); g_free (accel);
|
||||
}
|
||||
g_slist_free_full (reference, key_free);
|
||||
g_hash_table_destroy (defaults); g_hash_table_destroy (seen);
|
||||
GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
|
||||
GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (view));
|
||||
GtkTreeIter iter;
|
||||
GtkTreePath *path;
|
||||
gboolean custom;
|
||||
|
||||
if (gtkutil_treeview_get_selected (view, &iter, -1))
|
||||
{
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, CUSTOM_COLUMN, &custom, -1);
|
||||
if (!custom)
|
||||
return;
|
||||
|
||||
/* delete this row, select next one */
|
||||
if (gtk_list_store_remove (store, &iter))
|
||||
{
|
||||
path = gtk_tree_model_get_path (GTK_TREE_MODEL (store), &iter);
|
||||
gtk_tree_view_scroll_to_cell (view, path, NULL, TRUE, 1.0, 0.0);
|
||||
gtk_tree_view_set_cursor (view, path, NULL, FALSE);
|
||||
gtk_tree_path_free (path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static char *key_preferences_serialize (GtkTreeModel *model, GError **error);
|
||||
static GtkWidget *
|
||||
key_dialog_treeview_new (GtkWidget *box)
|
||||
{
|
||||
GtkWidget *scroll;
|
||||
GtkListStore *store, *combostore;
|
||||
GtkTreeViewColumn *col;
|
||||
GtkWidget *view;
|
||||
GtkCellRenderer *render;
|
||||
int i;
|
||||
|
||||
scroll = gtk_scrolled_window_new (NULL, NULL);
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scroll), GTK_SHADOW_IN);
|
||||
|
||||
store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
|
||||
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||
g_return_val_if_fail (store != NULL, NULL);
|
||||
|
||||
view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
|
||||
gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (view), TRUE);
|
||||
gtk_tree_view_set_enable_search (GTK_TREE_VIEW (view), FALSE);
|
||||
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (view), FALSE);
|
||||
|
||||
g_signal_connect (G_OBJECT (view), "key-press-event",
|
||||
G_CALLBACK (key_dialog_keypress), NULL);
|
||||
g_signal_connect (G_OBJECT (gtk_tree_view_get_selection (GTK_TREE_VIEW(view))),
|
||||
"changed", G_CALLBACK (key_dialog_selection_changed), NULL);
|
||||
|
||||
gtk_widget_set_name (view, "fkeys-treeview");
|
||||
if (!theme_gtk3_is_active ())
|
||||
{
|
||||
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||
|
||||
gtk_css_provider_load_from_data (
|
||||
provider,
|
||||
"treeview#fkeys-treeview row:nth-child(odd) {"
|
||||
" background-color: @theme_base_color;"
|
||||
"}"
|
||||
"treeview#fkeys-treeview row:nth-child(even) {"
|
||||
" background-color: shade(@theme_base_color, 0.96);"
|
||||
"}",
|
||||
-1,
|
||||
NULL);
|
||||
theme_css_apply_widget_provider (view, GTK_STYLE_PROVIDER (provider));
|
||||
g_object_unref (provider);
|
||||
}
|
||||
|
||||
render = gtk_cell_renderer_accel_new ();
|
||||
g_object_set (render, "editable", TRUE,
|
||||
#ifndef WIN32
|
||||
"accel-mode", GTK_CELL_RENDERER_ACCEL_MODE_OTHER,
|
||||
#endif
|
||||
NULL);
|
||||
g_signal_connect (G_OBJECT (render), "accel-edited",
|
||||
G_CALLBACK (key_dialog_set_key), NULL);
|
||||
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), KEY_COLUMN,
|
||||
"Key", render,
|
||||
"text", KEY_COLUMN,
|
||||
NULL);
|
||||
|
||||
render = gtk_cell_renderer_text_new ();
|
||||
gtk_tree_view_insert_column_with_attributes (
|
||||
GTK_TREE_VIEW (view), ACCEL_COLUMN,
|
||||
"Accel", render,
|
||||
"text", ACCEL_COLUMN,
|
||||
NULL);
|
||||
|
||||
combostore = gtk_list_store_new (1, G_TYPE_STRING);
|
||||
for (i = 0; i <= KEY_MAX_ACTIONS; i++)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
|
||||
if (key_actions[i].name[0])
|
||||
{
|
||||
gtk_list_store_append (combostore, &iter);
|
||||
gtk_list_store_set (combostore, &iter, 0, key_actions[i].name, -1);
|
||||
}
|
||||
}
|
||||
|
||||
render = gtk_cell_renderer_combo_new ();
|
||||
g_object_set (G_OBJECT (render), "model", combostore,
|
||||
"has-entry", FALSE,
|
||||
"editable", TRUE,
|
||||
"text-column", 0,
|
||||
NULL);
|
||||
g_signal_connect (G_OBJECT (render), "edited",
|
||||
G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (ACTION_COLUMN));
|
||||
g_signal_connect (G_OBJECT (render), "changed",
|
||||
G_CALLBACK (key_dialog_combo_changed), combostore);
|
||||
gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view), ACTION_COLUMN,
|
||||
"Action", render,
|
||||
"text", ACTION_COLUMN,
|
||||
"editable", CUSTOM_COLUMN,
|
||||
NULL);
|
||||
|
||||
render = gtk_cell_renderer_text_new ();
|
||||
g_object_set (render, "editable", TRUE, NULL);
|
||||
g_signal_connect (G_OBJECT (render), "edited",
|
||||
G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D1_COLUMN));
|
||||
gtk_tree_view_insert_column_with_attributes (
|
||||
GTK_TREE_VIEW (view), D1_COLUMN,
|
||||
"Data1", render,
|
||||
"text", D1_COLUMN,
|
||||
"editable", CUSTOM_COLUMN,
|
||||
NULL);
|
||||
|
||||
render = gtk_cell_renderer_text_new ();
|
||||
g_object_set (render, "editable", TRUE, NULL);
|
||||
g_signal_connect (G_OBJECT (render), "edited",
|
||||
G_CALLBACK (key_dialog_entry_edited), GINT_TO_POINTER (D2_COLUMN));
|
||||
gtk_tree_view_insert_column_with_attributes (
|
||||
GTK_TREE_VIEW (view), D2_COLUMN,
|
||||
"Data2", render,
|
||||
"text", D2_COLUMN,
|
||||
"editable", CUSTOM_COLUMN,
|
||||
NULL);
|
||||
|
||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), KEY_COLUMN);
|
||||
gtk_tree_view_column_set_fixed_width (col, 200);
|
||||
gtk_tree_view_column_set_resizable (col, TRUE);
|
||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACCEL_COLUMN);
|
||||
gtk_tree_view_column_set_visible (col, FALSE);
|
||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), ACTION_COLUMN);
|
||||
gtk_tree_view_column_set_fixed_width (col, 160);
|
||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D1_COLUMN);
|
||||
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
|
||||
gtk_tree_view_column_set_min_width (col, 80);
|
||||
gtk_tree_view_column_set_resizable (col, TRUE);
|
||||
col = gtk_tree_view_get_column (GTK_TREE_VIEW (view), D2_COLUMN);
|
||||
gtk_tree_view_column_set_sizing (col, GTK_TREE_VIEW_COLUMN_AUTOSIZE);
|
||||
gtk_tree_view_column_set_min_width (col, 80);
|
||||
gtk_tree_view_column_set_resizable (col, TRUE);
|
||||
|
||||
gtk_container_add (GTK_CONTAINER (scroll), view);
|
||||
gtk_box_pack_start (GTK_BOX (box), scroll, TRUE, TRUE, 0);
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
static void
|
||||
key_preferences_restore (gpointer unused)
|
||||
key_dialog_load (GtkListStore *store)
|
||||
{
|
||||
key_dialog_reset (NULL, NULL);
|
||||
}
|
||||
struct key_binding *kb = NULL;
|
||||
char *label_text, *accel_text;
|
||||
GtkTreeIter iter;
|
||||
GSList *list = keybind_list;
|
||||
|
||||
GtkWidget *
|
||||
key_preferences_create (void)
|
||||
{
|
||||
GtkListStore *store = gtk_list_store_new (N_COLUMNS, G_TYPE_STRING, G_TYPE_STRING,
|
||||
G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||
PreferencesShortcutAction actions[KEY_MAX_ACTIONS + 1];
|
||||
for (guint i = 0; i < G_N_ELEMENTS (actions); i++)
|
||||
{
|
||||
actions[i].id = key_actions[i].name;
|
||||
actions[i].help = key_actions[i].help;
|
||||
}
|
||||
key_dialog_load (store);
|
||||
key_dialog = preferences_shortcuts_page_new (store, actions, G_N_ELEMENTS (actions),
|
||||
key_modifier_get_valid, key_preferences_restore, NULL);
|
||||
g_object_add_weak_pointer (G_OBJECT (key_dialog), (gpointer *)&key_dialog);
|
||||
g_object_set_data_full (G_OBJECT (key_dialog), "original-bindings",
|
||||
key_preferences_serialize (GTK_TREE_MODEL (store), NULL), g_free);
|
||||
g_object_unref (store);
|
||||
return key_dialog;
|
||||
while (list)
|
||||
{
|
||||
kb = (struct key_binding*)list->data;
|
||||
|
||||
label_text = gtk_accelerator_get_label (kb->keyval, kb->mod);
|
||||
accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
|
||||
|
||||
gtk_list_store_append (store, &iter);
|
||||
gtk_list_store_set (store, &iter,
|
||||
KEY_COLUMN, label_text,
|
||||
ACCEL_COLUMN, accel_text,
|
||||
ACTION_COLUMN, key_actions[kb->action].name,
|
||||
D1_COLUMN, kb->data1,
|
||||
D2_COLUMN, kb->data2,
|
||||
CUSTOM_COLUMN, !key_binding_is_builtin (kb), -1);
|
||||
|
||||
g_free (accel_text);
|
||||
g_free (label_text);
|
||||
|
||||
list = g_slist_next (list);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
key_dialog_show (void)
|
||||
{
|
||||
setup_open_page ("keyboard");
|
||||
GtkWidget *vbox, *box, *pane, *scroll;
|
||||
GtkWidget *view, *xtext, *delete_button;
|
||||
GtkListStore *store;
|
||||
XTextColor xtext_palette[XTEXT_COLS];
|
||||
char buf[128];
|
||||
|
||||
if (key_dialog)
|
||||
{
|
||||
mg_bring_tofront (key_dialog);
|
||||
return;
|
||||
}
|
||||
|
||||
g_snprintf(buf, sizeof(buf), _("Keyboard Shortcuts - %s"), _(DISPLAY_NAME));
|
||||
key_dialog = mg_create_generic_tab ("editkeys", buf, TRUE, FALSE, key_dialog_close,
|
||||
NULL, 600, 360, &vbox, 0);
|
||||
|
||||
pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), pane, TRUE, TRUE, 0);
|
||||
box = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
||||
gtk_paned_pack1 (GTK_PANED (pane), box, TRUE, FALSE);
|
||||
view = key_dialog_treeview_new (box);
|
||||
scroll = gtk_scrolled_window_new (NULL, NULL);
|
||||
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
|
||||
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
|
||||
gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scroll), 80);
|
||||
gtk_paned_pack2 (GTK_PANED (pane), scroll, TRUE, FALSE);
|
||||
gtk_paned_set_position (GTK_PANED (pane), 220);
|
||||
theme_get_xtext_colors (xtext_palette, XTEXT_COLS);
|
||||
xtext = gtk_xtext_new (xtext_palette, 0);
|
||||
gtk_container_add (GTK_CONTAINER (scroll), xtext);
|
||||
gtk_xtext_set_font (GTK_XTEXT (xtext), prefs.hex_text_font);
|
||||
|
||||
g_object_set_data (G_OBJECT (key_dialog), "view", view);
|
||||
g_object_set_data (G_OBJECT (key_dialog), "xtext", xtext);
|
||||
g_object_set_data (G_OBJECT (key_dialog), KEY_DIALOG_THEME_LISTENER_ID_KEY,
|
||||
GUINT_TO_POINTER (theme_listener_register ("fkeys.window", key_dialog_theme_changed, key_dialog)));
|
||||
g_signal_connect (G_OBJECT (key_dialog), "destroy", G_CALLBACK (key_dialog_theme_destroy_cb), NULL);
|
||||
|
||||
box = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_button_box_set_layout (GTK_BUTTON_BOX (box), GTK_BUTTONBOX_SPREAD);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), box, FALSE, FALSE, 2);
|
||||
gtk_container_set_border_width (GTK_CONTAINER (box), 5);
|
||||
|
||||
gtkutil_button (box, ICON_FKEYS_NEW, NULL, key_dialog_add,
|
||||
NULL, _("Add"));
|
||||
delete_button = gtkutil_button (box, ICON_FKEYS_DELETE, NULL, key_dialog_delete,
|
||||
NULL, _("Delete"));
|
||||
g_object_set_data (G_OBJECT (key_dialog), "delete_button", delete_button);
|
||||
gtk_widget_set_sensitive (delete_button, FALSE);
|
||||
gtkutil_button (box, ICON_FKEYS_RESET, NULL, key_dialog_reset,
|
||||
NULL, _("Reset"));
|
||||
gtkutil_button (box, ICON_FKEYS_CANCEL, NULL, key_dialog_close,
|
||||
NULL, _("Cancel"));
|
||||
gtkutil_button (box, ICON_FKEYS_SAVE, NULL, key_dialog_save,
|
||||
NULL, _("Save"));
|
||||
|
||||
store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
|
||||
key_dialog_load (store);
|
||||
|
||||
gtk_widget_show_all (key_dialog);
|
||||
}
|
||||
|
||||
static char *
|
||||
key_preferences_serialize (GtkTreeModel *model, GError **error)
|
||||
static int
|
||||
key_save_kbs (void)
|
||||
{
|
||||
GtkTreeIter iter;
|
||||
GString *text = g_string_new ("# ZoiteChat key bindings config file\n\n");
|
||||
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||
while (valid)
|
||||
{
|
||||
char *accel, *action, *data1, *data2;
|
||||
guint key;
|
||||
GdkModifierType modifiers;
|
||||
gtk_tree_model_get (model, &iter, ACCEL_COLUMN, &accel, ACTION_COLUMN, &action,
|
||||
D1_COLUMN, &data1, D2_COLUMN, &data2, -1);
|
||||
if ((!accel || !*accel) && (!action || !*action) && (!data1 || !*data1) && (!data2 || !*data2))
|
||||
goto next;
|
||||
gtk_accelerator_parse (accel ? accel : "", &key, &modifiers);
|
||||
if (!key || !action || strcmp (key_actions[key_get_action_from_string (action)].name, action) != 0 ||
|
||||
(data1 && strpbrk (data1, "\r\n")) || (data2 && strpbrk (data2, "\r\n")))
|
||||
{
|
||||
GtkTreeView *view = g_object_get_data (G_OBJECT (key_dialog), "view");
|
||||
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
|
||||
setup_reveal_widget (GTK_WIDGET (view));
|
||||
preferences_shortcuts_reveal_path (key_dialog, path);
|
||||
gtk_widget_grab_focus (GTK_WIDGET (view));
|
||||
gtk_tree_path_free (path);
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL,
|
||||
_("Complete each keyboard shortcut with a key and an action. Data fields must stay on one line."));
|
||||
g_free (accel); g_free (action); g_free (data1); g_free (data2);
|
||||
g_string_free (text, TRUE);
|
||||
return NULL;
|
||||
}
|
||||
g_string_append_printf (text, "ACCEL=%s\n%s\n", accel, action);
|
||||
if (data1 && *data1) g_string_append_printf (text, "D1:%s\n", data1);
|
||||
else g_string_append (text, "D1!\n");
|
||||
if (data2 && *data2) g_string_append_printf (text, "D2:%s\n", data2);
|
||||
else g_string_append (text, "D2!\n");
|
||||
g_string_append_c (text, '\n');
|
||||
next:
|
||||
g_free (accel); g_free (action); g_free (data1); g_free (data2);
|
||||
valid = gtk_tree_model_iter_next (model, &iter);
|
||||
}
|
||||
return g_string_free (text, FALSE);
|
||||
}
|
||||
int fd;
|
||||
char buf[512];
|
||||
char *accel_text;
|
||||
GSList *list = keybind_list;
|
||||
struct key_binding *kb;
|
||||
|
||||
gboolean
|
||||
key_preferences_stage (GError **error)
|
||||
{
|
||||
char *contents;
|
||||
const char *original;
|
||||
if (!key_dialog) return TRUE;
|
||||
contents = key_preferences_serialize (get_store (), error);
|
||||
if (!contents) return FALSE;
|
||||
original = g_object_get_data (G_OBJECT (key_dialog), "original-bindings");
|
||||
g_object_set_data_full (G_OBJECT (key_dialog), "pending-bindings", NULL, g_free);
|
||||
if (g_strcmp0 (contents, original) != 0)
|
||||
{
|
||||
preferences_persistence_stage_file ("keybindings.conf", contents);
|
||||
g_object_set_data_full (G_OBJECT (key_dialog), "pending-bindings", g_strdup (contents), g_free);
|
||||
}
|
||||
g_free (contents);
|
||||
return TRUE;
|
||||
}
|
||||
fd = zoitechat_open_file ("keybindings.conf", O_CREAT | O_TRUNC | O_WRONLY,
|
||||
0x180, XOF_DOMODE);
|
||||
if (fd < 0)
|
||||
return 1;
|
||||
write (fd, buf, g_snprintf (buf, 510, "# ZoiteChat key bindings config file\n\n"));
|
||||
|
||||
void
|
||||
key_preferences_commit (void)
|
||||
{
|
||||
GSList *bindings = NULL;
|
||||
const char *contents;
|
||||
if (!key_dialog) return;
|
||||
contents = g_object_get_data (G_OBJECT (key_dialog), "pending-bindings");
|
||||
if (!contents) return;
|
||||
if (key_load_kbs_from_buffer (g_strdup (contents), strlen (contents), &bindings) == 0)
|
||||
while (list)
|
||||
{
|
||||
g_slist_free_full (keybind_list, key_free);
|
||||
keybind_list = bindings;
|
||||
menu_update_quit_accel ();
|
||||
kb = list->data;
|
||||
|
||||
accel_text = gtk_accelerator_name (kb->keyval, kb->mod);
|
||||
|
||||
g_snprintf (buf, 510, "ACCEL=%s\n%s\n", accel_text, key_actions[kb->action].name);
|
||||
write (fd, buf, strlen (buf));
|
||||
g_free (accel_text);
|
||||
|
||||
if (kb->data1 && kb->data1[0])
|
||||
write (fd, buf, g_snprintf (buf, 510, "D1:%s\n", kb->data1));
|
||||
else
|
||||
write (fd, "D1!\n", 4);
|
||||
|
||||
if (kb->data2 && kb->data2[0])
|
||||
write (fd, buf, g_snprintf (buf, 510, "D2:%s\n", kb->data2));
|
||||
else
|
||||
write (fd, "D2!\n", 4);
|
||||
|
||||
write (fd, "\n", 1);
|
||||
|
||||
list = g_slist_next (list);
|
||||
}
|
||||
|
||||
close (fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define KBSTATE_MOD 0
|
||||
|
||||
@@ -31,9 +31,6 @@
|
||||
|
||||
void key_init (void);
|
||||
void key_dialog_show (void);
|
||||
GtkWidget *key_preferences_create (void);
|
||||
gboolean key_preferences_stage (GError **error);
|
||||
void key_preferences_commit (void);
|
||||
int key_handle_key_press (GtkWidget * wid, GdkEventKey * evt, session *sess);
|
||||
int key_action_insert (GtkWidget * wid, GdkEventKey * evt, char *d1, char *d2,
|
||||
session *sess);
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include "config.h"
|
||||
#include <gtk/gtk.h>
|
||||
#include "../common/autoaway.h"
|
||||
#include "idle.h"
|
||||
|
||||
#ifdef WIN32
|
||||
#include <windows.h>
|
||||
#elif defined(__APPLE__)
|
||||
#include <CoreGraphics/CoreGraphics.h>
|
||||
#include <math.h>
|
||||
#elif defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
|
||||
#include <gdk/gdkx.h>
|
||||
#include <X11/extensions/scrnsaver.h>
|
||||
#endif
|
||||
|
||||
/* Do not count focus changes, redraws, timers or incoming IRC traffic. */
|
||||
static void
|
||||
idle_event (GdkEvent *event, gpointer data)
|
||||
{
|
||||
switch (event->type)
|
||||
{
|
||||
case GDK_KEY_PRESS:
|
||||
case GDK_BUTTON_PRESS:
|
||||
case GDK_SCROLL:
|
||||
case GDK_MOTION_NOTIFY:
|
||||
case GDK_TOUCH_BEGIN:
|
||||
case GDK_TOUCH_UPDATE:
|
||||
autoaway_activity ();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
gtk_main_do_event (event);
|
||||
}
|
||||
|
||||
void
|
||||
idle_init (void)
|
||||
{
|
||||
autoaway_activity ();
|
||||
/* Keep GTK's normal dispatch, including detached windows and dialogs. */
|
||||
gdk_event_handler_set (idle_event, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Microseconds since desktop input, or -1 for the portable app fallback. */
|
||||
gint64
|
||||
fe_get_idle_time (void)
|
||||
{
|
||||
#ifdef WIN32
|
||||
LASTINPUTINFO input = {0};
|
||||
DWORD elapsed;
|
||||
|
||||
input.cbSize = sizeof input;
|
||||
if (!GetLastInputInfo (&input))
|
||||
return -1;
|
||||
|
||||
/* DWORD subtraction handles the 49-day GetTickCount wrap. Some injected
|
||||
* input has a slightly newer timestamp; do not treat it as weeks idle. */
|
||||
elapsed = GetTickCount () - input.dwTime;
|
||||
if ((gint32)elapsed < 0)
|
||||
elapsed = 0;
|
||||
return (gint64)elapsed * 1000;
|
||||
#elif defined(__APPLE__)
|
||||
CFTimeInterval seconds = CGEventSourceSecondsSinceLastEventType (
|
||||
kCGEventSourceStateCombinedSessionState, kCGAnyInputEventType);
|
||||
if (!isfinite (seconds) || seconds < 0 ||
|
||||
seconds >= (double)G_MAXINT64 / G_USEC_PER_SEC)
|
||||
return -1;
|
||||
return (gint64)(seconds * G_USEC_PER_SEC);
|
||||
#elif defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
|
||||
GdkDisplay *display = gdk_display_get_default ();
|
||||
XScreenSaverInfo info = {0};
|
||||
Display *xdisplay;
|
||||
int event_base, error_base, error;
|
||||
Status ok;
|
||||
const char *wayland = g_getenv ("WAYLAND_DISPLAY");
|
||||
|
||||
/* XWayland idle is not desktop-wide. Never pass a Wayland display to Xlib. */
|
||||
if (!display || !GDK_IS_X11_DISPLAY (display) || (wayland && *wayland))
|
||||
return -1;
|
||||
|
||||
xdisplay = gdk_x11_display_get_xdisplay (display);
|
||||
gdk_x11_display_error_trap_push (display);
|
||||
ok = XScreenSaverQueryExtension (xdisplay, &event_base, &error_base);
|
||||
if (ok)
|
||||
ok = XScreenSaverQueryInfo (xdisplay, DefaultRootWindow (xdisplay), &info);
|
||||
error = gdk_x11_display_error_trap_pop (display);
|
||||
if (!ok || error)
|
||||
return -1;
|
||||
return (gint64)info.idle * 1000;
|
||||
#else
|
||||
return -1;
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#ifndef ZOITECHAT_IDLE_H
|
||||
#define ZOITECHAT_IDLE_H
|
||||
|
||||
void idle_init (void);
|
||||
|
||||
#endif
|
||||
+18
-22
@@ -31,7 +31,6 @@
|
||||
#include "../common/fe.h"
|
||||
#include "gtkutil.h"
|
||||
#include "maingui.h"
|
||||
#include "setup.h"
|
||||
|
||||
#define ICON_IGNORE_NEW "document-new"
|
||||
#define ICON_IGNORE_DELETE "edit-delete"
|
||||
@@ -235,8 +234,6 @@ ignore_delete_entry_clicked (GtkWidget * wid, struct session *sess)
|
||||
static void
|
||||
ignore_store_new (int cancel, char *mask, gpointer data)
|
||||
{
|
||||
if (cancel || !ignorewin)
|
||||
return;
|
||||
GtkTreeView *view = g_object_get_data (G_OBJECT (ignorewin), "view");
|
||||
GtkListStore *store = GTK_LIST_STORE (get_store ());
|
||||
GtkTreeIter iter;
|
||||
@@ -268,11 +265,6 @@ ignore_store_new (int cancel, char *mask, gpointer data)
|
||||
static void
|
||||
ignore_clear_cb (GtkDialog *dialog, gint response)
|
||||
{
|
||||
if (!ignorewin)
|
||||
{
|
||||
gtk_widget_destroy (GTK_WIDGET (dialog));
|
||||
return;
|
||||
}
|
||||
GtkListStore *store = GTK_LIST_STORE (get_store ());
|
||||
GtkTreeIter iter;
|
||||
char *mask;
|
||||
@@ -301,7 +293,7 @@ ignore_clear_entry_clicked (GtkWidget * wid)
|
||||
{
|
||||
GtkWidget *dialog;
|
||||
|
||||
dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (ignorewin)), GTK_DIALOG_DESTROY_WITH_PARENT,
|
||||
dialog = gtk_message_dialog_new (NULL, 0,
|
||||
GTK_MESSAGE_QUESTION, GTK_BUTTONS_OK_CANCEL,
|
||||
_("Are you sure you want to remove all ignores?"));
|
||||
theme_manager_attach_window (dialog);
|
||||
@@ -343,8 +335,8 @@ ignore_stats_entry (GtkWidget * box, char *label, int value)
|
||||
return wid;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
ignore_preferences_create (void)
|
||||
void
|
||||
ignore_gui_open (void)
|
||||
{
|
||||
GtkWidget *vbox, *box, *stat_box, *frame;
|
||||
GtkWidget *view;
|
||||
@@ -353,9 +345,20 @@ ignore_preferences_create (void)
|
||||
GSList *temp = ignore_list;
|
||||
char *mask;
|
||||
gboolean private, chan, notice, ctcp, dcc, invite, unignore;
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
ignorewin = vbox;
|
||||
g_signal_connect (vbox, "destroy", G_CALLBACK (close_ignore_gui_callback), NULL);
|
||||
char buf[128];
|
||||
|
||||
if (ignorewin)
|
||||
{
|
||||
mg_bring_tofront (ignorewin);
|
||||
return;
|
||||
}
|
||||
|
||||
g_snprintf(buf, sizeof(buf), _("Ignore list - %s"), _(DISPLAY_NAME));
|
||||
ignorewin =
|
||||
mg_create_generic_tab ("IgnoreList", buf, FALSE, TRUE,
|
||||
close_ignore_gui_callback,
|
||||
NULL, 700, 300, &vbox, 0);
|
||||
gtkutil_destroy_on_esc (ignorewin);
|
||||
|
||||
view = ignore_treeview_new (vbox);
|
||||
g_object_set_data (G_OBJECT (ignorewin), "view", view);
|
||||
@@ -418,14 +421,7 @@ ignore_preferences_create (void)
|
||||
|
||||
temp = temp->next;
|
||||
}
|
||||
gtk_widget_show_all (ignorewin);
|
||||
return vbox;
|
||||
}
|
||||
|
||||
void
|
||||
ignore_gui_open (void)
|
||||
{
|
||||
setup_open_page ("ignore");
|
||||
gtk_widget_show (ignorewin);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
+167
-71
@@ -50,7 +50,6 @@
|
||||
#include "banlist.h"
|
||||
#include "chanlist.h"
|
||||
#include "editlist.h"
|
||||
#include "setup.h"
|
||||
#include "fkeys.h"
|
||||
#ifdef __APPLE__
|
||||
#include "macos-shortcuts.h"
|
||||
@@ -1322,6 +1321,7 @@ menu_open_server_list (GtkWidget *wid, gpointer none)
|
||||
static void
|
||||
menu_settings (GtkWidget * wid, gpointer none)
|
||||
{
|
||||
extern void setup_open (void);
|
||||
setup_open ();
|
||||
}
|
||||
|
||||
@@ -1336,10 +1336,20 @@ menu_macos_minimize (GtkWidget *widget, gpointer userdata)
|
||||
}
|
||||
#endif
|
||||
|
||||
static void
|
||||
menu_usermenu (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("User menu - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, usermenu_list, buf, "usermenu", "usermenu.conf", 0);
|
||||
}
|
||||
|
||||
static void
|
||||
usermenu_create (GtkWidget *menu)
|
||||
{
|
||||
menu_create (menu, usermenu_list, "", FALSE);
|
||||
menu_quick_item (0, 0, menu, XCMENU_SHADED, 0, 0); /* sep */
|
||||
menu_quick_item_with_callback (menu_usermenu, _("Edit This Menu" ELLIPSIS), menu, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1648,6 +1658,33 @@ menu_banlist (GtkWidget * wid, gpointer none)
|
||||
banlist_opengui (current_sess);
|
||||
}
|
||||
|
||||
#ifdef USE_PLUGIN
|
||||
|
||||
static void
|
||||
menu_loadplugin (void)
|
||||
{
|
||||
plugingui_load ();
|
||||
}
|
||||
|
||||
static void
|
||||
menu_pluginlist (void)
|
||||
{
|
||||
plugingui_open ();
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static void
|
||||
menu_noplugin_info (void)
|
||||
{
|
||||
fe_message (_(DISPLAY_NAME " has been build without plugin support."), FE_MSG_INFO);
|
||||
}
|
||||
|
||||
#define menu_loadplugin menu_noplugin_info
|
||||
#define menu_pluginlist menu_noplugin_info
|
||||
|
||||
#endif
|
||||
|
||||
#define usercommands_help _("User Commands - Special codes:\n\n"\
|
||||
"%c = current channel\n"\
|
||||
"%e = current network name\n"\
|
||||
@@ -1704,47 +1741,73 @@ menu_banlist (GtkWidget * wid, gpointer none)
|
||||
"shell instead of ZoiteChat")
|
||||
|
||||
static void
|
||||
menu_preferences_selector_changed (GtkComboBox *combo, GtkStack *stack)
|
||||
menu_usercommands (void)
|
||||
{
|
||||
const char *id = gtk_combo_box_get_active_id (combo);
|
||||
if (id) gtk_stack_set_visible_child_name (stack, id);
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("User Defined Commands - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, command_list, buf, "commands", "commands.conf",
|
||||
usercommands_help);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
menu_preferences_create (const char *id)
|
||||
static void
|
||||
menu_ulpopup (void)
|
||||
{
|
||||
if (!strcmp (id, "replace"))
|
||||
return editlist_preferences_create (&replace_list, "replace.conf", _("Text"), _("Replace with"), NULL);
|
||||
if (!strcmp (id, "commands"))
|
||||
return editlist_preferences_create (&command_list, "commands.conf", _("Command"), _("Expansion"), usercommands_help);
|
||||
if (!strcmp (id, "urls"))
|
||||
return editlist_preferences_create (&urlhandler_list, "urlhandlers.conf", _("Name"), _("Command"), url_help);
|
||||
if (!strcmp (id, "ctcp"))
|
||||
return editlist_preferences_create (&ctcp_list, "ctcpreply.conf", _("Request"), _("Reply"), ctcp_help);
|
||||
{
|
||||
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
GtkWidget *selector = gtk_combo_box_text_new ();
|
||||
GtkWidget *stack = gtk_stack_new ();
|
||||
const char *ids[] = {"buttons", "popup", "dlgbuttons", "usermenu"};
|
||||
const char *names[] = {N_("User list buttons"), N_("User list popup"), N_("Dialog buttons"), N_("User menu")};
|
||||
const char *files[] = {"buttons.conf", "popup.conf", "dlgbuttons.conf", "usermenu.conf"};
|
||||
GSList **lists[] = {&button_list, &popup_list, &dlgbutton_list, &usermenu_list};
|
||||
const char *help[] = {ulbutton_help, ulbutton_help, dlgbutton_help, ulbutton_help};
|
||||
int i;
|
||||
gtk_box_pack_start (GTK_BOX (box), selector, FALSE, FALSE, 0);
|
||||
gtk_box_pack_start (GTK_BOX (box), stack, TRUE, TRUE, 0);
|
||||
gtk_stack_set_homogeneous (GTK_STACK (stack), FALSE);
|
||||
for (i = 0; i < 4; i++)
|
||||
{
|
||||
gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (selector), ids[i], _(names[i]));
|
||||
gtk_stack_add_named (GTK_STACK (stack), editlist_preferences_create (lists[i], files[i], _("Name"), _("Command"), help[i]), ids[i]);
|
||||
}
|
||||
g_signal_connect (selector, "changed", G_CALLBACK (menu_preferences_selector_changed), stack);
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (selector), 0);
|
||||
g_object_set_data (G_OBJECT (box), "menus-selector", selector);
|
||||
g_object_set_data (G_OBJECT (stack), "preferences-selector", selector);
|
||||
return box;
|
||||
}
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("Userlist Popup menu - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, popup_list, buf, "popup", "popup.conf", ulbutton_help);
|
||||
}
|
||||
|
||||
static void
|
||||
menu_rpopup (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("Replace - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (_("Text"), _("Replace with"), replace_list, buf, "replace", "replace.conf", 0);
|
||||
}
|
||||
|
||||
static void
|
||||
menu_urlhandlers (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("URL Handlers - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, urlhandler_list, buf, "urlhandlers", "urlhandlers.conf", url_help);
|
||||
}
|
||||
|
||||
static void
|
||||
menu_evtpopup (void)
|
||||
{
|
||||
pevent_dialog_show ();
|
||||
}
|
||||
|
||||
static void
|
||||
menu_keypopup (void)
|
||||
{
|
||||
key_dialog_show ();
|
||||
}
|
||||
|
||||
static void
|
||||
menu_ulbuttons (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("Userlist buttons - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, button_list, buf, "buttons", "buttons.conf", ulbutton_help);
|
||||
}
|
||||
|
||||
static void
|
||||
menu_dlgbuttons (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("Dialog buttons - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, dlgbutton_list, buf, "dlgbuttons", "dlgbuttons.conf",
|
||||
dlgbutton_help);
|
||||
}
|
||||
|
||||
static void
|
||||
menu_ctcpguiopen (void)
|
||||
{
|
||||
char buf[128];
|
||||
g_snprintf(buf, sizeof(buf), _("CTCP Replies - %s"), _(DISPLAY_NAME));
|
||||
editlist_gui_open (NULL, NULL, ctcp_list, buf, "ctcpreply", "ctcpreply.conf", ctcp_help);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -1937,7 +2000,7 @@ menu_about (GtkWidget *wid, gpointer sess)
|
||||
|
||||
static struct mymenu mymenu[] = {
|
||||
{N_("_ZoiteChat"), 0, 0, M_NEWMENU, MENU_ID_ZOITECHAT, 0, 1},
|
||||
{N_("Network Li_st" ELLIPSIS), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Network Li_st"), menu_open_server_list, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
|
||||
{N_("_New"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||
@@ -1948,14 +2011,14 @@ static struct mymenu mymenu[] = {
|
||||
{0, 0, 0, M_END, 0, 0, 0},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
|
||||
#define DETACH_OFFSET (10)
|
||||
{0, menu_detach, 0, M_MENUITEM, 0, 0, 1},
|
||||
#define CLOSE_OFFSET (11)
|
||||
{N_("_Load Plugin or Script" ELLIPSIS), menu_loadplugin, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0}, /* 11 */
|
||||
#define DETACH_OFFSET (12)
|
||||
{0, menu_detach, 0, M_MENUITEM, 0, 0, 1}, /* 12 */
|
||||
#define CLOSE_OFFSET (13)
|
||||
{0, menu_close, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
{N_("_Preferences" ELLIPSIS), menu_settings, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1},
|
||||
{N_("_Quit"), menu_quit, 0, M_MENUITEM, MENU_ID_QUIT, 0, 1}, /* 15 */
|
||||
|
||||
{N_("_View"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||
#define MENUBAR_OFFSET (17)
|
||||
@@ -1965,18 +2028,18 @@ static struct mymenu mymenu[] = {
|
||||
{N_("U_ser List Buttons"), menu_ulbuttons_toggle, 0, M_MENUTOG, MENU_ID_ULBUTTONS, 0, 1},
|
||||
{N_("M_ode Buttons"), menu_cmbuttons_toggle, 0, M_MENUTOG, MENU_ID_MODEBUTTONS, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||
{N_("_Channel Switcher"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 23 */
|
||||
#define TABS_OFFSET (24)
|
||||
{N_("_Tabs"), menu_layout_cb, 0, M_MENURADIO, MENU_ID_LAYOUT_TABS, 0, 1},
|
||||
{N_("T_ree"), 0, 0, M_MENURADIO, MENU_ID_LAYOUT_TREE, 0, 1},
|
||||
{0, 0, 0, M_END, 0, 0, 0},
|
||||
{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||
{N_("_Network Meters"), 0, 0, M_MENUSUB, 0, 0, 1}, /* 27 */
|
||||
#define METRE_OFFSET (28)
|
||||
{N_("Off"), menu_metres_off, 0, M_MENURADIO, 0, 0, 1},
|
||||
{N_("Graph"), menu_metres_graph, 0, M_MENURADIO, 0, 0, 1},
|
||||
{N_("Text"), menu_metres_text, 0, M_MENURADIO, 0, 0, 1},
|
||||
{N_("Both"), menu_metres_both, 0, M_MENURADIO, 0, 0, 1},
|
||||
{0, 0, 0, M_END, 0, 0, 0},
|
||||
{0, 0, 0, M_END, 0, 0, 0}, /* 32 */
|
||||
{ 0, 0, 0, M_SEP, 0, 0, 0 },
|
||||
{N_ ("_Fullscreen"), menu_fullscreen_toggle, 0, M_MENUTOG, MENU_ID_FULLSCREEN, 0, 1},
|
||||
|
||||
@@ -1989,7 +2052,20 @@ static struct mymenu mymenu[] = {
|
||||
#define AWAY_OFFSET (41)
|
||||
{N_("Marked _Away"), menu_away_toggle, 0, M_MENUITEM, MENU_ID_AWAY, 0, 1},
|
||||
|
||||
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1},
|
||||
{N_("_Usermenu"), 0, 0, M_NEWMENU, MENU_ID_USERMENU, 0, 1}, /* 40 */
|
||||
|
||||
{N_("S_ettings"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||
{N_("_Preferences"), menu_settings, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
{N_("Auto Replace"), menu_rpopup, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("CTCP Replies"), menu_ctcpguiopen, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Dialog Buttons"), menu_dlgbuttons, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Keyboard Shortcuts"), menu_keypopup, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Text Events"), menu_evtpopup, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("URL Handlers"), menu_urlhandlers, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("User Commands"), menu_usercommands, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("User List Buttons"), menu_ulbuttons, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("User List Popup"), menu_ulpopup, 0, M_MENUITEM, 0, 0, 1}, /* 52 */
|
||||
|
||||
{N_("_Window"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||
{N_("_Ban List"), menu_banlist, 0, M_MENUITEM, 0, 0, 1},
|
||||
@@ -1997,7 +2073,9 @@ static struct mymenu mymenu[] = {
|
||||
{N_("Direct Chat"), menu_dcc_chat_win, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("File _Transfers"), menu_dcc_win, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Friends List"), notify_opengui, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Ignore List"), ignore_gui_open, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("_Plugins and Scripts"), menu_pluginlist, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("_Raw Log"), menu_rawlog, 0, M_MENUITEM, 0, 0, 1}, /* 61 */
|
||||
{N_("_URL Grabber"), url_opengui, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_SEP, 0, 0, 0},
|
||||
{N_("Reset Marker Line"), menu_resetmarker, 0, M_MENUITEM, 0, 0, 1},
|
||||
@@ -2005,14 +2083,14 @@ static struct mymenu mymenu[] = {
|
||||
{N_("_Copy Selection"), menu_copy_selection, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("C_lear Text"), menu_flushbuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Save Text" ELLIPSIS), menu_savebuffer, 0, M_MENUITEM, 0, 0, 1},
|
||||
#define SEARCH_OFFSET (57)
|
||||
#define SEARCH_OFFSET (71)
|
||||
{N_("Search"), 0, 0, M_MENUSUB, 0, 0, 1},
|
||||
{N_("Search Text" ELLIPSIS), menu_search, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Search Next" ), menu_search_next, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("Search Previous" ), menu_search_prev, 0, M_MENUITEM, 0, 0, 1},
|
||||
{0, 0, 0, M_END, 0, 0, 0},
|
||||
|
||||
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1},
|
||||
{N_("_Help"), 0, 0, M_NEWMENU, 0, 0, 1}, /* 74 */
|
||||
{N_("_Contents"), menu_docs, 0, M_MENUITEM, 0, 0, 1},
|
||||
{N_("_About"), menu_about, 0, M_MENUITEM, 0, 0, 1},
|
||||
|
||||
@@ -2022,24 +2100,42 @@ static struct mymenu mymenu[] = {
|
||||
static const char *
|
||||
menu_get_key_action_name (int index)
|
||||
{
|
||||
/* Bind accelerators to actions rather than positions in the menu. */
|
||||
void *callback = mymenu[index].callback;
|
||||
if (callback == menu_open_server_list) return "network-list";
|
||||
if (callback == menu_newserver_tab) return "new-server-tab";
|
||||
if (callback == menu_newserver_window) return "new-server-window";
|
||||
if (callback == menu_close) return "close";
|
||||
if (callback == menu_quit) return "quit";
|
||||
if (callback == menu_bar_toggle_cb) return "menu-toggle";
|
||||
if (callback == menu_userlist_toggle) return "user-list-toggle";
|
||||
if (callback == menu_fullscreen_toggle) return "fullscreen-toggle";
|
||||
if (callback == menu_away_toggle) return "away-toggle";
|
||||
if (callback == menu_resetmarker) return "reset-marker";
|
||||
if (callback == menu_movetomarker) return "move-marker";
|
||||
if (callback == menu_copy_selection) return "copy-selection";
|
||||
if (callback == menu_search) return "search-text";
|
||||
if (callback == menu_search_next) return "search-next";
|
||||
if (callback == menu_search_prev) return "search-previous";
|
||||
if (callback == menu_docs) return "contents";
|
||||
switch (index)
|
||||
{
|
||||
case 1:
|
||||
return "network-list";
|
||||
case 4:
|
||||
return "new-server-tab";
|
||||
case 6:
|
||||
return "new-server-window";
|
||||
case CLOSE_OFFSET:
|
||||
return "close";
|
||||
case 15:
|
||||
return "quit";
|
||||
case MENUBAR_OFFSET:
|
||||
return "menu-toggle";
|
||||
case MENUBAR_OFFSET + 2:
|
||||
return "user-list-toggle";
|
||||
case 34:
|
||||
return "fullscreen-toggle";
|
||||
case AWAY_OFFSET:
|
||||
return "away-toggle";
|
||||
case 66:
|
||||
return "reset-marker";
|
||||
case 67:
|
||||
return "move-marker";
|
||||
case 68:
|
||||
return "copy-selection";
|
||||
case SEARCH_OFFSET + 1:
|
||||
return "search-text";
|
||||
case SEARCH_OFFSET + 2:
|
||||
return "search-next";
|
||||
case SEARCH_OFFSET + 3:
|
||||
return "search-previous";
|
||||
case 77:
|
||||
return "contents";
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -2912,7 +3008,7 @@ togitem:
|
||||
|
||||
#ifdef HAVE_GTK_MAC
|
||||
/* We want ZoiteChat to be the app menu, not including Quit or ZoiteChat itself */
|
||||
if (bar && item && (i <= CLOSE_OFFSET + 1 || mymenu[i].callback == menu_settings) && mymenu[i].id != MENU_ID_ZOITECHAT)
|
||||
if (bar && item && i <= CLOSE_OFFSET + 1 && mymenu[i].id != MENU_ID_ZOITECHAT)
|
||||
{
|
||||
if (!submenu || mymenu[i].type == M_MENUSUB)
|
||||
gtkosx_application_insert_app_menu_item (osx_app, item, appmenu_offset++);
|
||||
|
||||
@@ -42,7 +42,6 @@ void menu_bar_toggle (void);
|
||||
void menu_add_plugin_items (GtkWidget *menu, char *root, char *target);
|
||||
void menu_change_layout (void);
|
||||
void menu_update_quit_accel (void);
|
||||
GtkWidget *menu_preferences_create (const char *id);
|
||||
gboolean menu_key_action (const char *name, guint keyval, GdkModifierType state);
|
||||
|
||||
void menu_set_away (session_gui *gui, int away);
|
||||
|
||||
+24
-49
@@ -25,6 +25,7 @@ zoitechat_gtk_sources = [
|
||||
'gtkutil.c',
|
||||
'icon-resolver.c',
|
||||
'ignoregui.c',
|
||||
'idle.c',
|
||||
'inline-image.c',
|
||||
'joind.c',
|
||||
'menu.c',
|
||||
@@ -32,7 +33,6 @@ zoitechat_gtk_sources = [
|
||||
'notifygui.c',
|
||||
'pixmaps.c',
|
||||
'preferences-persistence.c',
|
||||
'preferences-pages.c',
|
||||
'plugin-tray.c',
|
||||
'plugin-notification.c',
|
||||
'rawlog.c',
|
||||
@@ -46,6 +46,7 @@ zoitechat_gtk_sources = [
|
||||
]
|
||||
|
||||
zoitechat_gtk_cflags = []
|
||||
idle_deps = []
|
||||
|
||||
zoitechat_gtk_deps = [
|
||||
zoitechat_common_dep,
|
||||
@@ -63,8 +64,7 @@ if host_machine.system() == 'darwin' and gtk_mac_dep.found()
|
||||
gtk_dep = declare_dependency(dependencies: [gtk_dep, gtk_mac_dep])
|
||||
endif
|
||||
|
||||
theme_math_dep = cc.find_library('m', required: false)
|
||||
zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep, theme_math_dep]
|
||||
zoitechat_theme_deps = [gtk_dep, zoitechat_common_dep]
|
||||
|
||||
if host_machine.system() != 'windows'
|
||||
appindicator_opt = get_option('appindicator')
|
||||
@@ -95,9 +95,17 @@ if host_machine.system() != 'windows'
|
||||
zoitechat_gtk_deps += gdk_x11_dep
|
||||
endif
|
||||
|
||||
xss_dep = dependency('xscrnsaver', required: false)
|
||||
if gdk_x11_dep.found() and xss_dep.found()
|
||||
zoitechat_gtk_deps += xss_dep
|
||||
idle_deps += [gdk_x11_dep, xss_dep]
|
||||
zoitechat_gtk_cflags += '-DHAVE_XSS'
|
||||
endif
|
||||
|
||||
x11_dep = dependency('x11', required: false)
|
||||
if x11_dep.found()
|
||||
zoitechat_gtk_deps += x11_dep
|
||||
idle_deps += x11_dep
|
||||
endif
|
||||
endif
|
||||
|
||||
@@ -107,13 +115,16 @@ zoitechat_gtk_objcflags = []
|
||||
if host_machine.system() == 'windows'
|
||||
zoitechat_gtk_sources += 'notifications/notification-windows.c'
|
||||
zoitechat_gtk_deps += cc.find_library('dwmapi', required: true)
|
||||
idle_deps += cc.find_library('user32', required: true)
|
||||
zoitechat_gtk_deps += idle_deps
|
||||
zoitechat_theme_deps += cc.find_library('dwmapi', required: true)
|
||||
|
||||
elif host_machine.system() == 'darwin'
|
||||
add_languages('objc', required: true)
|
||||
zoitechat_gtk_sources += 'macos-shortcuts.c'
|
||||
zoitechat_gtk_sources += 'notifications/notification-osx.m'
|
||||
zoitechat_gtk_deps += dependency('appleframeworks', modules: ['Cocoa', 'UserNotifications'])
|
||||
zoitechat_gtk_deps += dependency('appleframeworks', modules: ['Cocoa', 'UserNotifications', 'CoreGraphics'])
|
||||
idle_deps += dependency('appleframeworks', modules: ['CoreGraphics'])
|
||||
zoitechat_gtk_objcflags += '-fobjc-arc'
|
||||
else
|
||||
zoitechat_gtk_sources += 'notifications/notification-freedesktop.c'
|
||||
@@ -245,50 +256,6 @@ theme_runtime_tests = executable('theme_runtime_persistence_tests',
|
||||
link_with: zoitechat_theme_lib,
|
||||
)
|
||||
|
||||
preferences_persistence_tests = executable('preferences_persistence_tests',
|
||||
'tests/test-preferences-persistence.c',
|
||||
include_directories: [config_h_include],
|
||||
dependencies: [gtk_dep],
|
||||
)
|
||||
|
||||
test('Preferences Persistence Tests', preferences_persistence_tests,
|
||||
protocol: 'tap',
|
||||
timeout: 120,
|
||||
)
|
||||
|
||||
# g_test_skip() was added in GLib 2.38, below GTK 3.22's minimum GLib
|
||||
# requirement. Allow it in this test target without changing the app's API
|
||||
# ceiling or lowering macOS's existing 2.60 ceiling.
|
||||
preferences_pages_test_cflags = []
|
||||
if host_machine.system() != 'darwin'
|
||||
preferences_pages_test_cflags = [
|
||||
'-UGLIB_VERSION_MAX_ALLOWED',
|
||||
'-DGLIB_VERSION_MAX_ALLOWED=GLIB_VERSION_2_38',
|
||||
]
|
||||
endif
|
||||
|
||||
preferences_pages_tests = executable('preferences_pages_tests',
|
||||
[textevents, 'tests/test-preferences-pages.c', 'preferences-pages.c'],
|
||||
include_directories: [config_h_include, include_directories('../common')],
|
||||
dependencies: [gtk_dep],
|
||||
c_args: preferences_pages_test_cflags,
|
||||
)
|
||||
test('Preferences Pages Tests', preferences_pages_tests,
|
||||
protocol: 'tap',
|
||||
timeout: 120,
|
||||
)
|
||||
|
||||
spell_entry_tests = executable('spell_entry_tests',
|
||||
[marshal, 'tests/test-spell-entry.c'],
|
||||
include_directories: [config_h_include, include_directories('../common')],
|
||||
dependencies: [gtk_dep, libgmodule_dep],
|
||||
c_args: preferences_pages_test_cflags,
|
||||
)
|
||||
test('Spell Entry Tests', spell_entry_tests,
|
||||
protocol: 'tap',
|
||||
timeout: 120,
|
||||
)
|
||||
|
||||
test('Theme Runtime Persistence Tests', theme_runtime_tests,
|
||||
protocol: 'tap',
|
||||
timeout: 120,
|
||||
@@ -323,7 +290,7 @@ test('Theme GTK3 Settings Tests', theme_gtk3_settings_tests,
|
||||
theme_preferences_gtk3_populate_tests = executable('theme_preferences_gtk3_populate_tests',
|
||||
'theme/tests/test-theme-preferences-gtk3-populate.c',
|
||||
include_directories: [config_h_include],
|
||||
dependencies: [gtk_dep, theme_math_dep],
|
||||
dependencies: [gtk_dep],
|
||||
)
|
||||
|
||||
test('Theme Preferences GTK3 Populate Tests', theme_preferences_gtk3_populate_tests,
|
||||
@@ -348,3 +315,11 @@ xtext_tests = executable('xtext_tests',
|
||||
dependencies: [gtk_dep],
|
||||
)
|
||||
test('XText Tests', xtext_tests, protocol: 'tap', timeout: 120)
|
||||
|
||||
idle_tests = executable('idle_tests',
|
||||
'tests/test-idle.c',
|
||||
include_directories: [config_h_include],
|
||||
dependencies: [gtk_dep] + idle_deps,
|
||||
c_args: zoitechat_gtk_cflags,
|
||||
)
|
||||
test('Idle Backend Tests', idle_tests, protocol: 'tap', timeout: 30)
|
||||
+14
-14
@@ -35,8 +35,6 @@ typedef struct session zoitechat_context;
|
||||
#include "../common/cfgfiles.h"
|
||||
#include "gtkutil.h"
|
||||
#include "maingui.h"
|
||||
#include "setup.h"
|
||||
#include "plugingui.h"
|
||||
|
||||
/* model for the plugin treeview */
|
||||
enum
|
||||
@@ -359,15 +357,24 @@ plugingui_reloadbutton_cb (GtkWidget *wid, GtkTreeView *view)
|
||||
}
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
plugingui_preferences_create (void)
|
||||
void
|
||||
plugingui_open (void)
|
||||
{
|
||||
GtkWidget *view;
|
||||
GtkWidget *view_scroll;
|
||||
GtkWidget *vbox, *hbox;
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
plugin_window = vbox;
|
||||
g_signal_connect (vbox, "destroy", G_CALLBACK (plugingui_close), NULL);
|
||||
char buf[128];
|
||||
|
||||
if (plugin_window)
|
||||
{
|
||||
mg_bring_tofront (plugin_window);
|
||||
return;
|
||||
}
|
||||
|
||||
g_snprintf(buf, sizeof(buf), _("Plugins and Scripts - %s"), _(DISPLAY_NAME));
|
||||
plugin_window = mg_create_generic_tab ("Addons", buf, FALSE, TRUE, plugingui_close, NULL,
|
||||
700, 300, &vbox, 0);
|
||||
gtkutil_destroy_on_esc (plugin_window);
|
||||
|
||||
view = plugingui_treeview_new (vbox);
|
||||
view_scroll = gtk_widget_get_parent (view);
|
||||
@@ -397,11 +404,4 @@ plugingui_preferences_create (void)
|
||||
fe_pluginlist_update ();
|
||||
|
||||
gtk_widget_show_all (plugin_window);
|
||||
return vbox;
|
||||
}
|
||||
|
||||
void
|
||||
plugingui_open (void)
|
||||
{
|
||||
setup_open_page ("plugins");
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
#define ZOITECHAT_PLUGINGUI_H
|
||||
|
||||
void plugingui_open (void);
|
||||
GtkWidget *plugingui_preferences_create (void);
|
||||
void plugingui_load (void);
|
||||
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,62 +0,0 @@
|
||||
/* ZoiteChat - native editors for the everyday preferences pages.
|
||||
* Licensed under the GNU General Public License, version 2 or later.
|
||||
*/
|
||||
#ifndef ZOITECHAT_PREFERENCES_PAGES_H
|
||||
#define ZOITECHAT_PREFERENCES_PAGES_H
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
|
||||
struct zoitechatprefs;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
PREFERENCES_ALERT_DESKTOP = 1 << 0,
|
||||
PREFERENCES_ALERT_TRAY = 1 << 1,
|
||||
PREFERENCES_ALERT_ATTENTION = 1 << 2,
|
||||
PREFERENCES_ALERT_TRAY_BEHAVIOR = 1 << 3,
|
||||
PREFERENCES_ALERT_HIDDEN_ONLY = 1 << 4
|
||||
} PreferencesAlertCapabilities;
|
||||
|
||||
typedef void (*PreferencesSoundChanged) (gpointer user_data);
|
||||
|
||||
/* All editors write only to the window's staged preferences. The caller owns
|
||||
* both the preferences and the staged Beep event's allocated filename. */
|
||||
GtkWidget *preferences_text_page_new (struct zoitechatprefs *staged,
|
||||
const char *const *languages);
|
||||
GtkWidget *preferences_tabs_page_new (struct zoitechatprefs *staged);
|
||||
GtkWidget *preferences_notifications_page_new (struct zoitechatprefs *staged,
|
||||
PreferencesAlertCapabilities capabilities, char **beep_file,
|
||||
PreferencesSoundChanged sound_changed, gpointer user_data);
|
||||
void preferences_notifications_refresh_sound (GtkWidget *page);
|
||||
GtkWidget *preferences_history_page_new (struct zoitechatprefs *staged,
|
||||
const char *config_dir);
|
||||
gboolean preferences_history_page_stage (GtkWidget *page, GError **error);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
const char *code;
|
||||
const char *name;
|
||||
} PreferencesSpellLanguage;
|
||||
typedef void (*PreferencesTypingPreview) (GtkWidget *entry, const struct zoitechatprefs *staged);
|
||||
GtkWidget *preferences_typing_page_new (struct zoitechatprefs *staged, GtkWidget *preview_entry,
|
||||
const PreferencesSpellLanguage *languages, guint count, PreferencesTypingPreview update_preview);
|
||||
gboolean preferences_typing_page_stage (GtkWidget *page, GError **error);
|
||||
|
||||
/* These columns retain the existing keybindings.conf editor's model and order. */
|
||||
enum
|
||||
{
|
||||
PREFERENCES_SHORTCUT_LABEL, PREFERENCES_SHORTCUT_ACCEL,
|
||||
PREFERENCES_SHORTCUT_ACTION, PREFERENCES_SHORTCUT_DATA1,
|
||||
PREFERENCES_SHORTCUT_DATA2, PREFERENCES_SHORTCUT_CUSTOM,
|
||||
PREFERENCES_SHORTCUT_N_COLUMNS
|
||||
};
|
||||
typedef struct { const char *id; const char *help; } PreferencesShortcutAction;
|
||||
typedef GdkModifierType (*PreferencesShortcutModifiers) (GdkModifierType modifiers);
|
||||
typedef void (*PreferencesShortcutsRestore) (gpointer data);
|
||||
GtkWidget *preferences_shortcuts_page_new (GtkListStore *store,
|
||||
const PreferencesShortcutAction *actions, guint count,
|
||||
PreferencesShortcutModifiers normalize, PreferencesShortcutsRestore restore, gpointer data);
|
||||
void preferences_shortcuts_reveal_path (GtkWidget *page, GtkTreePath *raw_path);
|
||||
void preferences_shortcuts_find_action (GtkWidget *page, const char *action_id);
|
||||
|
||||
#endif
|
||||
@@ -23,227 +23,64 @@
|
||||
#include "../common/cfgfiles.h"
|
||||
#include "theme/theme-runtime.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <glib/gstdio.h>
|
||||
#ifdef WIN32
|
||||
#include <io.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *name;
|
||||
char *contents;
|
||||
char *temporary;
|
||||
char *backup;
|
||||
gboolean existed;
|
||||
gboolean installed;
|
||||
} PreferencesFile;
|
||||
|
||||
static GPtrArray *staged_files;
|
||||
|
||||
static void
|
||||
preferences_file_free (gpointer data)
|
||||
{
|
||||
PreferencesFile *file = data;
|
||||
if (file->temporary) g_unlink (file->temporary);
|
||||
if (file->backup) g_unlink (file->backup);
|
||||
g_free (file->name);
|
||||
g_free (file->contents);
|
||||
g_free (file->temporary);
|
||||
g_free (file->backup);
|
||||
g_free (file);
|
||||
}
|
||||
|
||||
void
|
||||
preferences_persistence_clear_staged_files (void)
|
||||
{
|
||||
if (staged_files) g_ptr_array_free (staged_files, TRUE);
|
||||
staged_files = NULL;
|
||||
}
|
||||
|
||||
void
|
||||
preferences_persistence_stage_file (const char *name, const char *contents)
|
||||
{
|
||||
PreferencesFile *file;
|
||||
g_return_if_fail (name && contents && !strchr (name, '/') && !strchr (name, '\\'));
|
||||
if (!staged_files)
|
||||
staged_files = g_ptr_array_new_with_free_func (preferences_file_free);
|
||||
file = g_new0 (PreferencesFile, 1);
|
||||
file->name = g_strdup (name);
|
||||
file->contents = g_strdup (contents);
|
||||
g_ptr_array_add (staged_files, file);
|
||||
}
|
||||
|
||||
/* A temporary in the configuration directory stays on the same filesystem.
|
||||
* Restrictive permissions also protect command text and identity settings. */
|
||||
static gboolean
|
||||
preferences_write_temporary (const char *contents, gsize length, char **path)
|
||||
{
|
||||
int fd;
|
||||
gsize written = 0;
|
||||
char *temporary = g_build_filename (get_xdir (), ".preferences-XXXXXX", NULL);
|
||||
fd = g_mkstemp (temporary);
|
||||
if (fd < 0) { g_free (temporary); return FALSE; }
|
||||
while (written < length)
|
||||
{
|
||||
gssize count = write (fd, contents + written, length - written);
|
||||
if (count < 0 && errno == EINTR) continue;
|
||||
if (count <= 0)
|
||||
{
|
||||
close (fd);
|
||||
g_unlink (temporary);
|
||||
g_free (temporary);
|
||||
return FALSE;
|
||||
}
|
||||
written += count;
|
||||
}
|
||||
if (close (fd) != 0)
|
||||
{
|
||||
g_unlink (temporary);
|
||||
g_free (temporary);
|
||||
return FALSE;
|
||||
}
|
||||
*path = temporary;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preferences_backup (PreferencesFile *file)
|
||||
{
|
||||
char *path = g_build_filename (get_xdir (), file->name, NULL);
|
||||
char *contents = NULL;
|
||||
gsize length;
|
||||
GError *error = NULL;
|
||||
gboolean success;
|
||||
if (!g_file_get_contents (path, &contents, &length, &error))
|
||||
{
|
||||
success = g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT);
|
||||
g_clear_error (&error);
|
||||
g_free (path);
|
||||
return success;
|
||||
}
|
||||
file->existed = TRUE;
|
||||
success = preferences_write_temporary (contents, length, &file->backup);
|
||||
g_free (contents);
|
||||
g_free (path);
|
||||
return success;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preferences_install (PreferencesFile *file, const char *temporary)
|
||||
{
|
||||
char *path = g_build_filename (get_xdir (), file->name, NULL);
|
||||
gboolean success;
|
||||
#ifdef WIN32
|
||||
/* Match the existing config/theme save convention on Windows. The
|
||||
* backup remains available if replacement fails after unlinking. */
|
||||
if (file->existed && g_unlink (path) != 0)
|
||||
{
|
||||
g_free (path);
|
||||
return FALSE;
|
||||
}
|
||||
file->installed = TRUE;
|
||||
#endif
|
||||
success = g_rename (temporary, path) == 0;
|
||||
if (success) file->installed = TRUE;
|
||||
g_free (path);
|
||||
return success;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
preferences_restore (PreferencesFile *file)
|
||||
{
|
||||
char *path;
|
||||
gboolean success;
|
||||
if (!file->installed) return TRUE;
|
||||
path = g_build_filename (get_xdir (), file->name, NULL);
|
||||
if (file->existed)
|
||||
{
|
||||
#ifdef WIN32
|
||||
g_unlink (path);
|
||||
#endif
|
||||
success = g_rename (file->backup, path) == 0;
|
||||
/* Retain the recovery copy if restoration itself fails. */
|
||||
if (!success) g_clear_pointer (&file->backup, g_free);
|
||||
}
|
||||
else success = g_unlink (path) == 0 || errno == ENOENT;
|
||||
g_free (path);
|
||||
return success;
|
||||
}
|
||||
|
||||
PreferencesPersistenceResult
|
||||
preferences_persistence_save_all (void)
|
||||
{
|
||||
PreferencesPersistenceResult result = {0};
|
||||
GPtrArray *files = g_ptr_array_new_with_free_func (preferences_file_free);
|
||||
PreferencesFile *config = g_new0 (PreferencesFile, 1);
|
||||
PreferencesFile *theme = g_new0 (PreferencesFile, 1);
|
||||
guint i;
|
||||
result.retry_possible = TRUE;
|
||||
config->name = g_strdup ("zoitechat.conf");
|
||||
theme->name = g_strdup ("colors.conf");
|
||||
g_ptr_array_add (files, config);
|
||||
g_ptr_array_add (files, theme);
|
||||
if (!save_config_prepare (&config->temporary))
|
||||
{
|
||||
result.config_failed = TRUE;
|
||||
result.failed_file = "zoitechat.conf";
|
||||
goto done;
|
||||
}
|
||||
if (!theme_runtime_save_prepare (&theme->temporary))
|
||||
{
|
||||
result.theme_failed = TRUE;
|
||||
result.failed_file = "colors.conf";
|
||||
goto done;
|
||||
}
|
||||
for (i = 0; staged_files && i < staged_files->len; i++)
|
||||
{
|
||||
PreferencesFile *queued = g_ptr_array_index (staged_files, i);
|
||||
PreferencesFile *file = g_new0 (PreferencesFile, 1);
|
||||
file->name = g_strdup (queued->name);
|
||||
g_ptr_array_add (files, file);
|
||||
if (!preferences_write_temporary (queued->contents, strlen (queued->contents), &file->temporary))
|
||||
{
|
||||
result.failed_file = queued->name;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
/* Prepare every file and recovery copy before replacing anything. */
|
||||
for (i = 0; i < files->len; i++)
|
||||
{
|
||||
PreferencesFile *file = g_ptr_array_index (files, i);
|
||||
if (!preferences_backup (file))
|
||||
{
|
||||
result.failed_file = i < 2 ? (i ? "colors.conf" : "zoitechat.conf")
|
||||
: ((PreferencesFile *)g_ptr_array_index (staged_files, i - 2))->name;
|
||||
result.config_failed = i == 0;
|
||||
result.theme_failed = i == 1;
|
||||
goto done;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < files->len; i++)
|
||||
{
|
||||
PreferencesFile *file = g_ptr_array_index (files, i);
|
||||
if (!preferences_install (file, file->temporary))
|
||||
{
|
||||
result.failed_file = i < 2 ? (i ? "colors.conf" : "zoitechat.conf")
|
||||
: ((PreferencesFile *)g_ptr_array_index (staged_files, i - 2))->name;
|
||||
result.config_failed = i == 0;
|
||||
result.theme_failed = i == 1;
|
||||
goto rollback;
|
||||
}
|
||||
}
|
||||
result.success = TRUE;
|
||||
goto done;
|
||||
rollback:
|
||||
for (i = files->len; i > 0; i--)
|
||||
if (!preferences_restore (g_ptr_array_index (files, i - 1)))
|
||||
result.partial_failure = TRUE;
|
||||
PreferencesPersistenceResult result;
|
||||
char *config_temp;
|
||||
char *theme_temp;
|
||||
|
||||
result.success = FALSE;
|
||||
result.retry_possible = TRUE;
|
||||
result.partial_failure = FALSE;
|
||||
result.config_failed = FALSE;
|
||||
result.theme_failed = FALSE;
|
||||
result.failed_file = NULL;
|
||||
config_temp = NULL;
|
||||
theme_temp = NULL;
|
||||
|
||||
if (!save_config_prepare (&config_temp))
|
||||
{
|
||||
result.config_failed = TRUE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!theme_runtime_save_prepare (&theme_temp))
|
||||
{
|
||||
result.theme_failed = TRUE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!save_config_finalize (config_temp))
|
||||
{
|
||||
result.config_failed = TRUE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
if (!theme_runtime_save_finalize (theme_temp))
|
||||
{
|
||||
result.theme_failed = TRUE;
|
||||
result.partial_failure = TRUE;
|
||||
goto done;
|
||||
}
|
||||
|
||||
result.success = TRUE;
|
||||
|
||||
done:
|
||||
g_ptr_array_free (files, TRUE);
|
||||
return result;
|
||||
if (!result.success)
|
||||
{
|
||||
if (result.config_failed && result.theme_failed)
|
||||
result.failed_file = "zoitechat.conf and colors.conf";
|
||||
else if (result.config_failed)
|
||||
result.failed_file = "zoitechat.conf";
|
||||
else if (result.theme_failed)
|
||||
result.failed_file = "colors.conf";
|
||||
}
|
||||
|
||||
save_config_discard (config_temp);
|
||||
theme_runtime_save_discard (theme_temp);
|
||||
g_free (config_temp);
|
||||
g_free (theme_temp);
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -34,9 +34,5 @@ typedef struct
|
||||
} PreferencesPersistenceResult;
|
||||
|
||||
PreferencesPersistenceResult preferences_persistence_save_all (void);
|
||||
/* Editors queue serialized settings; runtime state is committed only after
|
||||
* every queued file and the main preferences have been saved successfully. */
|
||||
void preferences_persistence_stage_file (const char *name, const char *contents);
|
||||
void preferences_persistence_clear_staged_files (void);
|
||||
|
||||
#endif
|
||||
+14
-16
@@ -70,7 +70,7 @@ static GtkWidget *entry_nick1;
|
||||
static GtkWidget *entry_nick2;
|
||||
static GtkWidget *entry_nick3;
|
||||
static GtkWidget *entry_guser;
|
||||
static GtkWidget *entry_greal;
|
||||
/* static GtkWidget *entry_greal; */
|
||||
|
||||
enum {
|
||||
SERVER_TREE,
|
||||
@@ -1650,7 +1650,7 @@ servlist_savegui (void)
|
||||
sp = strchr (prefs.hex_irc_user_name, ' ');
|
||||
if (sp)
|
||||
sp[0] = 0; /* spaces will break the login */
|
||||
safe_strcpy (prefs.hex_irc_real_name, gtk_entry_get_text (GTK_ENTRY (entry_greal)), sizeof(prefs.hex_irc_real_name));
|
||||
/* strcpy (prefs.hex_irc_real_name, gtk_entry_get_text (GTK_ENTRY (entry_greal))); */
|
||||
servlist_save ();
|
||||
if (!save_config ())
|
||||
fe_message (_("Could not save zoitechat.conf."), FE_MSG_WARN);
|
||||
@@ -2809,12 +2809,12 @@ servlist_open_networks (void)
|
||||
GtkWidget *label4;
|
||||
GtkWidget *label5;
|
||||
GtkWidget *label6;
|
||||
GtkWidget *label7;
|
||||
/* GtkWidget *label7; */
|
||||
GtkWidget *entry1;
|
||||
GtkWidget *entry2;
|
||||
GtkWidget *entry3;
|
||||
GtkWidget *entry4;
|
||||
GtkWidget *entry5;
|
||||
/* GtkWidget *entry5; */
|
||||
GtkWidget *vbox2;
|
||||
GtkWidget *label1;
|
||||
GtkWidget *table4;
|
||||
@@ -2900,12 +2900,12 @@ servlist_open_networks (void)
|
||||
gtk_widget_set_halign (label6, GTK_ALIGN_START);
|
||||
gtk_widget_set_valign (label6, GTK_ALIGN_CENTER);
|
||||
|
||||
label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
|
||||
/* label7 = gtk_label_new_with_mnemonic (_("Rea_l name:"));
|
||||
gtk_widget_show (label7);
|
||||
servlist_table_attach (table1, label7, 0, 1, 4, 5,
|
||||
FALSE, FALSE,
|
||||
SERVLIST_ALIGN_START, SERVLIST_ALIGN_CENTER,
|
||||
0, 0);
|
||||
gtk_table_attach (GTK_TABLE (table1), label7, 0, 1, 4, 5,
|
||||
(GtkAttachOptions) (GTK_FILL),
|
||||
(GtkAttachOptions) (0), 0, 0);
|
||||
*/
|
||||
|
||||
entry_nick1 = entry1 = gtk_entry_new ();
|
||||
gtk_entry_set_text (GTK_ENTRY (entry1), prefs.hex_irc_nick1);
|
||||
@@ -2939,14 +2939,12 @@ servlist_open_networks (void)
|
||||
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_CENTER,
|
||||
0, 0);
|
||||
|
||||
entry_greal = entry5 = gtk_entry_new ();
|
||||
gtk_entry_set_max_length (GTK_ENTRY (entry5), sizeof prefs.hex_irc_real_name - 1);
|
||||
/* entry_greal = entry5 = gtk_entry_new ();
|
||||
gtk_entry_set_text (GTK_ENTRY (entry5), prefs.hex_irc_real_name);
|
||||
gtk_widget_show (entry5);
|
||||
servlist_table_attach (table1, entry5, 1, 2, 4, 5,
|
||||
TRUE, FALSE,
|
||||
SERVLIST_ALIGN_FILL, SERVLIST_ALIGN_CENTER,
|
||||
0, 0);
|
||||
gtk_table_attach (GTK_TABLE (table1), entry5, 1, 2, 4, 5,
|
||||
(GtkAttachOptions) (GTK_EXPAND | GTK_FILL),
|
||||
(GtkAttachOptions) (0), 0, 0); */
|
||||
|
||||
vbox2 = gtkutil_box_new (GTK_ORIENTATION_VERTICAL, FALSE, 0);
|
||||
gtk_widget_show (vbox2);
|
||||
@@ -3100,7 +3098,7 @@ button_connect = gtkutil_button (hbuttonbox1, ICON_SERVLIST_CONNECT, NULL,
|
||||
|
||||
gtk_label_set_mnemonic_widget (GTK_LABEL (label3), entry1);
|
||||
gtk_label_set_mnemonic_widget (GTK_LABEL (label6), entry4);
|
||||
gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5);
|
||||
/* gtk_label_set_mnemonic_widget (GTK_LABEL (label7), entry5); */
|
||||
|
||||
gtk_widget_grab_focus (networks_tree);
|
||||
gtk_widget_grab_default (button_close);
|
||||
|
||||
+362
-986
File diff suppressed because it is too large.
Load diff
@@ -24,9 +24,5 @@
|
||||
|
||||
void setup_apply_real (const ThemeChangedEvent *event);
|
||||
void setup_open (void);
|
||||
/* Stable, untranslated page IDs allow existing menu entries to deep-link. */
|
||||
void setup_open_page (const char *id);
|
||||
/* Reveal an existing editor, including any nested menu selector, on errors. */
|
||||
void setup_reveal_widget (GtkWidget *widget);
|
||||
|
||||
#endif
|
||||
@@ -928,10 +928,6 @@ sexy_spell_entry_init(SexySpellEntry *entry)
|
||||
entry->priv = g_new0(SexySpellEntryPriv, 1);
|
||||
|
||||
entry->priv->dict_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
|
||||
entry->priv->attr_list = pango_attr_list_new();
|
||||
/* An untouched input still needs an empty word list. Loading the first
|
||||
* working dictionary on Save can recheck it before any changed signal. */
|
||||
entry->priv->words = g_new0(gchar *, 1);
|
||||
|
||||
if (have_enchant)
|
||||
{
|
||||
@@ -943,6 +939,8 @@ sexy_spell_entry_init(SexySpellEntry *entry)
|
||||
sexy_spell_entry_activate_default_languages(entry);
|
||||
}
|
||||
|
||||
entry->priv->attr_list = pango_attr_list_new();
|
||||
|
||||
entry->priv->checked = TRUE;
|
||||
entry->priv->parseattr = TRUE;
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/* ZoiteChat
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#include <gtk/gtk.h>
|
||||
#include "../idle.h"
|
||||
|
||||
static int activity_count, dispatch_count;
|
||||
static void test_gtk_dispatch (GdkEvent *event);
|
||||
|
||||
void
|
||||
autoaway_activity (void)
|
||||
{
|
||||
activity_count++;
|
||||
}
|
||||
|
||||
/* Exercise the real handler without delivering synthetic input to a widget. */
|
||||
#define gtk_main_do_event test_gtk_dispatch
|
||||
#include "../idle.c"
|
||||
#undef gtk_main_do_event
|
||||
|
||||
static void
|
||||
test_gtk_dispatch (GdkEvent *event)
|
||||
{
|
||||
dispatch_count++;
|
||||
}
|
||||
|
||||
static void
|
||||
test_user_input_and_dispatch (void)
|
||||
{
|
||||
GdkEvent event = {0};
|
||||
GdkEventType input[] = {GDK_KEY_PRESS, GDK_BUTTON_PRESS, GDK_SCROLL,
|
||||
GDK_MOTION_NOTIFY, GDK_TOUCH_BEGIN, GDK_TOUCH_UPDATE};
|
||||
GdkEventType other[] = {GDK_FOCUS_CHANGE, GDK_CONFIGURE, GDK_WINDOW_STATE,
|
||||
GDK_PROPERTY_NOTIFY, GDK_VISIBILITY_NOTIFY};
|
||||
unsigned int i;
|
||||
|
||||
activity_count = dispatch_count = 0;
|
||||
for (i = 0; i < G_N_ELEMENTS (input); i++)
|
||||
{
|
||||
event.type = input[i];
|
||||
idle_event (&event, NULL);
|
||||
}
|
||||
g_assert_cmpint (activity_count, ==, G_N_ELEMENTS (input));
|
||||
for (i = 0; i < G_N_ELEMENTS (other); i++)
|
||||
{
|
||||
event.type = other[i];
|
||||
idle_event (&event, NULL);
|
||||
}
|
||||
g_assert_cmpint (activity_count, ==, G_N_ELEMENTS (input));
|
||||
g_assert_cmpint (dispatch_count, ==, G_N_ELEMENTS (input) + G_N_ELEMENTS (other));
|
||||
}
|
||||
|
||||
static void
|
||||
test_native_idle (void)
|
||||
{
|
||||
gint64 idle = fe_get_idle_time ();
|
||||
g_assert_cmpint (idle, >=, -1);
|
||||
#if defined(HAVE_XSS) && defined(GDK_WINDOWING_X11)
|
||||
if (gdk_display_get_default () && GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
|
||||
{
|
||||
/* XWayland must use the app fallback, even if XSS is compiled in. */
|
||||
char *saved = g_strdup (g_getenv ("WAYLAND_DISPLAY"));
|
||||
g_unsetenv ("WAYLAND_DISPLAY");
|
||||
g_assert_cmpint (fe_get_idle_time (), >=, 0);
|
||||
g_setenv ("WAYLAND_DISPLAY", "wayland-test", TRUE);
|
||||
g_assert_cmpint (fe_get_idle_time (), ==, -1);
|
||||
if (saved)
|
||||
g_setenv ("WAYLAND_DISPLAY", saved, TRUE);
|
||||
else
|
||||
g_unsetenv ("WAYLAND_DISPLAY");
|
||||
g_free (saved);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
gtk_init_check (&argc, &argv);
|
||||
g_test_add_func ("/idle/user-input-and-dispatch", test_user_input_and_dispatch);
|
||||
g_test_add_func ("/idle/native-backend", test_native_idle);
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -1,633 +0,0 @@
|
||||
/* Native preferences editor regressions. GPL-2.0-or-later. */
|
||||
#include "../../common/zoitechat.h"
|
||||
#include <glib/gstdio.h>
|
||||
#include "../../common/fe.h"
|
||||
#include "../../common/util.h"
|
||||
#include "../preferences-pages.h"
|
||||
#include "../theme/theme-access.h"
|
||||
#include "../notifications/notification-backend.h"
|
||||
|
||||
static gboolean gtk_available, notifications_available;
|
||||
static int notification_calls, beep_calls, file_calls, sound_changes;
|
||||
static char *played_file, *opened_page;
|
||||
static char *opened_folder, *preview_mask;
|
||||
|
||||
/* External effects are recorded; tests drive the real native page widgets. */
|
||||
int notification_backend_supported (void) { return notifications_available; }
|
||||
void notification_backend_show (const char *title, const char *text) { notification_calls++; }
|
||||
void fe_beep (session *sess) { beep_calls++; }
|
||||
void sound_play (const char *file, gboolean quiet)
|
||||
{
|
||||
file_calls++;
|
||||
g_free (played_file);
|
||||
played_file = g_strdup (file);
|
||||
}
|
||||
void setup_open_page (const char *id)
|
||||
{
|
||||
g_free (opened_page);
|
||||
opened_page = g_strdup (id);
|
||||
}
|
||||
void setup_reveal_widget (GtkWidget *widget) { }
|
||||
void fe_open_url (const char *url)
|
||||
{
|
||||
g_free (opened_folder);
|
||||
opened_folder = g_strdup (url);
|
||||
}
|
||||
char *log_build_pathname (const char *mask, const char *config_dir,
|
||||
const char *server, const char *channel,
|
||||
const char *network, time_t now)
|
||||
{
|
||||
/* Record the resolver input and return a sentinel path, so these UI
|
||||
* tests verify the displayed result comes from the resolver. */
|
||||
g_free (preview_mask);
|
||||
preview_mask = g_strdup (mask);
|
||||
return g_build_filename (config_dir, "resolved-example.log", NULL);
|
||||
}
|
||||
gboolean theme_get_color (ThemeSemanticToken token, GdkRGBA *rgba)
|
||||
{
|
||||
return gdk_rgba_parse (rgba, token == THEME_TOKEN_TEXT_BACKGROUND ? "#20242a" : "#eeeeee");
|
||||
}
|
||||
int get_stamp_str (char *format, time_t stamp, char **result)
|
||||
{
|
||||
GDateTime *date = g_date_time_new_from_unix_local (stamp);
|
||||
*result = g_date_time_format (date, format);
|
||||
g_date_time_unref (date);
|
||||
return *result ? strlen (*result) : 0;
|
||||
}
|
||||
|
||||
static void changed (gpointer data) { sound_changes++; }
|
||||
|
||||
static GtkWidget *
|
||||
find_widget (GtkWidget *root, const char *name)
|
||||
{
|
||||
GtkWidget *found = NULL;
|
||||
if (!strcmp (gtk_widget_get_name (root), name)) return root;
|
||||
if (GTK_IS_BUTTON (root) && !g_strcmp0 (gtk_button_get_label (GTK_BUTTON (root)), name)) return root;
|
||||
if (!GTK_IS_CONTAINER (root)) return NULL;
|
||||
GList *children = gtk_container_get_children (GTK_CONTAINER (root));
|
||||
for (GList *i = children; i && !found; i = i->next) found = find_widget (i->data, name);
|
||||
g_list_free (children);
|
||||
return found;
|
||||
}
|
||||
|
||||
static GtkWidget *control (GtkWidget *root, const char *name)
|
||||
{
|
||||
GtkWidget *widget = find_widget (root, name);
|
||||
g_assert_nonnull (widget);
|
||||
return widget;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_page (GtkWidget *page)
|
||||
{
|
||||
g_object_ref_sink (page);
|
||||
gtk_widget_destroy (page);
|
||||
g_object_unref (page);
|
||||
}
|
||||
|
||||
static void
|
||||
defaults (struct zoitechatprefs *prefs)
|
||||
{
|
||||
memset (prefs, 0, sizeof *prefs);
|
||||
g_strlcpy (prefs->hex_text_font, "Monospace 11", sizeof prefs->hex_text_font);
|
||||
#ifdef WIN32
|
||||
g_strlcpy (prefs->hex_text_font_main, "Monospace 11", sizeof prefs->hex_text_font_main);
|
||||
#endif
|
||||
g_strlcpy (prefs->hex_stamp_text_format, "[%H:%M:%S] ", sizeof prefs->hex_stamp_text_format);
|
||||
prefs->hex_gui_tab_layout = 2;
|
||||
prefs->hex_gui_tab_pos = 3;
|
||||
prefs->hex_text_indent = 1;
|
||||
prefs->hex_text_show_marker = 1;
|
||||
prefs->hex_stamp_text = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
test_open_preserves_values (void)
|
||||
{
|
||||
struct zoitechatprefs prefs, original;
|
||||
GtkWidget *page;
|
||||
char *sound = NULL;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
/* Unknown legacy values and formats must survive merely visiting a page. */
|
||||
prefs.hex_gui_tab_layout = 1;
|
||||
prefs.hex_gui_tab_pos = 7;
|
||||
prefs.hex_gui_tab_trunc = 123;
|
||||
prefs.hex_gui_tab_newtofront = 9;
|
||||
prefs.hex_text_wrap_align = 9;
|
||||
g_strlcpy (prefs.hex_stamp_text_format, "时刻 %H:%M ", sizeof prefs.hex_stamp_text_format);
|
||||
original = prefs;
|
||||
page = preferences_text_page_new (&prefs, NULL);
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
destroy_page (page);
|
||||
page = preferences_tabs_page_new (&prefs);
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
destroy_page (page);
|
||||
page = preferences_notifications_page_new (&prefs, 0, &sound, changed, NULL);
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
g_assert_null (sound);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_timestamp_and_dependencies (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page, *style, *format;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
g_strlcpy (prefs.hex_stamp_text_format, "Custom %H:%M ", sizeof prefs.hex_stamp_text_format);
|
||||
page = preferences_text_page_new (&prefs, NULL);
|
||||
style = control (page, "timestamp-style"); format = control (page, "timestamp-format");
|
||||
g_assert_cmpstr (gtk_combo_box_get_active_id (GTK_COMBO_BOX (style)), ==, "custom");
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (style), "%H:%M ");
|
||||
g_assert_cmpstr (prefs.hex_stamp_text_format, ==, "%H:%M ");
|
||||
g_assert_false (gtk_widget_is_sensitive (format));
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (style), "custom");
|
||||
g_assert_cmpstr (prefs.hex_stamp_text_format, ==, "Custom %H:%M ");
|
||||
gtk_entry_set_text (GTK_ENTRY (format), "[%H:%M] ");
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "timestamps")), FALSE);
|
||||
g_assert_false (gtk_widget_is_sensitive (format));
|
||||
g_assert_cmpstr (prefs.hex_stamp_text_format, ==, "[%H:%M] ");
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "indent-nicks")), FALSE);
|
||||
g_assert_true (gtk_widget_is_sensitive (control (page, "wrap-alignment")));
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (control (page, "wrap-alignment")), 2);
|
||||
g_assert_cmpint (prefs.hex_text_wrap_align, ==, 2);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "indent-nicks")), TRUE);
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "wrap-alignment")));
|
||||
g_assert_cmpint (prefs.hex_text_wrap_align, ==, 2);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "inline-modes")), TRUE);
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "multiline-topic")));
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_preview_updates (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page;
|
||||
GtkTextBuffer *buffer;
|
||||
GtkTextIter start, end;
|
||||
char *text;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
page = preferences_text_page_new (&prefs, NULL);
|
||||
buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (control (page, "conversation-preview")));
|
||||
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||
text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
||||
g_assert_nonnull (strstr (text, "[14:35:08]"));
|
||||
g_assert_nonnull (strstr (text, "Unread messages"));
|
||||
g_free (text);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "timestamps")), FALSE);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "unread-marker")), FALSE);
|
||||
gtk_text_buffer_get_bounds (buffer, &start, &end);
|
||||
text = gtk_text_buffer_get_text (buffer, &start, &end, FALSE);
|
||||
g_assert_null (strstr (text, "14:35:08"));
|
||||
g_assert_null (strstr (text, "Unread messages"));
|
||||
g_free (text);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_layout_switching (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page, *tree_control, *tab_control;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
prefs.hex_gui_tab_closebuttons = 1;
|
||||
prefs.hex_gui_tab_dots = 1;
|
||||
page = preferences_tabs_page_new (&prefs);
|
||||
tree_control = control (page, "tree-dots"); tab_control = control (page, "tab-close-buttons");
|
||||
g_assert_true (gtk_widget_is_visible (tree_control));
|
||||
g_assert_false (gtk_widget_is_visible (tab_control));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "layout-tabs")), TRUE);
|
||||
g_assert_cmpint (prefs.hex_gui_tab_layout, ==, 0);
|
||||
g_assert_cmpint (prefs.hex_gui_tab_pos, ==, 6);
|
||||
g_assert_true (gtk_widget_is_visible (tab_control));
|
||||
g_assert_false (gtk_widget_is_visible (tree_control));
|
||||
g_assert_cmpint (prefs.hex_gui_tab_dots, ==, 1);
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (control (page, "channel-placement")), 4);
|
||||
g_assert_cmpint (prefs.hex_gui_tab_pos, ==, 5);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "layout-tree")), TRUE);
|
||||
g_assert_cmpint (prefs.hex_gui_tab_pos, ==, 3);
|
||||
g_assert_cmpint (prefs.hex_gui_tab_closebuttons, ==, 1);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_notification_mapping (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page;
|
||||
char *sound = NULL;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
page = preferences_notifications_page_new (&prefs, 31, &sound, changed, NULL);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "mention-desktop")), TRUE);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "private-beep")), TRUE);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "channel-tray")), TRUE);
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "private-attention")), TRUE);
|
||||
g_assert_cmpint (prefs.hex_input_balloon_hilight, ==, 1);
|
||||
g_assert_cmpint (prefs.hex_input_balloon_chans, ==, 0);
|
||||
g_assert_cmpint (prefs.hex_input_beep_priv, ==, 1);
|
||||
g_assert_cmpint (prefs.hex_input_beep_hilight, ==, 0);
|
||||
g_assert_cmpint (prefs.hex_input_tray_chans, ==, 1);
|
||||
g_assert_cmpint (prefs.hex_input_tray_priv, ==, 0);
|
||||
g_assert_cmpint (prefs.hex_input_flash_priv, ==, 1);
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "tray-close")));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "tray-enabled")), TRUE);
|
||||
g_assert_true (gtk_widget_is_sensitive (control (page, "tray-close")));
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_shared_sound_and_effects (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page;
|
||||
char *sound = g_strdup ("original.wav");
|
||||
if (!gtk_available) { g_free (sound); g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
page = preferences_notifications_page_new (&prefs, 31, &sound, changed, NULL);
|
||||
sound_changes = beep_calls = file_calls = 0;
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "alert-sound-file")), "new.wav");
|
||||
g_assert_cmpstr (sound, ==, "new.wav");
|
||||
g_assert_cmpint (sound_changes, ==, 1);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "Play")));
|
||||
g_assert_cmpstr (played_file, ==, "new.wav");
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "Use system sound")));
|
||||
g_assert_cmpstr (sound, ==, "");
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "Play")));
|
||||
g_assert_cmpint (beep_calls, ==, 1);
|
||||
g_free (sound); sound = g_strdup ("changed-in-sounds.wav");
|
||||
preferences_notifications_refresh_sound (page);
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (control (page, "alert-sound-file"))), ==, sound);
|
||||
g_assert_cmpint (sound_changes, ==, 2);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "open-event-sounds")));
|
||||
g_assert_cmpstr (opened_page, ==, "sounds");
|
||||
destroy_page (page);
|
||||
g_free (sound);
|
||||
}
|
||||
|
||||
static void
|
||||
test_notification_test_safety (void)
|
||||
{
|
||||
struct zoitechatprefs prefs, original;
|
||||
GtkWidget *page;
|
||||
char *sound = NULL;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
prefs.hex_away_omit_alerts = prefs.hex_gui_focus_omitalerts = 1;
|
||||
original = prefs;
|
||||
notifications_available = TRUE; notification_calls = 0;
|
||||
page = preferences_notifications_page_new (&prefs, 31, &sound, changed, NULL);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "notification-test")));
|
||||
g_assert_cmpint (notification_calls, ==, 1);
|
||||
g_assert_nonnull (strstr (gtk_label_get_text (GTK_LABEL (control (page, "notification-test-status"))), "marked away"));
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
notifications_available = FALSE;
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "notification-test")));
|
||||
g_assert_cmpint (notification_calls, ==, 1);
|
||||
destroy_page (page);
|
||||
page = preferences_notifications_page_new (&prefs, 0, &sound, changed, NULL);
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "notification-test")));
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "private-desktop")));
|
||||
g_assert_null (find_widget (page, "private-tray"));
|
||||
g_assert_null (find_widget (page, "private-attention"));
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_history_preserves_values (void)
|
||||
{
|
||||
struct zoitechatprefs prefs, original;
|
||||
GtkWidget *page;
|
||||
char *config, *history;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
config = g_dir_make_tmp ("zoitechat-history-ui-XXXXXX", NULL);
|
||||
history = g_build_filename (config, "scrollback", NULL);
|
||||
g_strlcpy (prefs.hex_irc_logmask, "custom-%s-%Y-%m.log", sizeof prefs.hex_irc_logmask);
|
||||
g_strlcpy (prefs.hex_stamp_log_format, "Custom %H:%M ", sizeof prefs.hex_stamp_log_format);
|
||||
prefs.hex_text_max_lines = 120000;
|
||||
prefs.hex_url_grabber_limit = 12000;
|
||||
original = prefs;
|
||||
page = preferences_history_page_new (&prefs, config);
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
g_assert_false (g_file_test (history, G_FILE_TEST_EXISTS));
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "open-history-folder")));
|
||||
g_assert_cmpstr (gtk_combo_box_get_active_id (GTK_COMBO_BOX (control (page, "log-file-style"))), ==, "custom");
|
||||
char *expected = g_build_filename (config, "resolved-example.log", NULL);
|
||||
g_assert_cmpstr (gtk_label_get_text (GTK_LABEL (control (page, "log-example-file"))), ==, expected);
|
||||
g_free (expected);
|
||||
destroy_page (page);
|
||||
g_rmdir (config);
|
||||
g_free (config); g_free (history);
|
||||
}
|
||||
|
||||
static void
|
||||
test_history_presets_and_custom (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page, *combo;
|
||||
char *mask, *expected;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs); prefs.hex_irc_logging = 1;
|
||||
mask = g_build_filename (g_get_tmp_dir (), "100%%done", "%s-custom.log", NULL);
|
||||
g_strlcpy (prefs.hex_irc_logmask, mask, sizeof prefs.hex_irc_logmask);
|
||||
page = preferences_history_page_new (&prefs, g_get_tmp_dir ());
|
||||
combo = control (page, "log-file-style");
|
||||
gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 1);
|
||||
expected = g_build_filename (g_get_tmp_dir (), "100%%done", "%n", "%Y-%m", "%c.log", NULL);
|
||||
g_assert_cmpstr (prefs.hex_irc_logmask, ==, expected);
|
||||
g_assert_cmpstr (preview_mask, ==, expected);
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), "custom");
|
||||
g_assert_cmpstr (prefs.hex_irc_logmask, ==, mask);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "default-log-folder")));
|
||||
g_assert_cmpstr (prefs.hex_irc_logmask, ==, "%s-custom.log");
|
||||
g_free (expected); g_free (mask);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_history_dependencies_and_timestamps (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page, *combo;
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs);
|
||||
g_strlcpy (prefs.hex_irc_logmask, "%n" G_DIR_SEPARATOR_S "%c.log", sizeof prefs.hex_irc_logmask);
|
||||
g_strlcpy (prefs.hex_stamp_log_format, "Time %H:%M ", sizeof prefs.hex_stamp_log_format);
|
||||
prefs.hex_stamp_log = 1;
|
||||
prefs.hex_url_logging = 1;
|
||||
prefs.hex_url_grabber_limit = 91;
|
||||
page = preferences_history_page_new (&prefs, g_get_tmp_dir ());
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "choose-log-folder")));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "save-logs")), TRUE);
|
||||
g_assert_true (gtk_widget_is_sensitive (control (page, "choose-log-folder")));
|
||||
combo = control (page, "log-timestamp-style");
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), "%H:%M ");
|
||||
g_assert_cmpstr (prefs.hex_stamp_log_format, ==, "%H:%M ");
|
||||
g_assert_nonnull (strstr (gtk_label_get_text (GTK_LABEL (control (page, "log-example-line"))), "14:35"));
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (combo), "custom");
|
||||
g_assert_cmpstr (prefs.hex_stamp_log_format, ==, "Time %H:%M ");
|
||||
g_assert_cmpstr (prefs.hex_stamp_text_format, ==, "[%H:%M:%S] ");
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "log-timestamps")), FALSE);
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "log-timestamp-format")));
|
||||
g_assert_cmpstr (prefs.hex_stamp_log_format, ==, "Time %H:%M ");
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "link-limit")));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "collect-links")), TRUE);
|
||||
g_assert_true (gtk_widget_is_sensitive (control (page, "link-limit")));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "collect-links")), FALSE);
|
||||
g_assert_cmpint (prefs.hex_url_grabber_limit, ==, 91);
|
||||
g_assert_cmpint (prefs.hex_url_logging, ==, 1);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "open-data-folder")));
|
||||
g_assert_cmpstr (opened_folder, ==, g_get_tmp_dir ());
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_history_validation (void)
|
||||
{
|
||||
struct zoitechatprefs prefs;
|
||||
GtkWidget *page;
|
||||
GError *error = NULL;
|
||||
GString *long_format = g_string_new ("");
|
||||
if (!gtk_available) { g_string_free (long_format, TRUE); g_test_skip ("GTK display unavailable"); return; }
|
||||
defaults (&prefs); prefs.hex_irc_logging = 1;
|
||||
g_strlcpy (prefs.hex_irc_logmask, "old-custom.log", sizeof prefs.hex_irc_logmask);
|
||||
page = preferences_history_page_new (&prefs, g_get_tmp_dir ());
|
||||
g_assert_true (preferences_history_page_stage (page, &error));
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "log-file-format")), "");
|
||||
g_assert_false (preferences_history_page_stage (page, &error));
|
||||
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL); g_clear_error (&error);
|
||||
for (int i = 0; i < 140; i++) g_string_append (long_format, "é");
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "log-file-format")), long_format->str);
|
||||
g_assert_false (preferences_history_page_stage (page, &error));
|
||||
g_clear_error (&error);
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "log-file-format")), "corrected-%Y.log");
|
||||
g_assert_true (preferences_history_page_stage (page, &error));
|
||||
g_assert_no_error (error);
|
||||
destroy_page (page);
|
||||
g_string_free (long_format, TRUE);
|
||||
}
|
||||
|
||||
static GtkWidget *
|
||||
language_button (GtkWidget *root, const char *code)
|
||||
{
|
||||
GtkWidget *found = NULL;
|
||||
if (!g_strcmp0 (g_object_get_data (G_OBJECT (root), "language-code"), code)) return root;
|
||||
if (!GTK_IS_CONTAINER (root)) return NULL;
|
||||
GList *children = gtk_container_get_children (GTK_CONTAINER (root));
|
||||
for (GList *i = children; i && !found; i = i->next) found = language_button (i->data, code);
|
||||
g_list_free (children); return found;
|
||||
}
|
||||
|
||||
static void
|
||||
test_typing_preservation_and_languages (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
struct zoitechatprefs prefs, original;
|
||||
defaults (&prefs);
|
||||
prefs.hex_gui_input_spell = prefs.hex_gui_input_icon = 1;
|
||||
prefs.hex_completion_amount = 5;
|
||||
g_strlcpy (prefs.hex_text_spell_langs, "en_US, missing_XX", sizeof prefs.hex_text_spell_langs);
|
||||
g_strlcpy (prefs.hex_completion_suffix, ",", sizeof prefs.hex_completion_suffix);
|
||||
original = prefs;
|
||||
PreferencesSpellLanguage languages[] = {{"en_US", "English (United States)"}, {"en_CA", "English (Canada)"}};
|
||||
GtkWidget *page = preferences_typing_page_new (&prefs, gtk_entry_new (), languages, 2, NULL);
|
||||
g_assert_cmpmem (&prefs, sizeof prefs, &original, sizeof original);
|
||||
g_assert_true (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (language_button (page, "en_US"))));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (language_button (page, "en_CA")), TRUE);
|
||||
g_assert_nonnull (strstr (prefs.hex_text_spell_langs, "missing_XX"));
|
||||
g_assert_nonnull (strstr (prefs.hex_text_spell_langs, "en_CA"));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (language_button (page, "en_US")), FALSE);
|
||||
g_assert_null (strstr (prefs.hex_text_spell_langs, "en_US"));
|
||||
g_assert_nonnull (strstr (prefs.hex_text_spell_langs, "missing_XX"));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "spelling-enabled")), FALSE);
|
||||
g_assert_false (gtk_widget_is_sensitive (language_button (page, "en_CA")));
|
||||
g_assert_nonnull (strstr (prefs.hex_text_spell_langs, "en_CA"));
|
||||
g_assert_false (gtk_widget_is_sensitive (control (page, "input-mode-icon")));
|
||||
gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (control (page, "input-nickname")), TRUE);
|
||||
g_assert_true (gtk_widget_is_sensitive (control (page, "input-mode-icon")));
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_typing_completion_and_validation (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
struct zoitechatprefs prefs;
|
||||
defaults (&prefs); prefs.hex_completion_amount = 5;
|
||||
GtkWidget *page = preferences_typing_page_new (&prefs, gtk_entry_new (), NULL, 0, NULL);
|
||||
GtkWidget *result = control (page, "completion-example-input");
|
||||
gtk_entry_set_text (GTK_ENTRY (result), "Al");
|
||||
gtk_spin_button_set_value (GTK_SPIN_BUTTON (control (page, "completion-limit")), 0);
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "completion-suffix")), "✓");
|
||||
g_assert_cmpstr (prefs.hex_completion_suffix, ==, "✓");
|
||||
GError *error = NULL;
|
||||
g_assert_true (preferences_typing_page_stage (page, &error));
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "completion-suffix")), "😀");
|
||||
g_assert_false (preferences_typing_page_stage (page, &error));
|
||||
g_assert_cmpstr (prefs.hex_completion_suffix, ==, "✓");
|
||||
g_clear_error (&error);
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "completion-suffix")), ":");
|
||||
char *too_long = g_strnfill (64, 'a');
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "spelling-codes")), too_long);
|
||||
g_assert_false (preferences_typing_page_stage (page, &error));
|
||||
g_clear_error (&error); g_free (too_long);
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "spelling-codes")), "unavailable-code");
|
||||
g_assert_true (preferences_typing_page_stage (page, &error));
|
||||
g_assert_no_error (error);
|
||||
destroy_page (page);
|
||||
}
|
||||
|
||||
static GdkModifierType test_modifiers (GdkModifierType mods)
|
||||
{
|
||||
return mods & (GDK_SHIFT_MASK | GDK_CONTROL_MASK | GDK_MOD1_MASK | GDK_META_MASK);
|
||||
}
|
||||
static void test_restore (gpointer data) { }
|
||||
|
||||
static GtkWidget *
|
||||
shortcut_fixture (GtkListStore **store)
|
||||
{
|
||||
*store = gtk_list_store_new (6, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN);
|
||||
GtkTreeIter iter;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
gtk_list_store_append (*store, &iter);
|
||||
gtk_list_store_set (*store, &iter, 0, "Ctrl+K", 1, "<Control>k", 2, i == 1 ? "Change Page" : "Run Command",
|
||||
3, i == 0 ? "\002" : i == 1 ? "2" : "echo original\\nsecond", 4, i == 1 ? "Relative-custom-value" : NULL, 5, i != 0, -1);
|
||||
}
|
||||
PreferencesShortcutAction actions[] = {{"Run Command", "Run a command"}, {"Change Page", "Change a page"}};
|
||||
return preferences_shortcuts_page_new (*store, actions, 2, test_modifiers, test_restore, NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
test_shortcuts_filter_and_protection (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
GtkListStore *store;
|
||||
GtkWidget *page = shortcut_fixture (&store);
|
||||
GtkTreeIter iter;
|
||||
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
|
||||
char *data;
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 3, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "\002"); g_free (data);
|
||||
g_assert_false (gtk_widget_get_sensitive (control (page, "shortcut-action")));
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-remove")));
|
||||
g_assert_cmpint (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL), ==, 3);
|
||||
g_assert_nonnull (strstr (gtk_label_get_text (GTK_LABEL (control (page, "shortcut-shared"))), "3 bindings"));
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "shortcut-search")), "Relative-custom-value");
|
||||
g_signal_emit_by_name (control (page, "shortcut-search"), "search-changed");
|
||||
GtkTreeModel *filtered = gtk_tree_view_get_model (GTK_TREE_VIEW (control (page, "shortcuts-list")));
|
||||
g_assert_cmpint (gtk_tree_model_iter_n_children (filtered, NULL), ==, 1);
|
||||
g_assert_cmpint (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL), ==, 3);
|
||||
GtkTreePath *path = gtk_tree_path_new_from_indices (1, -1);
|
||||
preferences_shortcuts_reveal_path (page, path); gtk_tree_path_free (path);
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (control (page, "shortcut-data2"))), ==, "Relative-custom-value");
|
||||
gtk_combo_box_set_active_id (GTK_COMBO_BOX (control (page, "shortcut-action")), "Run Command");
|
||||
gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, NULL, 1);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 4, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "Relative-custom-value"); g_free (data);
|
||||
destroy_page (page); g_object_unref (store);
|
||||
}
|
||||
|
||||
static void
|
||||
test_shortcuts_duplicate_and_record (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
GtkListStore *store;
|
||||
GtkWidget *page = shortcut_fixture (&store);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-duplicate")));
|
||||
g_assert_cmpint (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL), ==, 4);
|
||||
gtk_entry_set_text (GTK_ENTRY (control (page, "shortcut-data1")), "echo changed\\nnext");
|
||||
GtkTreeIter iter; char *data;
|
||||
gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, NULL, 3);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 3, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "echo changed\\nnext"); g_free (data);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-record")));
|
||||
GtkWidget *dialog = NULL;
|
||||
GList *tops = gtk_window_list_toplevels ();
|
||||
for (GList *i = tops; i; i = i->next) if (!strcmp (gtk_widget_get_name (i->data), "shortcut-recorder")) dialog = i->data;
|
||||
g_list_free (tops); g_assert_nonnull (dialog);
|
||||
GtkWidget *view = control (dialog, "GtkTreeView");
|
||||
GList *cells = gtk_cell_layout_get_cells (GTK_CELL_LAYOUT (gtk_tree_view_get_column (GTK_TREE_VIEW (view), 0)));
|
||||
g_signal_emit_by_name (cells->data, "accel-edited", "0", GDK_KEY_Tab, GDK_SHIFT_MASK | GDK_LOCK_MASK, 23);
|
||||
g_list_free (cells);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 1, &data, -1);
|
||||
guint key; GdkModifierType mods;
|
||||
gtk_accelerator_parse (data, &key, &mods);
|
||||
g_assert_cmpuint (key, ==, GDK_KEY_ISO_Left_Tab);
|
||||
g_assert_cmpuint (mods, ==, GDK_SHIFT_MASK); g_free (data);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-remove")));
|
||||
g_assert_cmpint (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store), NULL), ==, 3);
|
||||
destroy_page (page); g_object_unref (store);
|
||||
}
|
||||
|
||||
static void
|
||||
test_shortcuts_order_and_recorder_close (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
GtkListStore *store;
|
||||
GtkWidget *page = shortcut_fixture (&store);
|
||||
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
g_object_ref_sink (window);
|
||||
gtk_container_add (GTK_CONTAINER (window), page);
|
||||
gtk_widget_show_all (window);
|
||||
GtkTreePath *path = gtk_tree_path_new_from_indices (2, -1);
|
||||
preferences_shortcuts_reveal_path (page, path); gtk_tree_path_free (path);
|
||||
GdkEventKey event = {0};
|
||||
event.type = GDK_KEY_PRESS; event.keyval = GDK_KEY_Up; event.state = GDK_SHIFT_MASK;
|
||||
gboolean handled = FALSE;
|
||||
g_signal_emit_by_name (control (page, "shortcuts-list"), "key-press-event", &event, &handled);
|
||||
g_assert_true (handled);
|
||||
GtkTreeIter iter; char *data;
|
||||
gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, NULL, 1);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 3, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "echo original\\nsecond"); g_free (data);
|
||||
g_signal_emit_by_name (control (page, "shortcuts-list"), "key-press-event", &event, &handled);
|
||||
gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 3, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "\002"); g_free (data);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-record")));
|
||||
GList *tops = gtk_window_list_toplevels ();
|
||||
GtkWidget *dialog = NULL;
|
||||
for (GList *i = tops; i; i = i->next) if (!strcmp (gtk_widget_get_name (i->data), "shortcut-recorder")) dialog = i->data;
|
||||
g_list_free (tops); g_assert_nonnull (dialog);
|
||||
gtk_dialog_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL);
|
||||
gtk_tree_model_iter_nth_child (GTK_TREE_MODEL (store), &iter, NULL, 1);
|
||||
gtk_tree_model_get (GTK_TREE_MODEL (store), &iter, 1, &data, -1);
|
||||
g_assert_cmpstr (data, ==, "<Control>k"); g_free (data);
|
||||
gtk_button_clicked (GTK_BUTTON (control (page, "shortcut-record")));
|
||||
gtk_widget_destroy (window);
|
||||
g_object_unref (window); g_object_unref (store);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
gtk_available = gtk_init_check (&argc, &argv);
|
||||
g_test_add_func ("/preferences/pages/open-preserves-values", test_open_preserves_values);
|
||||
g_test_add_func ("/preferences/pages/timestamps-and-dependencies", test_timestamp_and_dependencies);
|
||||
g_test_add_func ("/preferences/pages/conversation-preview", test_preview_updates);
|
||||
g_test_add_func ("/preferences/pages/layout-switching", test_layout_switching);
|
||||
g_test_add_func ("/preferences/pages/notification-mapping", test_notification_mapping);
|
||||
g_test_add_func ("/preferences/pages/shared-sound", test_shared_sound_and_effects);
|
||||
g_test_add_func ("/preferences/pages/notification-test", test_notification_test_safety);
|
||||
g_test_add_func ("/preferences/history/open-preserves-values", test_history_preserves_values);
|
||||
g_test_add_func ("/preferences/history/presets-and-custom", test_history_presets_and_custom);
|
||||
g_test_add_func ("/preferences/history/dependencies-and-timestamps", test_history_dependencies_and_timestamps);
|
||||
g_test_add_func ("/preferences/history/validation", test_history_validation);
|
||||
g_test_add_func ("/preferences/typing/languages-preserved", test_typing_preservation_and_languages);
|
||||
g_test_add_func ("/preferences/typing/completion-validation", test_typing_completion_and_validation);
|
||||
g_test_add_func ("/preferences/shortcuts/filter-and-protection", test_shortcuts_filter_and_protection);
|
||||
g_test_add_func ("/preferences/shortcuts/duplicate-and-record", test_shortcuts_duplicate_and_record);
|
||||
g_test_add_func ("/preferences/shortcuts/order-and-recorder-close", test_shortcuts_order_and_recorder_close);
|
||||
int result = g_test_run ();
|
||||
g_free (played_file); g_free (opened_page);
|
||||
g_free (opened_folder); g_free (preview_mask);
|
||||
return result;
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
/* ZoiteChat
|
||||
* Copyright (C) 1998-2010 Peter Zelezny.
|
||||
* Copyright (C) 2009-2013 Berke Viktor.
|
||||
* Copyright (C) 2026 deepend-tildeclub.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*/
|
||||
|
||||
/* Regression coverage for saving multiple Preferences editors together. */
|
||||
#include "../preferences-persistence.h"
|
||||
#include "../../common/cfgfiles.h"
|
||||
#include "../theme/theme-runtime.h"
|
||||
#include <errno.h>
|
||||
#include <glib/gstdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static char *test_directory;
|
||||
static gboolean fail_theme_prepare;
|
||||
static int rename_count, fail_install, fail_restore;
|
||||
|
||||
char *get_xdir (void) { return test_directory; }
|
||||
|
||||
static gboolean
|
||||
prepare (const char *name, const char *contents, char **path)
|
||||
{
|
||||
*path = g_build_filename (test_directory, name, NULL);
|
||||
return g_file_set_contents (*path, contents, -1, NULL);
|
||||
}
|
||||
|
||||
int save_config_prepare (char **path) { return prepare ("config.new", "new config", path); }
|
||||
gboolean theme_runtime_save_prepare (char **path)
|
||||
{
|
||||
if (fail_theme_prepare) return FALSE;
|
||||
return prepare ("theme.new", "new theme", path);
|
||||
}
|
||||
|
||||
static int
|
||||
checked_rename (const char *source, const char *destination)
|
||||
{
|
||||
rename_count++;
|
||||
if (rename_count == fail_install || rename_count == fail_restore)
|
||||
{
|
||||
errno = EACCES;
|
||||
return -1;
|
||||
}
|
||||
return g_rename (source, destination);
|
||||
}
|
||||
|
||||
/* Exercise replacement failures without relying on filesystem permissions. */
|
||||
#undef g_rename
|
||||
#define g_rename checked_rename
|
||||
#include "../preferences-persistence.c"
|
||||
#undef g_rename
|
||||
|
||||
static void
|
||||
put (const char *name, const char *contents)
|
||||
{
|
||||
char *path = g_build_filename (test_directory, name, NULL);
|
||||
g_assert_true (g_file_set_contents (path, contents, -1, NULL));
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
static void
|
||||
assert_contents (const char *name, const char *expected)
|
||||
{
|
||||
char *path = g_build_filename (test_directory, name, NULL), *contents = NULL;
|
||||
g_assert_true (g_file_get_contents (path, &contents, NULL, NULL));
|
||||
g_assert_cmpstr (contents, ==, expected);
|
||||
g_free (contents);
|
||||
g_free (path);
|
||||
}
|
||||
|
||||
static void
|
||||
begin (void)
|
||||
{
|
||||
test_directory = g_dir_make_tmp ("zoitechat-preferences-XXXXXX", NULL);
|
||||
g_assert_nonnull (test_directory);
|
||||
rename_count = fail_install = fail_restore = 0;
|
||||
fail_theme_prepare = FALSE;
|
||||
put ("zoitechat.conf", "old config");
|
||||
put ("colors.conf", "old theme");
|
||||
put ("commands.conf", "old commands");
|
||||
preferences_persistence_stage_file ("commands.conf", "new commands");
|
||||
preferences_persistence_stage_file ("replace.conf", "new replacements");
|
||||
}
|
||||
|
||||
static int
|
||||
temporary_count (void)
|
||||
{
|
||||
GDir *directory = g_dir_open (test_directory, 0, NULL);
|
||||
const char *name;
|
||||
int count = 0;
|
||||
while ((name = g_dir_read_name (directory)))
|
||||
if (g_str_has_prefix (name, ".preferences-")) count++;
|
||||
g_dir_close (directory);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void
|
||||
end (void)
|
||||
{
|
||||
GDir *directory = g_dir_open (test_directory, 0, NULL);
|
||||
const char *name;
|
||||
preferences_persistence_clear_staged_files ();
|
||||
while ((name = g_dir_read_name (directory)))
|
||||
{
|
||||
char *path = g_build_filename (test_directory, name, NULL);
|
||||
if (g_file_test (path, G_FILE_TEST_IS_DIR)) g_rmdir (path);
|
||||
else g_unlink (path);
|
||||
g_free (path);
|
||||
}
|
||||
g_dir_close (directory);
|
||||
g_rmdir (test_directory);
|
||||
g_free (test_directory);
|
||||
}
|
||||
|
||||
static void
|
||||
test_success (void)
|
||||
{
|
||||
PreferencesPersistenceResult result;
|
||||
begin ();
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_true (result.success);
|
||||
assert_contents ("zoitechat.conf", "new config");
|
||||
assert_contents ("colors.conf", "new theme");
|
||||
assert_contents ("commands.conf", "new commands");
|
||||
assert_contents ("replace.conf", "new replacements");
|
||||
g_assert_cmpint (temporary_count (), ==, 0);
|
||||
end ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_prepare_failure (void)
|
||||
{
|
||||
PreferencesPersistenceResult result;
|
||||
begin ();
|
||||
fail_theme_prepare = TRUE;
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_false (result.success);
|
||||
g_assert_false (result.partial_failure);
|
||||
g_assert_true (result.theme_failed);
|
||||
assert_contents ("zoitechat.conf", "old config");
|
||||
assert_contents ("commands.conf", "old commands");
|
||||
g_assert_cmpint (rename_count, ==, 0);
|
||||
g_assert_cmpint (temporary_count (), ==, 0);
|
||||
end ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_backup_failure (void)
|
||||
{
|
||||
PreferencesPersistenceResult result;
|
||||
char *path;
|
||||
begin ();
|
||||
path = g_build_filename (test_directory, "replace.conf", NULL);
|
||||
g_assert_cmpint (g_mkdir (path, 0700), ==, 0);
|
||||
g_free (path);
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_false (result.success);
|
||||
g_assert_cmpstr (result.failed_file, ==, "replace.conf");
|
||||
assert_contents ("zoitechat.conf", "old config");
|
||||
assert_contents ("commands.conf", "old commands");
|
||||
g_assert_cmpint (rename_count, ==, 0);
|
||||
g_assert_cmpint (temporary_count (), ==, 0);
|
||||
end ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_rollback_and_retry (void)
|
||||
{
|
||||
PreferencesPersistenceResult result;
|
||||
begin ();
|
||||
fail_install = 4;
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_false (result.success);
|
||||
g_assert_false (result.partial_failure);
|
||||
g_assert_true (result.retry_possible);
|
||||
assert_contents ("zoitechat.conf", "old config");
|
||||
assert_contents ("colors.conf", "old theme");
|
||||
assert_contents ("commands.conf", "old commands");
|
||||
g_assert_cmpint (temporary_count (), ==, 0);
|
||||
fail_install = 0;
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_true (result.success);
|
||||
assert_contents ("commands.conf", "new commands");
|
||||
end ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_recovery_copy (void)
|
||||
{
|
||||
PreferencesPersistenceResult result;
|
||||
begin ();
|
||||
fail_install = 4;
|
||||
fail_restore = 5;
|
||||
result = preferences_persistence_save_all ();
|
||||
g_assert_false (result.success);
|
||||
g_assert_true (result.partial_failure);
|
||||
g_assert_cmpint (temporary_count (), ==, 1);
|
||||
assert_contents ("zoitechat.conf", "old config");
|
||||
assert_contents ("colors.conf", "old theme");
|
||||
end ();
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
g_test_add_func ("/preferences/save/success", test_success);
|
||||
g_test_add_func ("/preferences/save/prepare-failure", test_prepare_failure);
|
||||
g_test_add_func ("/preferences/save/backup-failure", test_backup_failure);
|
||||
g_test_add_func ("/preferences/save/rollback-retry", test_rollback_and_retry);
|
||||
g_test_add_func ("/preferences/save/recovery-copy", test_recovery_copy);
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
/* Spell-entry dictionary reload regressions. GPL-2.0-or-later. */
|
||||
#include "../../common/zoitechat.h"
|
||||
|
||||
/* Exercise the actual widget with a deterministic dictionary provider. This
|
||||
* must also run on builders without Enchant or installed dictionaries. */
|
||||
#include "../sexy-spell-entry.c"
|
||||
|
||||
struct zoitechatprefs prefs;
|
||||
struct EnchantBroker { int unused; };
|
||||
struct EnchantDict { char *language; };
|
||||
static gboolean gtk_available, dictionaries_available;
|
||||
static int active_dicts, checked_words;
|
||||
|
||||
gboolean theme_get_color (ThemeSemanticToken token, GdkRGBA *color)
|
||||
{
|
||||
return gdk_rgba_parse (color, token == THEME_TOKEN_TEXT_BACKGROUND ? "#ffffff" : "#000000");
|
||||
}
|
||||
gboolean theme_get_mirc_color (unsigned int index, GdkRGBA *color)
|
||||
{
|
||||
return gdk_rgba_parse (color, "#000000");
|
||||
}
|
||||
void theme_get_widget_style_values_for_widget (GtkWidget *widget, ThemeWidgetStyleValues *values)
|
||||
{
|
||||
memset (values, 0, sizeof *values);
|
||||
gdk_rgba_parse (&values->background, "#ffffff");
|
||||
}
|
||||
void theme_palette_color_get_rgb16 (const GdkRGBA *color, guint16 *red, guint16 *green, guint16 *blue)
|
||||
{
|
||||
*red = color->red * 65535;
|
||||
*green = color->green * 65535;
|
||||
*blue = color->blue * 65535;
|
||||
}
|
||||
const char *gtkutil_icon_name_from_stock (const char *stock) { return stock; }
|
||||
GtkWidget *gtkutil_image_new_from_stock (const char *stock, GtkIconSize size)
|
||||
{
|
||||
return gtk_image_new_from_icon_name (stock, size);
|
||||
}
|
||||
|
||||
#ifdef HAVE_ISO_CODES
|
||||
void codetable_init (void) { }
|
||||
void codetable_free (void) { }
|
||||
void codetable_lookup (const gchar *code, const gchar **language, const gchar **country)
|
||||
{
|
||||
*language = code;
|
||||
*country = "";
|
||||
}
|
||||
#endif
|
||||
|
||||
static struct EnchantBroker *test_broker_new (void) { return g_new0 (struct EnchantBroker, 1); }
|
||||
static void test_broker_free (struct EnchantBroker *broker) { g_free (broker); }
|
||||
static void test_list_dicts (struct EnchantBroker *broker, EnchantDictDescribeFn describe, void *data)
|
||||
{
|
||||
if (dictionaries_available) describe ("en_US", "test", "test", "", data);
|
||||
}
|
||||
static struct EnchantDict *test_request_dict (struct EnchantBroker *broker, const char *const language)
|
||||
{
|
||||
if (!dictionaries_available || (strcmp (language, "en_US") && strcmp (language, "en"))) return NULL;
|
||||
struct EnchantDict *dict = g_new0 (struct EnchantDict, 1);
|
||||
dict->language = g_strdup (language);
|
||||
active_dicts++;
|
||||
return dict;
|
||||
}
|
||||
static void test_free_dict (struct EnchantBroker *broker, struct EnchantDict *dict)
|
||||
{
|
||||
g_free (dict->language);
|
||||
g_free (dict);
|
||||
active_dicts--;
|
||||
}
|
||||
static void test_describe_dict (struct EnchantDict *dict, EnchantDictDescribeFn describe, void *data)
|
||||
{
|
||||
describe (dict->language, "test", "test", "", data);
|
||||
}
|
||||
static void test_add_word (struct EnchantDict *dict, const char *const word, ssize_t length) { }
|
||||
static int test_check_word (struct EnchantDict *dict, const char *const word, ssize_t length)
|
||||
{
|
||||
checked_words++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static SexySpellEntry *
|
||||
new_input (gboolean available)
|
||||
{
|
||||
dictionaries_available = available;
|
||||
memset (&prefs, 0, sizeof prefs);
|
||||
g_strlcpy (prefs.hex_text_spell_langs, "missing_XX", sizeof prefs.hex_text_spell_langs);
|
||||
SexySpellEntry *entry = SEXY_SPELL_ENTRY (sexy_spell_entry_new ());
|
||||
g_object_ref_sink (entry);
|
||||
return entry;
|
||||
}
|
||||
|
||||
static void
|
||||
destroy_input (SexySpellEntry *entry)
|
||||
{
|
||||
gtk_widget_destroy (GTK_WIDGET (entry));
|
||||
g_object_unref (entry);
|
||||
g_assert_cmpint (active_dicts, ==, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_first_dictionary_on_empty_input (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
SexySpellEntry *entry = new_input (FALSE);
|
||||
g_assert_null (entry->priv->dict_list);
|
||||
/* The real Save path: an untouched input, no old dictionary, then a newly
|
||||
* selected dictionary. Before the fix, the final recheck dereferenced NULL. */
|
||||
dictionaries_available = TRUE;
|
||||
g_strlcpy (prefs.hex_text_spell_langs, "en_US", sizeof prefs.hex_text_spell_langs);
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
sexy_spell_entry_deactivate_language (entry, NULL);
|
||||
sexy_spell_entry_activate_default_languages (entry);
|
||||
g_assert_true (sexy_spell_entry_language_is_active (entry, "en_US"));
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, "");
|
||||
}
|
||||
destroy_input (entry);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reload_preserves_draft (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
SexySpellEntry *entry = new_input (FALSE);
|
||||
const char *draft = "Hello café";
|
||||
gtk_entry_set_text (GTK_ENTRY (entry), draft);
|
||||
dictionaries_available = TRUE;
|
||||
g_strlcpy (prefs.hex_text_spell_langs, "en_US", sizeof prefs.hex_text_spell_langs);
|
||||
checked_words = 0;
|
||||
sexy_spell_entry_deactivate_language (entry, NULL);
|
||||
sexy_spell_entry_activate_default_languages (entry);
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, draft);
|
||||
g_assert_cmpint (checked_words, >, 0);
|
||||
/* Save-failure rollback and retry also reload the input dictionaries. */
|
||||
dictionaries_available = FALSE;
|
||||
sexy_spell_entry_deactivate_language (entry, NULL);
|
||||
sexy_spell_entry_activate_default_languages (entry);
|
||||
dictionaries_available = TRUE;
|
||||
sexy_spell_entry_activate_default_languages (entry);
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (entry)), ==, draft);
|
||||
destroy_input (entry);
|
||||
}
|
||||
|
||||
static void
|
||||
test_empty_input_spelling_toggles (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_skip ("GTK display unavailable"); return; }
|
||||
SexySpellEntry *entry = new_input (TRUE);
|
||||
sexy_spell_entry_set_parse_attributes (entry, FALSE);
|
||||
sexy_spell_entry_set_checked (entry, FALSE);
|
||||
sexy_spell_entry_set_checked (entry, TRUE);
|
||||
sexy_spell_entry_set_parse_attributes (entry, TRUE);
|
||||
sexy_spell_entry_deactivate_language (entry, NULL);
|
||||
sexy_spell_entry_activate_default_languages (entry);
|
||||
g_assert_true (sexy_spell_entry_language_is_active (entry, "en"));
|
||||
destroy_input (entry);
|
||||
}
|
||||
|
||||
int main (int argc, char **argv)
|
||||
{
|
||||
g_test_init (&argc, &argv, NULL);
|
||||
gtk_available = gtk_init_check (&argc, &argv);
|
||||
if (gtk_available)
|
||||
{
|
||||
SexySpellEntryClass *klass = g_type_class_ref (SEXY_TYPE_SPELL_ENTRY);
|
||||
have_enchant = TRUE;
|
||||
klass->word_check = default_word_check;
|
||||
enchant_broker_init = test_broker_new;
|
||||
enchant_broker_free = test_broker_free;
|
||||
enchant_broker_list_dicts = test_list_dicts;
|
||||
enchant_broker_request_dict = test_request_dict;
|
||||
enchant_broker_free_dict = test_free_dict;
|
||||
enchant_dict_describe = test_describe_dict;
|
||||
enchant_dict_add_to_session = test_add_word;
|
||||
enchant_dict_check = test_check_word;
|
||||
}
|
||||
g_test_add_func ("/spell-entry/first-dictionary-empty-input", test_first_dictionary_on_empty_input);
|
||||
g_test_add_func ("/spell-entry/reload-preserves-draft", test_reload_preserves_draft);
|
||||
g_test_add_func ("/spell-entry/empty-input-spelling-toggles", test_empty_input_spelling_toggles);
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -211,70 +211,6 @@ static void test_formatting (Fixture *f, gconstpointer data)
|
||||
"nick a\002bold\002 \035italic\035 \00304,02red\017 \037u\037 \036s\036 \026r\026");
|
||||
}
|
||||
|
||||
static void
|
||||
assert_same_xtext_pixels (cairo_surface_t *a, cairo_surface_t *b)
|
||||
{
|
||||
g_assert_cmpint (cairo_image_surface_get_width (a), ==, cairo_image_surface_get_width (b));
|
||||
g_assert_cmpint (cairo_image_surface_get_height (a), ==, cairo_image_surface_get_height (b));
|
||||
g_assert_cmpint (cairo_image_surface_get_stride (a), ==, cairo_image_surface_get_stride (b));
|
||||
g_assert_cmpint (memcmp (cairo_image_surface_get_data (a), cairo_image_surface_get_data (b),
|
||||
cairo_image_surface_get_stride (a) * cairo_image_surface_get_height (a)), ==, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
test_timestamp_indent_toggle (Fixture *f, gconstpointer data)
|
||||
{
|
||||
xtext_buffer *buf = f->xtext->buffer;
|
||||
cairo_surface_t *before, *after, *hidden;
|
||||
char *selection;
|
||||
int length;
|
||||
|
||||
/* Previously indented entries must retain their margin timestamps. */
|
||||
gtk_xtext_set_indent (f->xtext, TRUE);
|
||||
gtk_xtext_set_time_stamp (buf, TRUE);
|
||||
buf->indent = 130;
|
||||
gtk_xtext_append_indent (buf, (guchar *)"nick", -1, (guchar *)"hello", -1, 1234);
|
||||
gtk_xtext_recalc_widths (buf, FALSE);
|
||||
before = snapshot (f, FALSE);
|
||||
gtk_xtext_set_indent (f->xtext, FALSE);
|
||||
after = snapshot (f, FALSE);
|
||||
assert_same_xtext_pixels (before, after);
|
||||
|
||||
/* Check that a visible stamp, not just an unchanged blank margin,
|
||||
* was preserved. */
|
||||
gtk_xtext_set_time_stamp (buf, FALSE);
|
||||
hidden = snapshot (f, FALSE);
|
||||
g_assert_cmpint (memcmp (cairo_image_surface_get_data (after),
|
||||
cairo_image_surface_get_data (hidden),
|
||||
cairo_image_surface_get_stride (after) * cairo_image_surface_get_height (after)), !=, 0);
|
||||
cairo_surface_destroy (hidden);
|
||||
cairo_surface_destroy (after);
|
||||
cairo_surface_destroy (before);
|
||||
|
||||
/* Entries already containing an inline stamp must not gain a second
|
||||
* margin stamp after enabling indentation. */
|
||||
gtk_xtext_clear (buf, 0);
|
||||
gtk_xtext_set_time_stamp (buf, TRUE);
|
||||
gtk_xtext_append_wrap (buf, (guchar *)"[12:34] nick hello", -1, 1234, 8);
|
||||
before = snapshot (f, FALSE);
|
||||
gtk_xtext_set_indent (f->xtext, TRUE);
|
||||
after = snapshot (f, FALSE);
|
||||
assert_same_xtext_pixels (before, after);
|
||||
cairo_surface_destroy (after);
|
||||
cairo_surface_destroy (before);
|
||||
|
||||
/* The same inline stamp must not be duplicated when copied. */
|
||||
buf->text_last->mark_start = 0;
|
||||
buf->text_last->mark_end = buf->text_last->str_len;
|
||||
buf->last_ent_start = buf->text_last;
|
||||
buf->last_ent_end = buf->text_last;
|
||||
f->xtext->selection_buffer = buf;
|
||||
f->xtext->mark_stamp = TRUE;
|
||||
selection = gtk_xtext_selection_get_text (f->xtext, &length);
|
||||
g_assert_cmpstr (selection, ==, "[12:34] nick hello");
|
||||
g_free (selection);
|
||||
}
|
||||
|
||||
static void select_entries (Fixture *f, textentry *start, textentry *end)
|
||||
{
|
||||
textentry *ent;
|
||||
@@ -1142,7 +1078,6 @@ int main (int argc, char **argv)
|
||||
ADD ("layout/font-isolation", test_font_isolation);
|
||||
ADD ("layout/wide-metrics", test_wide_metrics);
|
||||
ADD ("render/formatting-timestamps", test_formatting);
|
||||
ADD ("render/timestamps-after-indent-toggle", test_timestamp_indent_toggle);
|
||||
ADD ("selection/clipboard", test_selection_and_clipboard);
|
||||
ADD ("selection/macos-command-copy", test_macos_chat_copy);
|
||||
ADD ("search/navigation-regex-clear", test_search);
|
||||
|
||||
+57
-248
@@ -38,8 +38,6 @@
|
||||
#include "theme/theme-access.h"
|
||||
#include "theme/theme-manager.h"
|
||||
#include "textgui.h"
|
||||
#include "setup.h"
|
||||
#include "preferences-persistence.h"
|
||||
|
||||
#define ICON_TEXTEVENT_SAVE_AS "document-save-as"
|
||||
#define ICON_TEXTEVENT_OPEN "document-open"
|
||||
@@ -52,10 +50,6 @@ extern char *pntevts[];
|
||||
static GtkWidget *pevent_dialog = NULL, *pevent_dialog_twid,
|
||||
*pevent_dialog_list, *pevent_dialog_hlist;
|
||||
|
||||
static char *pevent_staged[NUM_XP];
|
||||
static char *pevent_original[NUM_XP];
|
||||
static gboolean pevent_pending;
|
||||
|
||||
#define PEVENT_THEME_LISTENER_ID_KEY "textgui.theme-listener-id"
|
||||
|
||||
enum
|
||||
@@ -154,17 +148,10 @@ PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp)
|
||||
}
|
||||
|
||||
static void
|
||||
pevent_preferences_destroy (GtkWidget *widget, gpointer data)
|
||||
pevent_dialog_close (GtkWidget *wid, gpointer arg)
|
||||
{
|
||||
int i;
|
||||
(void)widget; (void)data;
|
||||
pevent_dialog = pevent_dialog_list = pevent_dialog_hlist = pevent_dialog_twid = NULL;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
{
|
||||
g_clear_pointer (&pevent_staged[i], g_free);
|
||||
g_clear_pointer (&pevent_original[i], g_free);
|
||||
}
|
||||
pevent_pending = FALSE;
|
||||
pevent_dialog = NULL;
|
||||
pevent_save (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -224,14 +211,9 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
|
||||
char *out;
|
||||
int sig;
|
||||
|
||||
path = gtk_tree_path_new_from_string (pathstr);
|
||||
if (!gtk_tree_model_get_iter (model, &iter, path))
|
||||
{
|
||||
gtk_tree_path_free (path);
|
||||
if (!gtkutil_treeview_get_selected (GTK_TREE_VIEW (pevent_dialog_list),
|
||||
&iter, ROW_COLUMN, &sig, -1))
|
||||
return;
|
||||
}
|
||||
gtk_tree_path_free (path);
|
||||
gtk_tree_model_get (model, &iter, ROW_COLUMN, &sig, -1);
|
||||
|
||||
text = new_text;
|
||||
len = strlen (new_text);
|
||||
@@ -252,11 +234,16 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
|
||||
return;
|
||||
}
|
||||
|
||||
path = gtk_tree_path_new_from_string (pathstr);
|
||||
gtk_tree_model_get_iter (model, &iter, path);
|
||||
gtk_list_store_set (GTK_LIST_STORE (model), &iter, TEXT_COLUMN, new_text, -1);
|
||||
gtk_tree_path_free (path);
|
||||
|
||||
g_free (pevent_staged[sig]);
|
||||
pevent_staged[sig] = g_strdup (text);
|
||||
g_free (out);
|
||||
g_free (pntevts_text[sig]);
|
||||
g_free (pntevts[sig]);
|
||||
|
||||
pntevts_text[sig] = g_strdup (text);
|
||||
pntevts[sig] = out;
|
||||
|
||||
out = g_malloc (len + 2);
|
||||
memcpy (out, text, len + 1);
|
||||
@@ -270,7 +257,8 @@ pevent_edited (GtkCellRendererText *render, gchar *pathstr, gchar *new_text, gpo
|
||||
/* Scroll to bottom */
|
||||
gtk_adjustment_set_value (xtext->adj, gtk_adjustment_get_upper (xtext->adj));
|
||||
|
||||
/* The Preferences Save action commits this staged template. */
|
||||
/* save this when we exit */
|
||||
prefs.save_pevents = 1;
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -328,222 +316,53 @@ pevent_dialog_fill (GtkWidget *list)
|
||||
i--;
|
||||
gtk_list_store_insert_with_values (store, &iter, 0,
|
||||
EVENT_COLUMN, te[i].name,
|
||||
TEXT_COLUMN, pevent_staged[i],
|
||||
TEXT_COLUMN, pntevts_text[i],
|
||||
ROW_COLUMN, i, -1);
|
||||
}
|
||||
while (i != 0);
|
||||
}
|
||||
|
||||
static char *
|
||||
pevent_preferences_serialize (void)
|
||||
{
|
||||
GString *text = g_string_new ("");
|
||||
int i;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
g_string_append_printf (text, "event_name=%s\nevent_text=%s\n\n", te[i].name, pevent_staged[i]);
|
||||
return g_string_free (text, FALSE);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pevent_preferences_validate (const char *text, int event, GError **error)
|
||||
{
|
||||
char *compiled = NULL;
|
||||
int maximum;
|
||||
if (strpbrk (text, "\r\n") || pevt_build_string (text, &compiled, &maximum) != 0)
|
||||
{
|
||||
g_free (compiled);
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Invalid message format for %s."), te[event].name);
|
||||
return FALSE;
|
||||
}
|
||||
g_free (compiled);
|
||||
if (maximum > (te[event].num_args & 0x7f))
|
||||
{
|
||||
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("%s only supports %d arguments."), te[event].name, te[event].num_args & 0x7f);
|
||||
return FALSE;
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
pevent_preferences_import_text (const char *contents, GError **error)
|
||||
{
|
||||
char **lines;
|
||||
char *values[NUM_XP] = {0};
|
||||
int i, event = -1, matched = 0;
|
||||
gboolean success = FALSE;
|
||||
if (!pevent_dialog_list)
|
||||
{
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("Open message formatting before importing templates."));
|
||||
return FALSE;
|
||||
}
|
||||
lines = g_strsplit (contents, "\n", -1);
|
||||
for (i = 0; lines[i]; i++)
|
||||
{
|
||||
char *line = lines[i];
|
||||
gsize length = strlen (line);
|
||||
int n;
|
||||
if (length && line[length - 1] == '\r') line[length - 1] = 0;
|
||||
if (g_str_has_prefix (line, "event_name="))
|
||||
{
|
||||
event = -1;
|
||||
for (n = 0; n < NUM_XP; n++)
|
||||
if (!strcmp (te[n].name, line + 11)) { event = n; break; }
|
||||
}
|
||||
else if (event >= 0 && g_str_has_prefix (line, "event_text="))
|
||||
{
|
||||
if (!pevent_preferences_validate (line + 11, event, error)) goto done;
|
||||
g_free (values[event]);
|
||||
values[event] = g_strdup (line + 11);
|
||||
matched++;
|
||||
}
|
||||
}
|
||||
if (!matched)
|
||||
{
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_INVAL, _("No recognized text events were found in this file."));
|
||||
goto done;
|
||||
}
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
if (values[i])
|
||||
{
|
||||
g_free (pevent_staged[i]);
|
||||
pevent_staged[i] = values[i];
|
||||
values[i] = NULL;
|
||||
}
|
||||
pevent_dialog_fill (pevent_dialog_list);
|
||||
success = TRUE;
|
||||
done:
|
||||
for (i = 0; i < NUM_XP; i++) g_free (values[i]);
|
||||
g_strfreev (lines);
|
||||
return success;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
pevent_preferences_import (const char *file, GError **error)
|
||||
{
|
||||
char *contents;
|
||||
if (!g_file_get_contents (file, &contents, NULL, error)) return FALSE;
|
||||
gboolean result = pevent_preferences_import_text (contents, error);
|
||||
g_free (contents);
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GWeakRef page;
|
||||
gboolean exporting;
|
||||
} PeEventFileRequest;
|
||||
|
||||
static void
|
||||
pevent_file_request_free (gpointer data)
|
||||
pevent_save_req_cb (void *arg1, char *file)
|
||||
{
|
||||
PeEventFileRequest *request = data;
|
||||
g_weak_ref_clear (&request->page);
|
||||
g_free (request);
|
||||
if (file)
|
||||
pevent_save (file);
|
||||
}
|
||||
|
||||
static void
|
||||
pevent_file_response (GtkNativeDialog *dialog, gint response, PeEventFileRequest *request)
|
||||
pevent_save_cb (GtkWidget * wid, void *data)
|
||||
{
|
||||
GtkWidget *page = g_weak_ref_get (&request->page);
|
||||
if (page && page == pevent_dialog && response == GTK_RESPONSE_ACCEPT)
|
||||
if (data)
|
||||
{
|
||||
char *file = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog));
|
||||
GError *error = NULL;
|
||||
if (file)
|
||||
{
|
||||
if (request->exporting)
|
||||
{
|
||||
char *contents = pevent_preferences_serialize ();
|
||||
g_file_set_contents (file, contents, -1, &error);
|
||||
g_free (contents);
|
||||
}
|
||||
else pevent_preferences_import (file, &error);
|
||||
if (error) { fe_message (error->message, FE_MSG_ERROR); g_clear_error (&error); }
|
||||
g_free (file);
|
||||
}
|
||||
gtkutil_file_req (NULL, _("Print Texts File"), pevent_save_req_cb, NULL,
|
||||
NULL, NULL, FRF_WRITE);
|
||||
return;
|
||||
}
|
||||
if (page) g_object_unref (page);
|
||||
g_object_unref (dialog);
|
||||
pevent_save (NULL);
|
||||
}
|
||||
|
||||
static void
|
||||
pevent_file_open (GtkWidget *button, gpointer exporting)
|
||||
pevent_load_req_cb (void *arg1, char *file)
|
||||
{
|
||||
GtkFileChooserNative *dialog;
|
||||
PeEventFileRequest *request = g_new0 (PeEventFileRequest, 1);
|
||||
(void)button;
|
||||
request->exporting = GPOINTER_TO_INT (exporting);
|
||||
g_weak_ref_init (&request->page, pevent_dialog);
|
||||
dialog = gtk_file_chooser_native_new (_("Text events file"),
|
||||
GTK_WINDOW (gtk_widget_get_toplevel (pevent_dialog)),
|
||||
request->exporting ? GTK_FILE_CHOOSER_ACTION_SAVE : GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||
request->exporting ? _("_Export") : _("_Import"), _("_Cancel"));
|
||||
gtk_native_dialog_set_modal (GTK_NATIVE_DIALOG (dialog), TRUE);
|
||||
g_object_set_data_full (G_OBJECT (dialog), "request", request, pevent_file_request_free);
|
||||
g_signal_connect (dialog, "response", G_CALLBACK (pevent_file_response), request);
|
||||
gtk_native_dialog_show (GTK_NATIVE_DIALOG (dialog));
|
||||
if (file)
|
||||
{
|
||||
pevent_load (file);
|
||||
pevent_make_pntevts ();
|
||||
pevent_dialog_fill (pevent_dialog_list);
|
||||
prefs.save_pevents = 1;
|
||||
}
|
||||
}
|
||||
|
||||
gboolean
|
||||
pevent_preferences_stage (GError **error)
|
||||
static void
|
||||
pevent_load_cb (GtkWidget * wid, void *data)
|
||||
{
|
||||
int i;
|
||||
char *contents;
|
||||
pevent_pending = FALSE;
|
||||
if (!pevent_dialog) return TRUE;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
{
|
||||
if (!pevent_preferences_validate (pevent_staged[i], i, error))
|
||||
{
|
||||
GtkTreeModel *model = gtk_tree_view_get_model (GTK_TREE_VIEW (pevent_dialog_list));
|
||||
GtkTreeIter iter;
|
||||
gboolean valid = gtk_tree_model_get_iter_first (model, &iter);
|
||||
setup_reveal_widget (pevent_dialog_list);
|
||||
while (valid)
|
||||
{
|
||||
int event;
|
||||
gtk_tree_model_get (model, &iter, ROW_COLUMN, &event, -1);
|
||||
if (event == i)
|
||||
{
|
||||
GtkTreePath *path = gtk_tree_model_get_path (model, &iter);
|
||||
gtk_widget_grab_focus (pevent_dialog_list);
|
||||
gtk_tree_view_set_cursor (GTK_TREE_VIEW (pevent_dialog_list), path, NULL, FALSE);
|
||||
gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (pevent_dialog_list), path, NULL, FALSE, 0, 0);
|
||||
gtk_tree_path_free (path);
|
||||
break;
|
||||
}
|
||||
valid = gtk_tree_model_iter_next (model, &iter);
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
if (g_strcmp0 (pevent_staged[i], pevent_original[i]) != 0) pevent_pending = TRUE;
|
||||
}
|
||||
if (!pevent_pending) return TRUE;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
if (g_strcmp0 (pntevts_text[i], pevent_original[i]) != 0)
|
||||
{
|
||||
setup_reveal_widget (pevent_dialog_list);
|
||||
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_AGAIN, _("Text events changed outside Preferences. Reopen Preferences before editing them."));
|
||||
return FALSE;
|
||||
}
|
||||
contents = pevent_preferences_serialize ();
|
||||
preferences_persistence_stage_file ("pevents.conf", contents);
|
||||
g_free (contents);
|
||||
return TRUE;
|
||||
gtkutil_file_req (NULL, _("Print Texts File"), pevent_load_req_cb, NULL, NULL, NULL, 0);
|
||||
}
|
||||
|
||||
void
|
||||
pevent_preferences_commit (void)
|
||||
static void
|
||||
pevent_ok_cb (GtkWidget * wid, void *data)
|
||||
{
|
||||
int i;
|
||||
if (!pevent_pending) return;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
{
|
||||
g_free (pntevts_text[i]);
|
||||
pntevts_text[i] = g_strdup (pevent_staged[i]);
|
||||
}
|
||||
pevent_make_pntevts ();
|
||||
prefs.save_pevents = 1;
|
||||
gtk_widget_destroy (pevent_dialog);
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -554,7 +373,7 @@ pevent_test_cb (GtkWidget * wid, GtkWidget * twid)
|
||||
|
||||
for (n = 0; n < NUM_XP; n++)
|
||||
{
|
||||
text = pevent_staged[n];
|
||||
text = _(pntevts_text[n]);
|
||||
len = strlen (text);
|
||||
|
||||
out = g_malloc (len + 2);
|
||||
@@ -666,27 +485,22 @@ pevent_hlist_treeview_new (GtkWidget *box)
|
||||
return view;
|
||||
}
|
||||
|
||||
static void
|
||||
pevent_preferences_preview_realize (GtkWidget *widget, gpointer data)
|
||||
{
|
||||
gtk_xtext_set_font (GTK_XTEXT (widget), prefs.hex_text_font);
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
pevent_preferences_create (void)
|
||||
void
|
||||
pevent_dialog_show (void)
|
||||
{
|
||||
GtkWidget *vbox, *hbox, *wid, *pane, *preview_pane;
|
||||
XTextColor xtext_palette[XTEXT_COLS];
|
||||
|
||||
int i;
|
||||
vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 10);
|
||||
pevent_dialog = vbox;
|
||||
for (i = 0; i < NUM_XP; i++)
|
||||
if (pevent_dialog)
|
||||
{
|
||||
pevent_original[i] = g_strdup (pntevts_text[i]);
|
||||
pevent_staged[i] = g_strdup (pntevts_text[i]);
|
||||
mg_bring_tofront (pevent_dialog);
|
||||
return;
|
||||
}
|
||||
g_signal_connect (vbox, "destroy", G_CALLBACK (pevent_preferences_destroy), NULL);
|
||||
|
||||
pevent_dialog =
|
||||
mg_create_generic_tab ("edit events", _("Edit Events"),
|
||||
TRUE, FALSE, pevent_dialog_close, NULL,
|
||||
600, 455, &vbox, 0);
|
||||
|
||||
preview_pane = gtk_paned_new (GTK_ORIENTATION_VERTICAL);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), preview_pane, TRUE, TRUE, 0);
|
||||
@@ -709,7 +523,7 @@ pevent_preferences_create (void)
|
||||
pevent_dialog_twid = gtk_xtext_new (xtext_palette, 0);
|
||||
gtk_widget_set_sensitive (pevent_dialog_twid, FALSE);
|
||||
gtk_container_add (GTK_CONTAINER (wid), pevent_dialog_twid);
|
||||
g_signal_connect (pevent_dialog_twid, "realize", G_CALLBACK (pevent_preferences_preview_realize), NULL);
|
||||
gtk_xtext_set_font (GTK_XTEXT (pevent_dialog_twid), prefs.hex_text_font);
|
||||
g_object_set_data (G_OBJECT (pevent_dialog), "xtext", pevent_dialog_twid);
|
||||
g_object_set_data (G_OBJECT (pevent_dialog), PEVENT_THEME_LISTENER_ID_KEY,
|
||||
GUINT_TO_POINTER (theme_listener_register ("textgui.events", pevent_dialog_theme_changed, pevent_dialog)));
|
||||
@@ -718,20 +532,15 @@ pevent_preferences_create (void)
|
||||
hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL);
|
||||
gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_SPREAD);
|
||||
gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 4);
|
||||
gtkutil_button (hbox, ICON_TEXTEVENT_SAVE_AS, NULL, pevent_file_open,
|
||||
(void *) 1, _("Export..."));
|
||||
gtkutil_button (hbox, ICON_TEXTEVENT_OPEN, NULL, pevent_file_open,
|
||||
NULL, _("Import..."));
|
||||
gtkutil_button (hbox, ICON_TEXTEVENT_SAVE_AS, NULL, pevent_save_cb,
|
||||
(void *) 1, _("Save As..."));
|
||||
gtkutil_button (hbox, ICON_TEXTEVENT_OPEN, NULL, pevent_load_cb,
|
||||
NULL, _("Load From..."));
|
||||
gtkutil_button (hbox, NULL, NULL, pevent_test_cb,
|
||||
pevent_dialog_twid, _("Test All"));
|
||||
gtkutil_button (hbox, ICON_TEXTEVENT_OK, NULL, pevent_ok_cb,
|
||||
NULL, _("OK"));
|
||||
|
||||
gtk_widget_show_all (pevent_dialog);
|
||||
pevent_dialog_theme_apply (pevent_dialog);
|
||||
return vbox;
|
||||
}
|
||||
|
||||
void
|
||||
pevent_dialog_show (void)
|
||||
{
|
||||
setup_open_page ("events");
|
||||
}
|
||||
@@ -22,9 +22,5 @@
|
||||
|
||||
void PrintTextRaw (void *xtbuf, unsigned char *text, int indent, time_t stamp);
|
||||
void pevent_dialog_show (void);
|
||||
GtkWidget *pevent_preferences_create (void);
|
||||
gboolean pevent_preferences_import_text (const char *contents, GError **error);
|
||||
gboolean pevent_preferences_stage (GError **error);
|
||||
void pevent_preferences_commit (void);
|
||||
|
||||
#endif
|
||||
@@ -112,14 +112,6 @@ theme_runtime_mode_has_user_colors (gboolean dark_mode)
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
|
||||
{
|
||||
(void)map;
|
||||
*color = stub_light_colors[token];
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
|
||||
{
|
||||
|
||||
@@ -571,31 +571,6 @@ test_stale_theme_symlink_is_replaced (void)
|
||||
theme_gtk3_disable ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_builtin_appearances_restore_desktop_theme (void)
|
||||
{
|
||||
if (!gtk_available) return;
|
||||
GtkSettings *settings = gtk_settings_get_default ();
|
||||
char *original, *name;
|
||||
gboolean dark;
|
||||
theme_gtk3_disable ();
|
||||
g_object_get (settings, "gtk-theme-name", &original, NULL);
|
||||
g_strlcpy (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK, sizeof prefs.hex_gui_gtk3_theme);
|
||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||
g_assert_true (theme_gtk3_apply_current (NULL));
|
||||
g_object_get (settings, "gtk-theme-name", &name, "gtk-application-prefer-dark-theme", &dark, NULL);
|
||||
g_assert_cmpstr (name, ==, "Adwaita"); g_assert_true (dark); g_free (name);
|
||||
g_strlcpy (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_LIGHT, sizeof prefs.hex_gui_gtk3_theme);
|
||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_LIGHT;
|
||||
g_assert_true (theme_gtk3_apply_current (NULL));
|
||||
g_object_get (settings, "gtk-application-prefer-dark-theme", &dark, NULL);
|
||||
g_assert_false (dark);
|
||||
prefs.hex_gui_gtk3_theme[0] = '\0'; prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_AUTO;
|
||||
g_assert_true (theme_gtk3_apply_current (NULL));
|
||||
g_object_get (settings, "gtk-theme-name", &name, NULL);
|
||||
g_assert_cmpstr (name, ==, original); g_free (name); g_free (original);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
@@ -621,7 +596,6 @@ main (int argc, char **argv)
|
||||
if (!gtk_available)
|
||||
g_test_message ("Skipping GTK3 settings tests because GTK initialization failed");
|
||||
|
||||
g_test_add_func ("/theme/gtk3/builtin_appearances_restore_desktop", test_builtin_appearances_restore_desktop_theme);
|
||||
rc = g_test_run ();
|
||||
theme_gtk3_disable ();
|
||||
teardown_themes ();
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
#include "../../../common/gtk3-theme-service.h"
|
||||
#include "../theme-gtk3.h"
|
||||
#include "../theme-manager.h"
|
||||
#include "../theme-runtime.h"
|
||||
|
||||
struct session *current_sess;
|
||||
struct session *current_tab;
|
||||
@@ -39,61 +38,6 @@ static char applied_theme_id[256];
|
||||
static ThemeGtk3Variant applied_variant;
|
||||
static gboolean removed_selected;
|
||||
|
||||
static gboolean custom_tokens[THEME_TOKEN_COUNT];
|
||||
static GdkRGBA custom_colors[THEME_TOKEN_COUNT];
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color)
|
||||
{
|
||||
if (!custom_tokens[token]) return FALSE;
|
||||
*color = custom_colors[token];
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
|
||||
{
|
||||
(void)map;
|
||||
return gdk_rgba_parse (color, token == THEME_TOKEN_TEXT_BACKGROUND || token == THEME_TOKEN_SELECTION_FOREGROUND ? "#ffffff" :
|
||||
token == THEME_TOKEN_SELECTION_BACKGROUND ? "#336699" : "#000000");
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color)
|
||||
{
|
||||
(void)widget;
|
||||
theme_runtime_get_default_color (token, NULL, color);
|
||||
if (token == THEME_TOKEN_TEXT_BACKGROUND && prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_DARK)
|
||||
gdk_rgba_parse (color, "#181818");
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void theme_runtime_reset_user_color (ThemeSemanticToken token) { custom_tokens[token] = FALSE; }
|
||||
void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *color)
|
||||
{ custom_tokens[token] = TRUE; custom_colors[token] = *color; }
|
||||
|
||||
gboolean theme_manager_apply_mode (unsigned int mode, gboolean *changed)
|
||||
{ (void)mode; if (changed) *changed = FALSE; return FALSE; }
|
||||
void theme_manager_apply_and_dispatch (unsigned int mode, ThemeChangedReason reasons, gboolean *changed)
|
||||
{ (void)mode; (void)reasons; if (changed) *changed = FALSE; }
|
||||
guint theme_listener_register (const char *id, ThemeChangedCallback callback, gpointer data)
|
||||
{ (void)id; (void)callback; (void)data; return 0; }
|
||||
void theme_listener_unregister (guint id) { (void)id; }
|
||||
gboolean pevent_preferences_import_text (const char *contents, GError **error)
|
||||
{ (void)contents; (void)error; return TRUE; }
|
||||
|
||||
void
|
||||
setup_open_page (const char *id)
|
||||
{
|
||||
(void)id;
|
||||
}
|
||||
|
||||
void
|
||||
setup_reveal_widget (GtkWidget *widget)
|
||||
{
|
||||
(void)widget;
|
||||
}
|
||||
|
||||
GtkWidget *
|
||||
gtkutil_box_new (GtkOrientation orientation, gboolean homogeneous, gint spacing)
|
||||
{
|
||||
@@ -162,7 +106,6 @@ theme_manager_set_token_color (unsigned int dark_mode, ThemeSemanticToken token,
|
||||
(void)token;
|
||||
(void)color;
|
||||
set_token_color_calls++;
|
||||
theme_runtime_user_set_color (token, color);
|
||||
if (changed)
|
||||
*changed = FALSE;
|
||||
}
|
||||
@@ -515,154 +458,6 @@ test_select_theme_does_not_pin_palette_tokens (void)
|
||||
gtk_widget_destroy (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_embedded_color_editor (void)
|
||||
{
|
||||
GtkWidget *window, *editor;
|
||||
theme_color_manager_ui *ui;
|
||||
theme_color_manager_row *row;
|
||||
GError *error = NULL;
|
||||
if (!gtk_available)
|
||||
{
|
||||
g_test_message ("GTK display not available");
|
||||
return;
|
||||
}
|
||||
memset (&prefs, 0, sizeof prefs);
|
||||
theme_preferences_stage_begin ();
|
||||
window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
editor = theme_preferences_create_color_editor (GTK_WINDOW (window), NULL);
|
||||
gtk_container_add (GTK_CONTAINER (window), editor);
|
||||
ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
|
||||
row = g_ptr_array_index (ui->rows, 0);
|
||||
set_token_color_calls = 0;
|
||||
theme_preferences_manager_entry_commit (row);
|
||||
g_assert_true (theme_preferences_color_editor_stage (editor, &error));
|
||||
g_assert_no_error (error);
|
||||
theme_preferences_stage_apply ();
|
||||
g_assert_cmpint (set_token_color_calls, ==, 0);
|
||||
gtk_entry_set_text (GTK_ENTRY (row->entry), "#123456");
|
||||
g_assert_true (theme_preferences_color_editor_stage (editor, &error));
|
||||
g_assert_no_error (error);
|
||||
/* A typed edit previews its token, then Save reapplies that token. */
|
||||
g_assert_cmpint (set_token_color_calls, ==, 1);
|
||||
theme_preferences_stage_apply ();
|
||||
g_assert_cmpint (set_token_color_calls, ==, 2);
|
||||
gtk_entry_set_text (GTK_ENTRY (row->entry), "invalid");
|
||||
g_assert_false (theme_preferences_color_editor_stage (editor, &error));
|
||||
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL);
|
||||
g_clear_error (&error);
|
||||
gtk_widget_show_all (window);
|
||||
gtk_button_clicked (GTK_BUTTON (row->button));
|
||||
gtk_widget_destroy (window);
|
||||
theme_preferences_stage_discard ();
|
||||
}
|
||||
|
||||
static void
|
||||
test_appearance_schemes_and_cancel (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
|
||||
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
|
||||
GdkRGBA original, restored;
|
||||
gdk_rgba_parse (&original, "#345678");
|
||||
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &original);
|
||||
theme_preferences_stage_begin ();
|
||||
struct zoitechatprefs staged = prefs;
|
||||
GtkWidget *page = theme_preferences_create_page (NULL, &staged, NULL);
|
||||
theme_preferences_ui *appearance = g_object_get_data (G_OBJECT (page), "theme-preferences-ui");
|
||||
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (appearance->color_editor), "zoitechat-theme-color-manager");
|
||||
gtk_button_clicked (GTK_BUTTON (ui->scheme_buttons[2]));
|
||||
g_assert_true (theme_preferences_stage.changed);
|
||||
g_assert_true (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_BACKGROUND]);
|
||||
gtk_button_clicked (GTK_BUTTON (appearance->style_buttons[2]));
|
||||
g_assert_cmpint (prefs.hex_gui_dark_mode, ==, ZOITECHAT_DARK_MODE_DARK);
|
||||
theme_preferences_stage_discard ();
|
||||
g_assert_cmpint (prefs.hex_gui_dark_mode, ==, ZOITECHAT_DARK_MODE_AUTO);
|
||||
g_assert_true (theme_runtime_get_user_color (THEME_TOKEN_TEXT_FOREGROUND, &restored));
|
||||
g_assert_true (gdk_rgba_equal (&original, &restored));
|
||||
for (int token = 0; token < THEME_TOKEN_COUNT; token++)
|
||||
if (token != THEME_TOKEN_TEXT_FOREGROUND) g_assert_false (custom_tokens[token]);
|
||||
gtk_widget_destroy (page);
|
||||
}
|
||||
|
||||
static void
|
||||
test_individual_reset_preserves_other_overrides (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
|
||||
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
|
||||
theme_preferences_stage_begin ();
|
||||
GtkWidget *editor = theme_preferences_create_color_editor (NULL, NULL);
|
||||
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
|
||||
gtk_button_clicked (GTK_BUTTON (ui->scheme_buttons[2]));
|
||||
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_TEXT_BACKGROUND);
|
||||
gtk_button_clicked (GTK_BUTTON (row->reset));
|
||||
g_assert_false (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_BACKGROUND]);
|
||||
g_assert_true (theme_preferences_stage.staged_custom[THEME_TOKEN_TEXT_FOREGROUND]);
|
||||
g_assert_cmpstr (gtk_label_get_text (GTK_LABEL (row->status)), ==, "Theme");
|
||||
GdkRGBA light, dark;
|
||||
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_BACKGROUND, &light);
|
||||
prefs.hex_gui_dark_mode = ZOITECHAT_DARK_MODE_DARK;
|
||||
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_BACKGROUND, &dark);
|
||||
g_assert_false (gdk_rgba_equal (&light, &dark));
|
||||
theme_preferences_stage_commit ();
|
||||
g_assert_false (custom_tokens[THEME_TOKEN_TEXT_BACKGROUND]);
|
||||
g_assert_true (custom_tokens[THEME_TOKEN_TEXT_FOREGROUND]);
|
||||
gtk_widget_destroy (editor);
|
||||
}
|
||||
|
||||
static void
|
||||
test_contrast_suggestion_and_preview_navigation (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
|
||||
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
|
||||
theme_preferences_stage_begin ();
|
||||
GtkWidget *editor = theme_preferences_create_color_editor (NULL, NULL);
|
||||
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
|
||||
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_TEXT_FOREGROUND);
|
||||
GdkRGBA white, black, gray;
|
||||
gdk_rgba_parse (&white, "#ffffff"); gdk_rgba_parse (&black, "#000000"); gdk_rgba_parse (&gray, "#dddddd");
|
||||
g_assert_cmpfloat_with_epsilon (theme_preferences_contrast_ratio (&white, &black), 21.0, 0.001);
|
||||
theme_preferences_manager_row_commit (row, &gray);
|
||||
g_assert_true (gtk_widget_get_visible (ui->contrast_fix));
|
||||
gtk_button_clicked (GTK_BUTTON (ui->contrast_fix));
|
||||
theme_preferences_staged_get_color (THEME_TOKEN_TEXT_FOREGROUND, &gray);
|
||||
g_assert_cmpfloat (theme_preferences_contrast_ratio (&gray, &white), >=, 4.5);
|
||||
theme_preferences_manager_focus_color (ui, THEME_TOKEN_MIRC_12);
|
||||
g_assert_true (gtk_expander_get_expanded (GTK_EXPANDER (ui->advanced)));
|
||||
gtk_entry_set_text (GTK_ENTRY (row->entry), "invalid");
|
||||
theme_preferences_manager_entry_commit (row);
|
||||
g_assert_cmpstr (gtk_entry_get_text (GTK_ENTRY (row->entry)), ==, "invalid");
|
||||
GError *error = NULL;
|
||||
g_assert_false (theme_preferences_color_editor_stage (editor, &error));
|
||||
g_assert_error (error, G_FILE_ERROR, G_FILE_ERROR_INVAL); g_clear_error (&error);
|
||||
theme_preferences_stage_discard (); gtk_widget_destroy (editor);
|
||||
}
|
||||
|
||||
static void
|
||||
test_picker_cancel_restores_inheritance (void)
|
||||
{
|
||||
if (!gtk_available) { g_test_message ("GTK display not available"); return; }
|
||||
memset (&prefs, 0, sizeof prefs); memset (custom_tokens, 0, sizeof custom_tokens);
|
||||
theme_preferences_stage_begin ();
|
||||
GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||
GtkWidget *editor = theme_preferences_create_color_editor (GTK_WINDOW (window), NULL);
|
||||
gtk_container_add (GTK_CONTAINER (window), editor);
|
||||
theme_color_manager_ui *ui = g_object_get_data (G_OBJECT (editor), "zoitechat-theme-color-manager");
|
||||
theme_color_manager_row *row = g_ptr_array_index (ui->rows, THEME_TOKEN_MIRC_12);
|
||||
gtk_button_clicked (GTK_BUTTON (row->button));
|
||||
GList *windows = gtk_window_list_toplevels ();
|
||||
GtkWidget *picker = NULL;
|
||||
for (GList *i = windows; i; i = i->next) if (GTK_IS_COLOR_CHOOSER_DIALOG (i->data)) picker = i->data;
|
||||
g_assert_nonnull (picker);
|
||||
GdkRGBA color; gdk_rgba_parse (&color, "#aabbcc");
|
||||
gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (picker), &color);
|
||||
g_assert_true (theme_preferences_stage.staged_custom[row->token]);
|
||||
gtk_dialog_response (GTK_DIALOG (picker), GTK_RESPONSE_CANCEL);
|
||||
g_assert_false (theme_preferences_stage.staged_custom[row->token]);
|
||||
g_assert_false (custom_tokens[row->token]);
|
||||
g_list_free (windows);
|
||||
theme_preferences_stage_discard (); gtk_widget_destroy (window);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
@@ -676,10 +471,5 @@ main (int argc, char **argv)
|
||||
test_select_none_resets_theme_and_applies);
|
||||
g_test_add_func ("/theme/preferences/gtk3_select_theme_does_not_pin_palette_tokens",
|
||||
test_select_theme_does_not_pin_palette_tokens);
|
||||
g_test_add_func ("/theme/preferences/embedded_color_editor", test_embedded_color_editor);
|
||||
g_test_add_func ("/theme/preferences/appearance_schemes_cancel", test_appearance_schemes_and_cancel);
|
||||
g_test_add_func ("/theme/preferences/individual_reset_inherits", test_individual_reset_preserves_other_overrides);
|
||||
g_test_add_func ("/theme/preferences/contrast_and_navigation", test_contrast_suggestion_and_preview_navigation);
|
||||
g_test_add_func ("/theme/preferences/picker_cancel_inherits", test_picker_cancel_restores_inheritance);
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -430,36 +430,6 @@ test_save_writes_only_custom_token_keys (void)
|
||||
g_free (cfg);
|
||||
}
|
||||
|
||||
static void
|
||||
test_reset_one_colour_restores_inheritance_after_reload (void)
|
||||
{
|
||||
GdkRGBA color;
|
||||
char *cfg;
|
||||
setup_temp_home ();
|
||||
theme_runtime_load ();
|
||||
gdk_rgba_parse (&color, "#456789");
|
||||
theme_runtime_user_set_color (THEME_TOKEN_TEXT_FOREGROUND, &color);
|
||||
theme_runtime_user_set_color (THEME_TOKEN_TEXT_BACKGROUND, &color);
|
||||
theme_runtime_reset_user_color (THEME_TOKEN_TEXT_BACKGROUND);
|
||||
theme_runtime_apply_mode (ZOITECHAT_DARK_MODE_LIGHT, NULL);
|
||||
g_assert_true (theme_runtime_save ());
|
||||
cfg = read_colors_conf ();
|
||||
g_assert_nonnull (strstr (cfg, "theme.mode.light.token.text_foreground"));
|
||||
g_assert_null (strstr (cfg, "theme.mode.light.token.text_background"));
|
||||
g_free (cfg);
|
||||
theme_runtime_load ();
|
||||
g_assert_false (theme_runtime_get_user_color (THEME_TOKEN_TEXT_BACKGROUND, &color));
|
||||
g_assert_true (theme_runtime_get_user_color (THEME_TOKEN_TEXT_FOREGROUND, &color));
|
||||
g_assert_cmpfloat_with_epsilon (color.red, 0x45 / 255.0, 0.0001);
|
||||
ThemeGtkPaletteMap map = { .enabled = TRUE };
|
||||
gdk_rgba_parse (&map.text_background, "#111111");
|
||||
theme_runtime_get_default_color (THEME_TOKEN_TEXT_BACKGROUND, &map, &color);
|
||||
g_assert_cmpfloat_with_epsilon (color.red, 0x11 / 255.0, 0.0001);
|
||||
gdk_rgba_parse (&map.text_background, "#eeeeee");
|
||||
theme_runtime_get_default_color (THEME_TOKEN_TEXT_BACKGROUND, &map, &color);
|
||||
g_assert_cmpfloat_with_epsilon (color.red, 0xee / 255.0, 0.0001);
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
{
|
||||
@@ -480,6 +450,5 @@ main (int argc, char **argv)
|
||||
test_save_discard_unlinks_temp_file);
|
||||
g_test_add_func ("/theme/runtime/save_writes_only_custom_token_keys",
|
||||
test_save_writes_only_custom_token_keys);
|
||||
g_test_add_func ("/theme/runtime/reset_colour_persists_inheritance", test_reset_one_colour_restores_inheritance_after_reload);
|
||||
return g_test_run ();
|
||||
}
|
||||
@@ -206,14 +206,6 @@ theme_access_get_gtk_palette_map (GtkWidget *widget, ThemeGtkPaletteMap *out_map
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color)
|
||||
{
|
||||
ThemeGtkPaletteMap map = { 0 };
|
||||
theme_access_get_gtk_palette_map (widget, &map);
|
||||
return theme_runtime_get_default_color (token, &map, color);
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba)
|
||||
{
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#include "theme-palette.h"
|
||||
#include "../xtext-color.h"
|
||||
|
||||
gboolean theme_get_default_color_for_widget (GtkWidget *widget, ThemeSemanticToken token, GdkRGBA *color);
|
||||
gboolean theme_get_color (ThemeSemanticToken token, GdkRGBA *out_rgba);
|
||||
gboolean theme_get_mirc_color (unsigned int mirc_index, GdkRGBA *out_rgba);
|
||||
gboolean theme_get_color_rgb16 (ThemeSemanticToken token, guint16 *red, guint16 *green, guint16 *blue);
|
||||
|
||||
@@ -1223,27 +1223,9 @@ theme_gtk3_init (void)
|
||||
gboolean
|
||||
theme_gtk3_apply_current (GError **error)
|
||||
{
|
||||
if (!strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_LIGHT) || !strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK))
|
||||
{
|
||||
/* GTK ships Adwaita on every supported platform. Explicit Light/Dark
|
||||
* choices must not depend on the desktop theme providing both variants. */
|
||||
GtkSettings *settings = gtk_settings_get_default ();
|
||||
theme_gtk3_disable ();
|
||||
if (settings)
|
||||
settings_apply_property (settings, "gtk-theme-name", "Adwaita");
|
||||
settings_apply_for_variant (!strcmp (prefs.hex_gui_gtk3_theme, THEME_GTK3_BUILTIN_DARK) ?
|
||||
THEME_GTK3_VARIANT_PREFER_DARK : THEME_GTK3_VARIANT_PREFER_LIGHT);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (!prefs.hex_gui_gtk3_theme[0])
|
||||
{
|
||||
theme_gtk3_disable ();
|
||||
/* Restoring a previous theme's settings must not undo an explicit
|
||||
* appearance choice, including when a settings notification re-enters. */
|
||||
settings_apply_for_variant (prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_AUTO ?
|
||||
THEME_GTK3_VARIANT_FOLLOW_SYSTEM : prefs.hex_gui_dark_mode == ZOITECHAT_DARK_MODE_DARK ?
|
||||
THEME_GTK3_VARIANT_PREFER_DARK : THEME_GTK3_VARIANT_PREFER_LIGHT);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
#include <glib.h>
|
||||
#include <gdk/gdk.h>
|
||||
|
||||
#define THEME_GTK3_BUILTIN_LIGHT "builtin:light"
|
||||
#define THEME_GTK3_BUILTIN_DARK "builtin:dark"
|
||||
|
||||
typedef enum
|
||||
{
|
||||
THEME_GTK3_VARIANT_FOLLOW_SYSTEM = 0,
|
||||
|
||||
+581
-1001
File diff suppressed because it is too large.
Load diff
@@ -37,8 +37,5 @@ void theme_preferences_stage_begin (void);
|
||||
void theme_preferences_stage_apply (void);
|
||||
void theme_preferences_stage_commit (void);
|
||||
void theme_preferences_stage_discard (void);
|
||||
void theme_preferences_stage_revert_runtime (void);
|
||||
GtkWidget *theme_preferences_create_color_editor (GtkWindow *parent, gboolean *color_change_flag);
|
||||
gboolean theme_preferences_color_editor_stage (GtkWidget *editor, GError **error);
|
||||
|
||||
#endif
|
||||
@@ -367,37 +367,6 @@ theme_runtime_get_xtext_colors_mapped (const ThemeGtkPaletteMap *gtk_map, XTextC
|
||||
theme_palette_to_xtext_colors (&mapped_palette, palette, palette_len);
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color)
|
||||
{
|
||||
if (token < 0 || token >= THEME_TOKEN_COUNT || !light_custom_tokens[token])
|
||||
return FALSE;
|
||||
return theme_palette_get_color (&light_palette, token, color);
|
||||
}
|
||||
|
||||
gboolean
|
||||
theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color)
|
||||
{
|
||||
ThemePalette defaults;
|
||||
gboolean custom[THEME_TOKEN_COUNT] = { FALSE };
|
||||
theme_palette_from_legacy_colors (&defaults, legacy_light_defaults, G_N_ELEMENTS (legacy_light_defaults));
|
||||
theme_runtime_apply_gtk_map (&defaults, map, custom);
|
||||
return theme_palette_get_color (&defaults, token, color);
|
||||
}
|
||||
|
||||
void
|
||||
theme_runtime_reset_user_color (ThemeSemanticToken token)
|
||||
{
|
||||
GdkRGBA color;
|
||||
if (!theme_runtime_get_default_color (token, NULL, &color))
|
||||
return;
|
||||
if (!user_colors_valid)
|
||||
light_palette = active_palette;
|
||||
theme_palette_set_color (&light_palette, token, &color);
|
||||
light_custom_tokens[token] = FALSE;
|
||||
user_colors_valid = TRUE;
|
||||
}
|
||||
|
||||
void
|
||||
theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col)
|
||||
{
|
||||
|
||||
@@ -44,9 +44,6 @@ gboolean theme_runtime_save_finalize (const char *temp_path);
|
||||
void theme_runtime_save_discard (const char *temp_path);
|
||||
gboolean theme_runtime_apply_mode (unsigned int mode, gboolean *palette_changed);
|
||||
gboolean theme_runtime_apply_dark_mode (gboolean enable);
|
||||
gboolean theme_runtime_get_user_color (ThemeSemanticToken token, GdkRGBA *color);
|
||||
void theme_runtime_reset_user_color (ThemeSemanticToken token);
|
||||
gboolean theme_runtime_get_default_color (ThemeSemanticToken token, const ThemeGtkPaletteMap *map, GdkRGBA *color);
|
||||
void theme_runtime_user_set_color (ThemeSemanticToken token, const GdkRGBA *col);
|
||||
void theme_runtime_dark_set_color (ThemeSemanticToken token, const GdkRGBA *col);
|
||||
void theme_runtime_reset_mode_colors (gboolean dark_mode);
|
||||
|
||||
+2
-6
@@ -2713,7 +2713,7 @@ gtk_xtext_selection_get_text (GtkXText *xtext, int *len_ret)
|
||||
first = FALSE;
|
||||
if (ent->mark_end > ent->mark_start)
|
||||
{
|
||||
if (ent->mark_start == 0 && xtext->mark_stamp && ent->stamp_len == 0)
|
||||
if (ent->mark_start == 0 && xtext->mark_stamp)
|
||||
{
|
||||
char *stamp;
|
||||
int len = xtext_get_stamp_str (ent->stamp, &stamp);
|
||||
@@ -4038,11 +4038,7 @@ gtk_xtext_render_line (GtkXText * xtext, textentry * ent, int line,
|
||||
xtext_draw_bg (xtext, bg_x, y - xtext->font->ascent, bg_w, xtext->fontsize);
|
||||
}
|
||||
|
||||
/* Preserve margin stamps across indent changes, including plain
|
||||
* entries with no inline timestamp. Never draw a second stamp
|
||||
* for entries that already contain an inline timestamp. */
|
||||
if (entline == 1 && (xtext->auto_indent || ent->left_len != -1) &&
|
||||
ent->stamp_len == 0 && xtext->buffer->time_stamp &&
|
||||
if (entline == 1 && xtext->auto_indent && xtext->buffer->time_stamp &&
|
||||
(!xtext->skip_stamp || xtext->mark_stamp || xtext->force_stamp))
|
||||
{
|
||||
char *time_str;
|
||||
|
||||
@@ -48,6 +48,12 @@
|
||||
#endif
|
||||
|
||||
|
||||
gint64
|
||||
fe_get_idle_time (void)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int done = FALSE;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{EE388A27-6992-43C0-BA9B-61D8968D42E3}</ProjectGuid>
|
||||
<SolutionDir>$(MSBuildThisFileDirectory)</SolutionDir>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<Import Project="zoitechat.props" />
|
||||
<PropertyGroup>
|
||||
<TargetName>autoaway-tests</TargetName>
|
||||
<OutDir>$(ZoiteChatBuild)\$(ZoiteChatPlatform)\tests\</OutDir>
|
||||
<IntDir>$(ZoiteChatObj)autoaway-tests\</IntDir>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<PreprocessorDefinitions>WIN32;_WIN64;_CONSOLE;$(OwnFlags);%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<AdditionalIncludeDirectories>$(ZoiteChatLib);$(DepsRoot)\include;$(OpenSslInclude);$(Glib);$(Gtk);%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<AdditionalLibraryDirectories>$(DepsRoot)\lib;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
<AdditionalDependencies>gio-2.0.lib;gobject-2.0.lib;glib-2.0.lib;$(IntlLib);%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="..\src\common\autoaway.c" />
|
||||
<ClCompile Include="..\src\common\tests\test-autoaway.c" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
</Project>
|
||||
Reference in new issue
Block a user