Merge branch 'wip/geary-inspector' into 'mainline'
Add an Inspector to Geary See merge request GNOME/geary!199
This commit is contained in:
commit
6234405e4f
17 changed files with 1026 additions and 116 deletions
|
|
@ -16,30 +16,30 @@ variables:
|
|||
INSTALL_CMD: ninja -v -C $BUILD_DIR install
|
||||
|
||||
# Fedora packages
|
||||
FEDORA_DEPS: vala
|
||||
meson desktop-file-utils libcanberra-devel
|
||||
folks-devel libgee-devel glib2-devel gmime-devel
|
||||
gtk3-devel libnotify-devel sqlite-devel
|
||||
webkitgtk4-devel libsecret-devel libxml2-devel
|
||||
vala-tools gcr-devel enchant2-devel libunwind-devel
|
||||
iso-codes-devel gnome-online-accounts-devel itstool
|
||||
json-glib-devel
|
||||
FEDORA_DEPS: meson vala
|
||||
desktop-file-utils enchant2-devel folks-devel gcr-devel
|
||||
glib2-devel gmime-devel gnome-online-accounts-devel
|
||||
gtk3-devel iso-codes-devel json-glib-devel itstool
|
||||
libcanberra-devel libgee-devel libhandy-devel
|
||||
libnotify-devel libsecret-devel libunwind-devel
|
||||
libxml2-devel sqlite-devel webkitgtk4-devel
|
||||
FEDORA_TEST_DEPS: Xvfb tar xz
|
||||
|
||||
# Ubuntu packages
|
||||
UBUNTU_DEPS: valac build-essential
|
||||
meson desktop-file-utils libcanberra-dev
|
||||
libfolks-dev libgee-0.8-dev libglib2.0-dev
|
||||
libgmime-2.6-dev libgtk-3-dev libsecret-1-dev
|
||||
libxml2-dev libnotify-dev libsqlite3-dev
|
||||
libwebkit2gtk-4.0-dev libgcr-3-dev libenchant-dev
|
||||
libunwind-dev iso-codes libgoa-1.0-dev itstool gettext
|
||||
libmessaging-menu-dev libunity-dev libjson-glib-dev
|
||||
UBUNTU_DEPS: meson build-essential valac
|
||||
desktop-file-utils gettext iso-codes itstool
|
||||
libcanberra-dev libenchant-dev libfolks-dev
|
||||
libgcr-3-dev libgee-0.8-dev libglib2.0-dev
|
||||
libgmime-2.6-dev libgoa-1.0-dev libgtk-3-dev
|
||||
libhandy-0.0-dev libjson-glib-dev libmessaging-menu-dev
|
||||
libnotify-dev libsecret-1-dev libsqlite3-dev
|
||||
libunity-dev libunwind-dev libwebkit2gtk-4.0-dev
|
||||
libxml2-dev
|
||||
UBUNTU_TEST_DEPS: xauth xvfb
|
||||
|
||||
fedora:
|
||||
stage: build
|
||||
image: fedora:latest
|
||||
image: fedora:rawhide
|
||||
before_script:
|
||||
- dnf update -y --nogpgcheck
|
||||
- dnf install -y --nogpgcheck $FEDORA_DEPS $FEDORA_TEST_DEPS
|
||||
|
|
@ -51,7 +51,7 @@ fedora:
|
|||
|
||||
ubuntu:
|
||||
stage: build
|
||||
image: ubuntu:rolling
|
||||
image: ubuntu:devel
|
||||
before_script:
|
||||
- apt-get update
|
||||
- apt-get install -q -y --no-install-recommends $UBUNTU_DEPS $UBUNTU_TEST_DEPS
|
||||
|
|
|
|||
39
INSTALL
39
INSTALL
|
|
@ -38,40 +38,31 @@ distribution's package repositories:
|
|||
Installing dependencies on Fedora
|
||||
---------------------------------
|
||||
|
||||
Fedora 25 and later ships with the correct versions of the required
|
||||
libraries. Install them by running this command:
|
||||
Install them by running this command:
|
||||
|
||||
sudo yum install vala meson desktop-file-utils iso-codes-devel \
|
||||
libcanberra-devel folks-devel libgee-devel glib2-devel \
|
||||
gmime-devel gtk3-devel libnotify-devel sqlite-devel \
|
||||
webkitgtk4-devel libsecret-devel libxml2-devel vala-tools \
|
||||
gcr-devel enchant2-devel libunwind-devel json-glib-devel \
|
||||
gnome-online-accounts-devel itstool
|
||||
sudo yum install meson vala \
|
||||
desktop-file-utils enchant2-devel folks-devel gcr-devel \
|
||||
glib2-devel gmime-devel gnome-online-accounts-devel gtk3-devel \
|
||||
iso-codes-devel json-glib-devel libcanberra-devel \
|
||||
libgee-devel libhandy-devel libnotify-devel libsecret-devel \
|
||||
libunwind-devel libxml2-devel sqlite-devel webkitgtk4-devel
|
||||
|
||||
Installing dependencies on Ubuntu/Debian
|
||||
----------------------------------------
|
||||
|
||||
Ubuntu 17.10 (Artful) and later ships with the correct versions of the
|
||||
required libraries.
|
||||
|
||||
Ubuntu 16.04 LTS (Xenial) does not meet the minimum requirements,
|
||||
users of that are encourage to use Geary 0.12 LTS instead.
|
||||
|
||||
Debian 9 (Stretch) and later ships with the correct versions of the
|
||||
required libraries.
|
||||
|
||||
Install them by running this command:
|
||||
|
||||
sudo apt-get install valac meson desktop-file-utils iso-codes \
|
||||
libcanberra-dev libfolks-dev libgee-0.8-dev libglib2.0-dev \
|
||||
libgmime-2.6-dev libgtk-3-dev libsecret-1-dev libxml2-dev \
|
||||
libnotify-dev libsqlite3-dev libwebkit2gtk-4.0-dev \
|
||||
libgcr-3-dev libenchant-dev libunwind-dev libgoa-1.0-dev \
|
||||
libjson-glib-dev itstool gettext
|
||||
sudo apt-get install meson build-essential valac \
|
||||
desktop-file-utils iso-codes gettext libcanberra-dev \
|
||||
libenchant-dev libfolks-dev libgcr-3-dev libgee-0.8-dev \
|
||||
libglib2.0-dev libgmime-2.6-dev libgoa-1.0-dev libgtk-3-dev \
|
||||
libjson-glib-dev libhandy-dev libnotify-dev libsecret-1-dev \
|
||||
libsqlite3-dev libunwind-dev libwebkit2gtk-4.0-dev \
|
||||
libxml2-dev
|
||||
|
||||
And for Ubuntu Unity integration:
|
||||
|
||||
sudo apt-get install libunity-dev libmessaging-menu-dev
|
||||
sudo apt-get install libmessaging-menu-dev libunity-dev
|
||||
|
||||
Running
|
||||
-------
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ iso_codes = dependency('iso-codes')
|
|||
javascriptcoregtk = dependency('javascriptcoregtk-4.0', version: '>=' + target_webkit)
|
||||
json_glib = dependency('json-glib-1.0', version: '>= 1.0')
|
||||
libcanberra = dependency('libcanberra', version: '>= 0.28')
|
||||
libhandy = dependency('libhandy-0.0', version: '>= 0.0.9')
|
||||
libmath = cc.find_library('m')
|
||||
libnotify = dependency('libnotify', version: '>= 0.7.5')
|
||||
libsecret = dependency('libsecret-1', version: '>= 0.11')
|
||||
|
|
|
|||
|
|
@ -181,6 +181,20 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "libhandy",
|
||||
"buildsystem": "meson",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-Dglade_catalog=disabled"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://source.puri.sm/Librem5/libhandy.git"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "libunwind",
|
||||
"sources": [
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ src/client/application/goa-mediator.vala
|
|||
src/client/application/main.vala
|
||||
src/client/application/secret-mediator.vala
|
||||
src/client/components/client-web-view.vala
|
||||
src/client/components/components-inspector.vala
|
||||
src/client/components/components-placeholder-pane.vala
|
||||
src/client/components/components-validator.vala
|
||||
src/client/components/count-badge.vala
|
||||
|
|
@ -415,6 +416,7 @@ ui/composer-headerbar.ui
|
|||
ui/composer-link-popover.ui
|
||||
ui/composer-menus.ui
|
||||
ui/composer-widget.ui
|
||||
ui/components-inspector.ui
|
||||
ui/components-placeholder-pane.ui
|
||||
ui/conversation-email.ui
|
||||
ui/conversation-email-attachment-view.ui
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
|
|||
|
||||
public EditorRow() {
|
||||
get_style_context().add_class("geary-settings");
|
||||
get_style_context().add_class("geary-labelled-row");
|
||||
|
||||
this.layout.orientation = Gtk.Orientation.HORIZONTAL;
|
||||
this.layout.show();
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ public class GearyApplication : Gtk.Application {
|
|||
private const string ACTION_ABOUT = "about";
|
||||
private const string ACTION_ACCOUNTS = "accounts";
|
||||
private const string ACTION_COMPOSE = "compose";
|
||||
private const string ACTION_INSPECT = "inspect";
|
||||
private const string ACTION_HELP = "help";
|
||||
private const string ACTION_MAILTO = "mailto";
|
||||
private const string ACTION_PREFERENCES = "preferences";
|
||||
|
|
@ -64,6 +65,7 @@ public class GearyApplication : Gtk.Application {
|
|||
{ACTION_ABOUT, on_activate_about},
|
||||
{ACTION_ACCOUNTS, on_activate_accounts},
|
||||
{ACTION_COMPOSE, on_activate_compose},
|
||||
{ACTION_INSPECT, on_activate_inspect},
|
||||
{ACTION_HELP, on_activate_help},
|
||||
{ACTION_MAILTO, on_activate_mailto, "s"},
|
||||
{ACTION_PREFERENCES, on_activate_preferences},
|
||||
|
|
@ -74,6 +76,14 @@ public class GearyApplication : Gtk.Application {
|
|||
private const int64 FORCE_SHUTDOWN_USEC = 5 * USEC_PER_SEC;
|
||||
|
||||
|
||||
/** Object returned by {@link get_runtime_information}. */
|
||||
public struct RuntimeDetail {
|
||||
|
||||
public string name;
|
||||
public string value;
|
||||
|
||||
}
|
||||
|
||||
[Version (deprecated = true)]
|
||||
public static GearyApplication instance {
|
||||
get { return _instance; }
|
||||
|
|
@ -128,8 +138,93 @@ public class GearyApplication : Gtk.Application {
|
|||
private bool exiting_fired = false;
|
||||
private int exitcode = 0;
|
||||
private bool is_destroyed = false;
|
||||
private Components.Inspector? inspector = null;
|
||||
|
||||
|
||||
/**
|
||||
* Returns name/value pairs of application information.
|
||||
*
|
||||
* This includes Geary library version information, the current
|
||||
* desktop, and so on.
|
||||
*/
|
||||
public Gee.Collection<RuntimeDetail?> get_runtime_information() {
|
||||
Gee.LinkedList<RuntimeDetail?> info =
|
||||
new Gee.LinkedList<RuntimeDetail?>();
|
||||
|
||||
/// Application runtime information label
|
||||
info.add({ _("Geary version"), VERSION });
|
||||
/// Application runtime information label
|
||||
info.add({ _("GTK version"),
|
||||
"%u.%u.%u".printf(
|
||||
Gtk.get_major_version(),
|
||||
Gtk.get_minor_version(),
|
||||
Gtk.get_micro_version()
|
||||
)});
|
||||
/// Applciation runtime information label
|
||||
info.add({ _("GLib version"),
|
||||
"%u.%u.%u".printf(
|
||||
GLib.Version.major,
|
||||
GLib.Version.minor,
|
||||
GLib.Version.micro
|
||||
)});
|
||||
/// Application runtime information label
|
||||
info.add({ _("WebKitGTK version"),
|
||||
"%u.%u.%u".printf(
|
||||
WebKit.get_major_version(),
|
||||
WebKit.get_minor_version(),
|
||||
WebKit.get_micro_version()
|
||||
)});
|
||||
/// Application runtime information label
|
||||
info.add({ _("Desktop environment"),
|
||||
Environment.get_variable("XDG_CURRENT_DESKTOP") ??
|
||||
_("Unknown")
|
||||
});
|
||||
|
||||
// Distro name and version using LSB util
|
||||
|
||||
GLib.SubprocessLauncher launcher = new GLib.SubprocessLauncher(
|
||||
GLib.SubprocessFlags.STDOUT_PIPE |
|
||||
GLib.SubprocessFlags.STDERR_SILENCE
|
||||
);
|
||||
// Reset lang vars so we can guess the strings below
|
||||
launcher.setenv("LANGUAGE", "C", true);
|
||||
launcher.setenv("LANG", "C", true);
|
||||
launcher.setenv("LC_ALL", "C", true);
|
||||
|
||||
string lsb_output = "";
|
||||
try {
|
||||
GLib.Subprocess lsb_release = launcher.spawnv(
|
||||
{ "lsb_release", "-ir" }
|
||||
);
|
||||
lsb_release.communicate_utf8(null, null, out lsb_output, null);
|
||||
} catch (GLib.Error err) {
|
||||
warning("Failed to exec lsb_release: %s", err.message);
|
||||
}
|
||||
if (lsb_output != "") {
|
||||
foreach (string line in lsb_output.split("\n")) {
|
||||
string[] parts = line.split(":", 2);
|
||||
if (parts.length > 1) {
|
||||
if (parts[0].has_prefix("Distributor ID")) {
|
||||
/// Application runtime information label
|
||||
info.add(
|
||||
{ _("Distribution name"), parts[1].strip() }
|
||||
);
|
||||
} else if (parts[0].has_prefix("Release")) {
|
||||
/// Application runtime information label
|
||||
info.add(
|
||||
{ _("Distribution release"), parts[1].strip() }
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Application runtime information label
|
||||
info.add({ _("Installation prefix"), INSTALL_PREFIX });
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Signal that is activated when 'exit' is called, but before the application actually exits.
|
||||
*
|
||||
|
|
@ -250,6 +345,7 @@ public class GearyApplication : Gtk.Application {
|
|||
// Application accels
|
||||
add_app_accelerators(ACTION_COMPOSE, { "<Ctrl>N" });
|
||||
add_app_accelerators(ACTION_HELP, { "F1" });
|
||||
add_app_accelerators(ACTION_INSPECT, { "<Alt><Shift>I" });
|
||||
add_app_accelerators(ACTION_QUIT, { "<Ctrl>Q" });
|
||||
|
||||
// Common window accels
|
||||
|
|
@ -260,6 +356,7 @@ public class GearyApplication : Gtk.Application {
|
|||
add_window_accelerators(ACTION_UNDO, { "<Ctrl>Z" });
|
||||
|
||||
ComposerWidget.add_window_accelerators(this);
|
||||
Components.Inspector.add_window_accelerators(this);
|
||||
|
||||
yield controller.open_async(null);
|
||||
|
||||
|
|
@ -469,6 +566,18 @@ public class GearyApplication : Gtk.Application {
|
|||
}
|
||||
}
|
||||
|
||||
private void on_activate_inspect() {
|
||||
if (this.inspector == null) {
|
||||
this.inspector = new Components.Inspector(this);
|
||||
this.inspector.destroy.connect(() => {
|
||||
this.inspector = null;
|
||||
});
|
||||
this.inspector.show();
|
||||
} else {
|
||||
this.inspector.present();
|
||||
}
|
||||
}
|
||||
|
||||
private void on_activate_mailto(SimpleAction action, Variant? param) {
|
||||
if (this.controller != null && param != null) {
|
||||
this.controller.compose_mailto(param.get_string());
|
||||
|
|
|
|||
438
src/client/components/components-inspector.vala
Normal file
438
src/client/components/components-inspector.vala
Normal file
|
|
@ -0,0 +1,438 @@
|
|||
|
||||
/*
|
||||
* Copyright 2019 Michael Gratton <mike@vee.net>
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A window that displays debugging and development information.
|
||||
*/
|
||||
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")]
|
||||
public class Components.Inspector : Gtk.ApplicationWindow {
|
||||
|
||||
|
||||
private const int COL_MESSAGE = 0;
|
||||
|
||||
private const string ACTION_CLOSE = "inspector-close";
|
||||
private const string ACTION_PLAY_TOGGLE = "toggle-play";
|
||||
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
|
||||
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
|
||||
|
||||
private const ActionEntry[] action_entries = {
|
||||
{GearyApplication.ACTION_CLOSE, on_close },
|
||||
{GearyApplication.ACTION_COPY, on_copy_clicked },
|
||||
{ACTION_CLOSE, on_close },
|
||||
{ACTION_PLAY_TOGGLE, on_logs_play_toggled, null, "true" },
|
||||
{ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
|
||||
{ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
|
||||
};
|
||||
|
||||
public static void add_window_accelerators(GearyApplication app) {
|
||||
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
||||
app.add_window_accelerators(ACTION_PLAY_TOGGLE, { "space" } );
|
||||
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
|
||||
}
|
||||
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.HeaderBar header_bar;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Stack stack;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Button copy_button;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Widget logs_pane;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.ToggleButton play_button;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.ToggleButton search_button;
|
||||
|
||||
[GtkChild]
|
||||
private Hdy.SearchBar search_bar;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.SearchEntry search_entry;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.ScrolledWindow logs_scroller;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.TreeView logs_view;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.CellRendererText log_renderer;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.Widget detail_pane;
|
||||
|
||||
[GtkChild]
|
||||
private Gtk.ListBox detail_list;
|
||||
|
||||
private Gtk.ListStore logs_store = new Gtk.ListStore.newv({
|
||||
typeof(string)
|
||||
});
|
||||
|
||||
private Gtk.TreeModelFilter logs_filter;
|
||||
|
||||
private string[] logs_filter_terms = new string[0];
|
||||
|
||||
private string details;
|
||||
|
||||
private bool update_logs = true;
|
||||
private Geary.Logging.Record? first_pending = null;
|
||||
|
||||
private bool autoscroll = true;
|
||||
|
||||
|
||||
public Inspector(GearyApplication app) {
|
||||
Object(application: app);
|
||||
this.title = this.header_bar.title = _("Inspector");
|
||||
|
||||
add_action_entries(Inspector.action_entries, this);
|
||||
|
||||
this.search_bar.connect_entry(this.search_entry);
|
||||
|
||||
GLib.Settings system = app.config.gnome_interface;
|
||||
system.bind(
|
||||
"monospace-font-name",
|
||||
this.log_renderer, "font",
|
||||
SettingsBindFlags.DEFAULT
|
||||
);
|
||||
|
||||
StringBuilder details = new StringBuilder();
|
||||
foreach (GearyApplication.RuntimeDetail? detail
|
||||
in app.get_runtime_information()) {
|
||||
this.detail_list.add(
|
||||
new DetailRow("%s:".printf(detail.name), detail.value)
|
||||
);
|
||||
details.append_printf("%s: %s\n", detail.name, detail.value);
|
||||
}
|
||||
this.details = details.str;
|
||||
|
||||
// Enable updates to get the log marker
|
||||
enable_log_updates(true);
|
||||
|
||||
// Install the listener then starting add the backlog
|
||||
// (ba-doom-tish) so to avoid the race.
|
||||
Geary.Logging.set_log_listener(this.on_log_record);
|
||||
|
||||
Gtk.ListStore logs_store = this.logs_store;
|
||||
Geary.Logging.Record? logs = Geary.Logging.get_logs();
|
||||
int index = 0;
|
||||
while (logs != null) {
|
||||
if (should_append(logs)) {
|
||||
string message = logs.format();
|
||||
Gtk.TreeIter iter;
|
||||
logs_store.insert(out iter, index++);
|
||||
logs_store.set_value(iter, COL_MESSAGE, message);
|
||||
}
|
||||
logs = logs.next;
|
||||
}
|
||||
|
||||
this.logs_filter = new Gtk.TreeModelFilter(logs_store, null);
|
||||
this.logs_filter.set_visible_func((model, iter) => {
|
||||
bool ret = true;
|
||||
if (this.logs_filter_terms.length > 0) {
|
||||
ret = true;
|
||||
Value value;
|
||||
model.get_value(iter, COL_MESSAGE, out value);
|
||||
string? message = (string) value;
|
||||
if (message != null) {
|
||||
message = message.casefold();
|
||||
foreach (string term in this.logs_filter_terms) {
|
||||
if (!message.contains(term)) {
|
||||
ret = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
});
|
||||
|
||||
this.logs_view.set_model(this.logs_filter);
|
||||
}
|
||||
|
||||
public override void destroy() {
|
||||
Geary.Logging.set_log_listener(null);
|
||||
base.destroy();
|
||||
}
|
||||
|
||||
public override bool key_press_event(Gdk.EventKey event) {
|
||||
bool ret = Gdk.EVENT_PROPAGATE;
|
||||
|
||||
if (this.search_bar.search_mode_enabled &&
|
||||
event.keyval == Gdk.Key.Escape) {
|
||||
// Manually deactivate search so the button stays in sync
|
||||
this.search_button.set_active(false);
|
||||
ret = Gdk.EVENT_STOP;
|
||||
}
|
||||
|
||||
if (ret == Gdk.EVENT_PROPAGATE) {
|
||||
ret = this.search_bar.handle_event(event);
|
||||
}
|
||||
|
||||
if (ret == Gdk.EVENT_PROPAGATE &&
|
||||
this.search_bar.search_mode_enabled) {
|
||||
// Ensure <Space> and others are passed to the search
|
||||
// entry before getting used as an accelerator.
|
||||
ret = this.search_entry.key_press_event(event);
|
||||
}
|
||||
|
||||
if (ret == Gdk.EVENT_PROPAGATE) {
|
||||
ret = base.key_press_event(event);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
private void enable_log_updates(bool enabled) {
|
||||
// Log a marker to indicate when it was started/stopped
|
||||
debug(
|
||||
"---- 8< ---- %s %s ---- 8< ----",
|
||||
this.header_bar.title,
|
||||
enabled ? "▶" : "■"
|
||||
);
|
||||
|
||||
this.update_logs = enabled;
|
||||
|
||||
// Disable autoscroll when not updating as well to stop the
|
||||
// tree view jumping to the bottom when changing the filter.
|
||||
this.autoscroll = enabled;
|
||||
|
||||
if (enabled) {
|
||||
Geary.Logging.Record? logs = this.first_pending;
|
||||
while (logs != null) {
|
||||
append_record(logs);
|
||||
logs = logs.next;
|
||||
}
|
||||
this.first_pending = null;
|
||||
}
|
||||
}
|
||||
|
||||
private inline bool should_append(Geary.Logging.Record record) {
|
||||
// Blacklist GdkPixbuf since it spams us e.g. when window
|
||||
// focus changes, including between MainWindow and the
|
||||
// Inspector, which is very annoying.
|
||||
return (record.domain != "GdkPixbuf");
|
||||
}
|
||||
|
||||
private async void save(string path,
|
||||
GLib.Cancellable? cancellable)
|
||||
throws GLib.Error {
|
||||
GLib.File dest = GLib.File.new_for_path(path);
|
||||
GLib.FileIOStream dest_io = yield dest.create_readwrite_async(
|
||||
GLib.FileCreateFlags.NONE,
|
||||
GLib.Priority.DEFAULT,
|
||||
cancellable
|
||||
);
|
||||
GLib.DataOutputStream out = new GLib.DataOutputStream(
|
||||
new GLib.BufferedOutputStream(dest_io.get_output_stream())
|
||||
);
|
||||
|
||||
out.put_string(this.details);
|
||||
out.put_byte('\n');
|
||||
out.put_byte('\n');
|
||||
|
||||
Gtk.TreeModel model = this.logs_view.model;
|
||||
Gtk.TreeIter? iter;
|
||||
bool valid = model.get_iter_first(out iter);
|
||||
while (valid && !cancellable.is_cancelled()) {
|
||||
Value value;
|
||||
model.get_value(iter, COL_MESSAGE, out value);
|
||||
string? message = (string) value;
|
||||
if (message != null) {
|
||||
out.put_string(message);
|
||||
out.put_byte('\n');
|
||||
}
|
||||
valid = model.iter_next(ref iter);
|
||||
}
|
||||
|
||||
yield out.close_async();
|
||||
yield dest_io.close_async();
|
||||
}
|
||||
|
||||
private void update_ui() {
|
||||
bool logs_visible = this.stack.visible_child == this.logs_pane;
|
||||
uint logs_selected = this.logs_view.get_selection().count_selected_rows();
|
||||
this.copy_button.set_sensitive(!logs_visible || logs_selected > 0);
|
||||
this.play_button.set_visible(logs_visible);
|
||||
this.search_button.set_visible(logs_visible);
|
||||
}
|
||||
|
||||
private void update_scrollbar() {
|
||||
Gtk.Adjustment adj = this.logs_scroller.get_vadjustment();
|
||||
adj.set_value(adj.upper - adj.page_size);
|
||||
}
|
||||
|
||||
private void update_logs_filter() {
|
||||
string cleaned =
|
||||
Geary.String.reduce_whitespace(this.search_entry.text).casefold();
|
||||
this.logs_filter_terms = cleaned.split(" ");
|
||||
this.logs_filter.refilter();
|
||||
}
|
||||
|
||||
private void append_record(Geary.Logging.Record record) {
|
||||
if (should_append(record)) {
|
||||
Gtk.TreeIter inserted_iter;
|
||||
this.logs_store.append(out inserted_iter);
|
||||
this.logs_store.set_value(inserted_iter, COL_MESSAGE, record.format());
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_visible_child_changed() {
|
||||
update_ui();
|
||||
}
|
||||
|
||||
private void on_copy_clicked() {
|
||||
string clipboard_value = "";
|
||||
if (this.stack.visible_child == this.logs_pane) {
|
||||
StringBuilder rows = new StringBuilder();
|
||||
Gtk.TreeModel model = this.logs_view.model;
|
||||
foreach (Gtk.TreePath path in
|
||||
this.logs_view.get_selection().get_selected_rows(null)) {
|
||||
Gtk.TreeIter iter;
|
||||
if (model.get_iter(out iter, path)) {
|
||||
Value value;
|
||||
model.get_value(iter, COL_MESSAGE, out value);
|
||||
|
||||
string? message = (string) value;
|
||||
if (message != null) {
|
||||
rows.append(message);
|
||||
rows.append_c('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
clipboard_value = rows.str;
|
||||
} else if (this.stack.visible_child == this.detail_pane) {
|
||||
clipboard_value = this.details;
|
||||
}
|
||||
|
||||
if (!Geary.String.is_empty(clipboard_value)) {
|
||||
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_save_as_clicked() {
|
||||
Gtk.FileChooserNative chooser = new Gtk.FileChooserNative(
|
||||
_("Save As"),
|
||||
this,
|
||||
Gtk.FileChooserAction.SAVE,
|
||||
_("Save As"),
|
||||
_("Cancel")
|
||||
);
|
||||
chooser.set_current_name(
|
||||
new GLib.DateTime.now_local().format("Geary Inspector - %F %T.txt")
|
||||
);
|
||||
|
||||
if (chooser.run() == Gtk.ResponseType.ACCEPT) {
|
||||
this.save.begin(
|
||||
chooser.get_filename(),
|
||||
null,
|
||||
(obj, res) => {
|
||||
try {
|
||||
this.save.end(res);
|
||||
} catch (GLib.Error err) {
|
||||
warning("Failed to save inspector data: %s", err.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_logs_size_allocate() {
|
||||
if (this.autoscroll) {
|
||||
update_scrollbar();
|
||||
}
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_logs_selection_changed() {
|
||||
update_ui();
|
||||
}
|
||||
|
||||
private void on_logs_search_toggled(GLib.SimpleAction action,
|
||||
GLib.Variant? param) {
|
||||
bool enabled = !((bool) action.state);
|
||||
this.search_bar.set_search_mode(enabled);
|
||||
action.set_state(enabled);
|
||||
}
|
||||
|
||||
private void on_logs_search_activated() {
|
||||
this.search_button.set_active(true);
|
||||
this.search_entry.grab_focus();
|
||||
}
|
||||
|
||||
private void on_logs_play_toggled(GLib.SimpleAction action,
|
||||
GLib.Variant? param) {
|
||||
bool enabled = !((bool) action.state);
|
||||
enable_log_updates(enabled);
|
||||
action.set_state(enabled);
|
||||
}
|
||||
|
||||
[GtkCallback]
|
||||
private void on_logs_search_changed() {
|
||||
update_logs_filter();
|
||||
}
|
||||
|
||||
private void on_log_record(Geary.Logging.Record record) {
|
||||
if (this.update_logs) {
|
||||
GLib.MainContext.default().invoke(() => {
|
||||
append_record(record);
|
||||
return GLib.Source.REMOVE;
|
||||
});
|
||||
} else if (this.first_pending == null) {
|
||||
this.first_pending = record;
|
||||
}
|
||||
}
|
||||
|
||||
private void on_close() {
|
||||
destroy();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private class Components.DetailRow : Gtk.ListBoxRow {
|
||||
|
||||
|
||||
private Gtk.Grid layout { get; private set; default = new Gtk.Grid(); }
|
||||
private Gtk.Label label { get; private set; default = new Gtk.Label(""); }
|
||||
private Gtk.Label value { get; private set; default = new Gtk.Label(""); }
|
||||
|
||||
|
||||
public DetailRow(string label, string value) {
|
||||
get_style_context().add_class("geary-labelled-row");
|
||||
|
||||
this.label.halign = Gtk.Align.START;
|
||||
this.label.valign = Gtk.Align.CENTER;
|
||||
this.label.set_text(label);
|
||||
this.label.show();
|
||||
|
||||
this.value.halign = Gtk.Align.END;
|
||||
this.value.hexpand = true;
|
||||
this.value.valign = Gtk.Align.CENTER;
|
||||
this.value.xalign = 1.0f;
|
||||
this.value.set_text(value);
|
||||
this.value.show();
|
||||
|
||||
this.layout.orientation = Gtk.Orientation.HORIZONTAL;
|
||||
this.layout.add(this.label);
|
||||
this.layout.add(this.value);
|
||||
this.layout.show();
|
||||
add(this.layout);
|
||||
|
||||
this.activatable = false;
|
||||
show();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -135,8 +135,6 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
|
|||
{ACTION_SHOW_EXTENDED, on_toggle_action, null, "false", on_show_extended_toggled },
|
||||
};
|
||||
|
||||
public static Gee.MultiMap<string, string> action_accelerators = new Gee.HashMultiMap<string, string>();
|
||||
|
||||
public static void add_window_accelerators(GearyApplication application) {
|
||||
application.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
|
||||
application.add_window_accelerators(ACTION_CUT, { "<Ctrl>x" } );
|
||||
|
|
|
|||
|
|
@ -24,7 +24,10 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
|||
Geary.ErrorContext error,
|
||||
Geary.AccountInformation? account,
|
||||
Geary.ServiceInformation? service) {
|
||||
Object(use_header_bar: 1);
|
||||
Object(
|
||||
transient_for: parent,
|
||||
use_header_bar: 1
|
||||
);
|
||||
set_default_size(600, -1);
|
||||
|
||||
this.error = error;
|
||||
|
|
@ -50,18 +53,18 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
|
|||
|
||||
private string format_details() {
|
||||
StringBuilder details = new StringBuilder();
|
||||
details.append_printf(
|
||||
"Geary version: %s\n",
|
||||
GearyApplication.VERSION
|
||||
);
|
||||
details.append_printf(
|
||||
"GTK version: %u.%u.%u\n",
|
||||
Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version()
|
||||
);
|
||||
details.append_printf(
|
||||
"Desktop: %s\n",
|
||||
Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "Unknown"
|
||||
);
|
||||
|
||||
Gtk.ApplicationWindow? parent =
|
||||
this.get_toplevel() as Gtk.ApplicationWindow;
|
||||
GearyApplication? app = (parent != null)
|
||||
? parent.application as GearyApplication
|
||||
: null;
|
||||
if (app != null) {
|
||||
foreach (GearyApplication.RuntimeDetail? detail
|
||||
in app.get_runtime_information()) {
|
||||
details.append_printf("%s: %s", detail.name, detail.value);
|
||||
}
|
||||
}
|
||||
if (this.account != null) {
|
||||
details.append_printf(
|
||||
"Account id: %s\n",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ geary_client_vala_sources = files(
|
|||
'accounts/accounts-manager.vala',
|
||||
|
||||
'components/client-web-view.vala',
|
||||
'components/components-inspector.vala',
|
||||
'components/components-placeholder-pane.vala',
|
||||
'components/components-validator.vala',
|
||||
'components/count-badge.vala',
|
||||
|
|
@ -124,6 +125,7 @@ geary_client_dependencies = [
|
|||
gtk,
|
||||
json_glib,
|
||||
libcanberra,
|
||||
libhandy,
|
||||
libnotify,
|
||||
libsecret,
|
||||
libsoup,
|
||||
|
|
|
|||
|
|
@ -13,8 +13,19 @@
|
|||
|
||||
namespace Geary.Logging {
|
||||
|
||||
|
||||
/** Specifies the default number of log records retained. */
|
||||
public const uint DEFAULT_MAX_LOG_BUFFER_LENGTH = 4096;
|
||||
|
||||
private const string DOMAIN = "Geary";
|
||||
|
||||
/**
|
||||
* Denotes a type of log message.
|
||||
*
|
||||
* Logging for each type of log message may be dynamically enabled or
|
||||
* disabled at run time by {@link enable_flags} and {@link
|
||||
* disable_flags}.
|
||||
*/
|
||||
[Flags]
|
||||
public enum Flag {
|
||||
NONE = 0,
|
||||
|
|
@ -36,21 +47,84 @@ public enum Flag {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A record of a single message sent to the logging system.
|
||||
*
|
||||
* A record is created for each message logged, and stored in a
|
||||
* limited-length, singly-linked buffer. Applications can retrieve
|
||||
* this by calling {@link get_logs} and then {get_next}, and can be
|
||||
* notified of new records via {@link set_log_listener}.
|
||||
*/
|
||||
public class Record {
|
||||
|
||||
|
||||
/** Returns the GLib domain of the log message. */
|
||||
public string domain { get; private set; }
|
||||
|
||||
/** Returns the next log record in the buffer, if any. */
|
||||
public Record? next { get; internal set; default = null; }
|
||||
|
||||
private LogLevelFlags flags;
|
||||
private int64 timestamp;
|
||||
private double elapsed;
|
||||
private string message;
|
||||
|
||||
|
||||
internal Record(string domain,
|
||||
LogLevelFlags flags,
|
||||
int64 timestamp,
|
||||
double elapsed,
|
||||
string message) {
|
||||
this.domain = domain;
|
||||
this.flags = flags;
|
||||
this.timestamp = timestamp;
|
||||
this.elapsed = elapsed;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
/** Returns a formatted string representation of this record. */
|
||||
public string format() {
|
||||
GLib.DateTime time = new GLib.DateTime.from_unix_utc(
|
||||
this.timestamp / 1000 / 1000
|
||||
).to_local();
|
||||
return "%s %02d:%02d:%02d %lf %s: %s".printf(
|
||||
to_prefix(this.flags),
|
||||
time.get_hour(), time.get_minute(), time.get_second(),
|
||||
this.elapsed,
|
||||
this.domain ?? "default",
|
||||
this.message
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Specifies the function signature for {@link set_log_listener}. */
|
||||
public delegate void LogRecord(Record record);
|
||||
|
||||
private int init_count = 0;
|
||||
private Flag logging_flags = Flag.NONE;
|
||||
private unowned FileStream? stream = null;
|
||||
private Timer? entry_timer = null;
|
||||
|
||||
private Record? first_record = null;
|
||||
private Record? last_record = null;
|
||||
private uint log_length = 0;
|
||||
private uint max_log_length = 0;
|
||||
private LogRecord? listener = null;
|
||||
|
||||
|
||||
/**
|
||||
* Must be called before ''any'' call to the Logging namespace.
|
||||
*
|
||||
* This will be initialized by the Engine when it's opened, but applications may want to set up
|
||||
* logging before that, in which case, call this directly.
|
||||
* This will be initialized by the Engine when it's opened, but
|
||||
* applications may want to set up logging before that, in which case,
|
||||
* call this directly.
|
||||
*/
|
||||
public void init() {
|
||||
if (init_count++ != 0)
|
||||
return;
|
||||
entry_timer = new Timer();
|
||||
max_log_length = DEFAULT_MAX_LOG_BUFFER_LENGTH;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -75,6 +149,12 @@ public void disable_flags(Flag flags) {
|
|||
logging_flags &= ~flags;
|
||||
}
|
||||
|
||||
/** Sets a function to be called when a new log record is created. */
|
||||
public void set_log_listener(LogRecord? new_listener) {
|
||||
listener = new_listener;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the current logging flags.
|
||||
*/
|
||||
|
|
@ -120,6 +200,11 @@ public inline void debug(Flag flags, string fmt, ...) {
|
|||
}
|
||||
}
|
||||
|
||||
/** Returns the oldest log record in the logging system's buffer. */
|
||||
public Record? get_logs() {
|
||||
return first_record;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a FileStream to receive all log output from the Engine, be it via the specialized
|
||||
* Logging calls (which use the topic-based {@link Flag} or GLib's standard issue
|
||||
|
|
@ -135,6 +220,37 @@ public void log_to(FileStream? stream) {
|
|||
public void default_handler(string? domain,
|
||||
LogLevelFlags log_levels,
|
||||
string message) {
|
||||
Record record = new Record(
|
||||
domain,
|
||||
log_levels,
|
||||
GLib.get_real_time(),
|
||||
entry_timer.elapsed(),
|
||||
message
|
||||
);
|
||||
entry_timer.start();
|
||||
|
||||
// Update the record linked list
|
||||
if (first_record == null) {
|
||||
first_record = record;
|
||||
last_record = record;
|
||||
} else {
|
||||
last_record.next = record;
|
||||
last_record = record;
|
||||
}
|
||||
log_length++;
|
||||
while (log_length > max_log_length) {
|
||||
first_record = first_record.next;
|
||||
log_length--;
|
||||
}
|
||||
if (first_record == null) {
|
||||
last_record = null;
|
||||
}
|
||||
|
||||
if (listener != null) {
|
||||
listener(record);
|
||||
}
|
||||
|
||||
// Print to the output stream if needed
|
||||
unowned FileStream? out = stream;
|
||||
if (out != null ||
|
||||
((LogLevelFlags.LEVEL_WARNING & log_levels) > 0) ||
|
||||
|
|
@ -145,17 +261,8 @@ public void default_handler(string? domain,
|
|||
out = GLib.stderr;
|
||||
}
|
||||
|
||||
GLib.Time tm = GLib.Time.local(time_t());
|
||||
out.printf(
|
||||
"%s %02d:%02d:%02d %lf %s: %s\n",
|
||||
to_prefix(log_levels),
|
||||
tm.hour, tm.minute, tm.second,
|
||||
entry_timer.elapsed(),
|
||||
domain ?? "default",
|
||||
message
|
||||
);
|
||||
|
||||
entry_timer.start();
|
||||
out.puts(record.format());
|
||||
out.putc('\n');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -95,9 +95,9 @@ private class Geary.ImapDB.Attachment : Geary.Attachment {
|
|||
save_file(part, attachments_dir, cancellable);
|
||||
update_db(cx, cancellable);
|
||||
} catch (Error err) {
|
||||
// Don't honour the cancellable here, we need to delete
|
||||
// it.
|
||||
this.delete(cx, cancellable);
|
||||
// Don't honour the cancellable here, it needs to be
|
||||
// deleted
|
||||
this.delete(cx, null);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
|
@ -161,10 +161,8 @@ private class Geary.ImapDB.Attachment : Geary.Attachment {
|
|||
// create directory, but don't throw exception if already exists
|
||||
try {
|
||||
target.get_parent().make_directory_with_parents(cancellable);
|
||||
} catch (IOError ioe) {
|
||||
// fall through if already exists
|
||||
if (!(ioe is IOError.EXISTS))
|
||||
throw ioe;
|
||||
} catch (IOError.EXISTS err) {
|
||||
// All good
|
||||
}
|
||||
|
||||
// Delete any existing file now since we might not be creating
|
||||
|
|
|
|||
|
|
@ -37,9 +37,10 @@ geary_c_options = [
|
|||
# Select libunwind's optimised, local-only backtrace unwiding. See
|
||||
# libunwind(3).
|
||||
'-DUNW_LOCAL_ONLY',
|
||||
# Neither GOA nor GCK want to hang out unless you are cool enough
|
||||
'-DGOA_API_IS_SUBJECT_TO_CHANGE',
|
||||
# None of these kids want to hang out unless you are cool enough
|
||||
'-DGCK_API_SUBJECT_TO_CHANGE',
|
||||
'-DGOA_API_IS_SUBJECT_TO_CHANGE',
|
||||
'-DHANDY_USE_UNSTABLE_API',
|
||||
]
|
||||
|
||||
subdir('sqlite3-unicodesn')
|
||||
|
|
|
|||
242
ui/components-inspector.ui
Normal file
242
ui/components-inspector.ui
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<requires lib="libhandy" version="0.0"/>
|
||||
<object class="GtkListStore" id="logs_store">
|
||||
<columns>
|
||||
<!-- column-name log -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
<data>
|
||||
<row>
|
||||
<col id="0" translatable="yes">Inspector opened</col>
|
||||
</row>
|
||||
</data>
|
||||
</object>
|
||||
<template class="ComponentsInspector" parent="GtkApplicationWindow">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="default_width">750</property>
|
||||
<property name="default_height">500</property>
|
||||
<child type="titlebar">
|
||||
<object class="GtkHeaderBar" id="header_bar">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="show_close_button">True</property>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="play_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes" comments="Tooltip for inspector button">Togggle appending new log entries</property>
|
||||
<property name="action_name">win.toggle-play</property>
|
||||
<property name="active">True</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">media-playback-start-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToggleButton" id="search_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes" comments="Tooltip for inspector button">Search fo matching log entries</property>
|
||||
<property name="action_name">win.toggle-search</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-find-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="title">
|
||||
<object class="GtkStackSwitcher">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stack">stack</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="save_as_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes" comments="Tooltip for inspector button">Save logs entries and details</property>
|
||||
<signal name="clicked" handler="on_save_as_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">document-save-as-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="copy_button">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes" comments="Tooltip for inspector button">Copy selected log entries</property>
|
||||
<property name="action_name">win.copy</property>
|
||||
<child>
|
||||
<object class="GtkImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">edit-copy-symbolic</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkStack" id="stack">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<signal name="notify::visible-child" handler="on_visible_child_changed" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkGrid" id="logs_pane">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="HdySearchBar" id="search_bar">
|
||||
<property name="name">search_bar</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry" id="search_entry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
<signal name="search-changed" handler="on_logs_search_changed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="logs_scroller">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="logs_view">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">logs_store</property>
|
||||
<property name="headers_visible">False</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="show_expanders">False</property>
|
||||
<signal name="size-allocate" handler="on_logs_size_allocate" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<property name="mode">multiple</property>
|
||||
<signal name="changed" handler="on_logs_selection_changed" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn" id="log_column">
|
||||
<property name="title" translatable="yes">column</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText" id="log_renderer"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="top_attach">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">logs_pane</property>
|
||||
<property name="title" translatable="yes" comments="Inspector stack title">Logs</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="detail_pane">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">never</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="max_content_width">600</property>
|
||||
<child>
|
||||
<object class="GtkViewport">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="HdyColumn">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_left">16</property>
|
||||
<property name="margin_right">16</property>
|
||||
<property name="margin_top">32</property>
|
||||
<property name="margin_bottom">32</property>
|
||||
<property name="maximum_width">500</property>
|
||||
<property name="linear_growth_width">1</property>
|
||||
<child>
|
||||
<object class="GtkFrame">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label_xalign">0</property>
|
||||
<child>
|
||||
<object class="GtkListBox" id="detail_list">
|
||||
<property name="name">detail_list</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="selection_mode">none</property>
|
||||
<property name="activate_on_single_click">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child type="label_item">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="name">general_pane</property>
|
||||
<property name="title" translatable="yes" comments="Inspector stack title">General</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</template>
|
||||
</interface>
|
||||
76
ui/geary.css
76
ui/geary.css
|
|
@ -207,6 +207,45 @@ grid.geary-message-summary {
|
|||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* GtkListboxRows with padded labels */
|
||||
|
||||
row.geary-labelled-row {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
row.geary-labelled-row > grid > * {
|
||||
margin: 18px 6px;
|
||||
}
|
||||
|
||||
row.geary-labelled-row > grid > *:first-child:dir(ltr),
|
||||
row.geary-labelled-row > grid > *:last-child:dir(rtl) {
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
row.geary-labelled-row > grid > *:last-child:dir(ltr),
|
||||
row.geary-labelled-row > grid > *:first-child:dir(rtl) {
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
/* Images should have some padding to offset them from adjacent
|
||||
widgets, but care ust be taken since images are also used as children
|
||||
of other widgets like entries, comboboxes and switches, and these
|
||||
shouldn't be be touched. */
|
||||
row.geary-labelled-row widget > image,
|
||||
row.geary-labelled-row grid > image {
|
||||
padding: 0px 6px;
|
||||
}
|
||||
|
||||
row.geary-labelled-row > grid > combobox,
|
||||
row.geary-labelled-row > grid > entry,
|
||||
row.geary-labelled-row:not(.geary-add-row) > grid > image,
|
||||
row.geary-labelled-row > grid > switch {
|
||||
/* These use more space than labels, so set their valign to center
|
||||
when adding them and free up some space around them here to keep a
|
||||
consistent row height. */
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
/* Accounts.Editor */
|
||||
|
||||
grid.geary-accounts-editor-pane-content {
|
||||
|
|
@ -230,10 +269,6 @@ label.geary-settings-heading {
|
|||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
row.geary-settings {
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
row.geary-settings entry {
|
||||
border-width: 0px;
|
||||
background-color: transparent;
|
||||
|
|
@ -249,20 +284,6 @@ row.geary-settings.geary-drag-icon {
|
|||
border: 1px solid @borders;
|
||||
}
|
||||
|
||||
row.geary-settings > grid > * {
|
||||
margin: 18px 6px;
|
||||
}
|
||||
|
||||
row.geary-settings > grid > *:first-child:dir(ltr),
|
||||
row.geary-settings > grid > *:last-child:dir(rtl) {
|
||||
margin-left: 18px;
|
||||
}
|
||||
|
||||
row.geary-settings > grid > *:last-child:dir(ltr),
|
||||
row.geary-settings > grid > *:first-child:dir(rtl) {
|
||||
margin-right: 18px;
|
||||
}
|
||||
|
||||
/* dir pseudo-class used here for required additional specificity */
|
||||
row.geary-settings > grid > grid.geary-drag-handle:dir(ltr),
|
||||
row.geary-settings > grid > grid.geary-drag-handle:dir(rtl) {
|
||||
|
|
@ -283,25 +304,6 @@ frame.geary-settings.geary-signature {
|
|||
min-height: 5em;
|
||||
}
|
||||
|
||||
/* Images should have some padding to offset them from adjacent
|
||||
widgets, but care ust be taken since images are also used as children
|
||||
of other widgets like entries, comboboxes and switches, and these
|
||||
shouldn't be be touched. */
|
||||
row.geary-settings widget > image,
|
||||
row.geary-settings grid > image {
|
||||
padding: 0px 6px;
|
||||
}
|
||||
|
||||
row.geary-settings > grid > combobox,
|
||||
row.geary-settings > grid > entry,
|
||||
row.geary-settings:not(.geary-add-row) > grid > image,
|
||||
row.geary-settings > grid > switch {
|
||||
/* These use more space than labels, so set their valign to center
|
||||
when adding them and free up some space around them here to keep a
|
||||
consistent row height. */
|
||||
margin: 0 12px;
|
||||
}
|
||||
|
||||
buttonbox.geary-settings {
|
||||
margin-top: 36px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
<file compressed="true" preprocess="xml-stripblanks">certificate_warning_dialog.glade</file>
|
||||
<file compressed="true">client-web-view.js</file>
|
||||
<file compressed="true">client-web-view-allow-remote-images.js</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">components-inspector.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">components-placeholder-pane.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">composer-headerbar.ui</file>
|
||||
<file compressed="true" preprocess="xml-stripblanks">composer-link-popover.ui</file>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue