Compare commits

..

1 commit
main ... gtk4

Author SHA1 Message Date
Niels De Graef
be3ed978e2 Draft: Port to GTK4 2025-12-15 09:40:38 +01:00
163 changed files with 13716 additions and 16536 deletions

View file

@ -67,7 +67,7 @@ build.container.fedora@x86_64:
gsettings-desktop-schemas
gsound-devel
gspell-devel
gtk3-devel
gtk4-devel
iso-codes-devel
itstool
json-glib-devel
@ -76,6 +76,7 @@ build.container.fedora@x86_64:
libicu-devel
libpeas-devel
libsecret-devel
libspelling-devel
libstemmer-devel
libunwind-devel
libxml2-devel
@ -93,8 +94,8 @@ build.container.fedora@x86_64:
# When branching a stable release, change 'main' to the
# release branch name to ensure that a new image will
# be created, tailored for the stable branch.
BRANCH_NAME: 'main'
CONTAINER_TAG: '2025-12-12.0'
BRANCH_NAME: 'nielsdg/gtk4'
CONTAINER_TAG: '2025-12-15.0'
FEDORA_VERSION: latest
# Derive FDO variables from this automatically.
# DO NOT edit, instead change the variables above

View file

@ -52,10 +52,10 @@ valac = meson.get_compiler('vala')
# Required libraries and other dependencies
#
target_glib = '2.74'
target_gtk = '3.24.24'
target_glib = '2.80'
target_gtk = '4.16.0'
target_vala = '0.56'
target_webkit = '2.30'
target_webkit = '2.40'
if not valac.version().version_compare('>=' + target_vala)
error('Vala does not meet minimum required version: ' + target_vala)
@ -64,9 +64,9 @@ endif
# Primary deps
glib = dependency('glib-2.0', version: '>=' + target_glib)
gmime = dependency('gmime-3.0', version: '>= 3.2.4')
gtk = dependency('gtk+-3.0', version: '>=' + target_gtk)
gtk = dependency('gtk4', version: '>=' + target_gtk)
sqlite = dependency('sqlite3', version: '>= 3.24')
webkit2gtk = dependency('webkit2gtk-4.1', version: '>=' + target_webkit)
webkitgtk = dependency('webkitgtk-6.0', version: '>=' + target_webkit)
# Secondary deps - keep sorted alphabetically
cairo = dependency('cairo')
@ -74,18 +74,17 @@ enchant = dependency('enchant-2', version: '>=2.1')
folks = dependency('folks', version: '>=0.11')
gck = dependency('gck-2')
gcr = dependency('gcr-4')
gdk = dependency('gdk-3.0', version: '>=' + target_gtk)
gee = dependency('gee-0.8', version: '>= 0.8.5')
gio = dependency('gio-2.0', version: '>=' + target_glib)
goa = dependency('goa-1.0')
gsound = dependency('gsound')
gspell = dependency('gspell-1')
libspelling = dependency('libspelling-1')
gthread = dependency('gthread-2.0', version: '>=' + target_glib)
icu_uc = dependency('icu-uc', version: '>=60')
iso_codes = dependency('iso-codes')
javascriptcoregtk = dependency('javascriptcoregtk-4.1', version: '>=' + target_webkit)
javascriptcoregtk = dependency('javascriptcoregtk-6.0', version: '>=' + target_webkit)
json_glib = dependency('json-glib-1.0', version: '>= 1.0')
libhandy = dependency('libhandy-1', version: '>= 1.6', required: false)
libadwaita = dependency('libadwaita-1', version: '>= 1.7')
libmath = cc.find_library('m')
libpeas = dependency('libpeas-2')
libsecret = dependency('libsecret-1', version: '>= 0.11')
@ -100,7 +99,7 @@ libunwind_generic_dep = dependency(
libxml = dependency('libxml-2.0', version: '>= 2.7.8')
libytnef = dependency('libytnef', version: '>= 1.9.3', required: get_option('tnef'))
posix = valac.find_library('posix')
webkit2gtk_web_extension = dependency('webkit2gtk-web-extension-4.1', version: '>=' + target_webkit)
webkitgtk_web_extension = dependency('webkitgtk-web-process-extension-6.0', version: '>=' + target_webkit)
# System dependencies above ensures appropriate versions for the
# following libraries, but the declared dependency is what we actually
@ -133,26 +132,6 @@ libstemmer = declare_dependency(
],
)
# Required until libhandy 1.2.1 is GA
libhandy_vapi = ''
if not libhandy.found()
libhandy_project = subproject(
'libhandy',
default_options: [
'examples=false',
'package_subdir=geary',
'tests=false',
]
)
libhandy = declare_dependency(
dependencies: [
libhandy_project.get_variable('libhandy_dep'),
libhandy_project.get_variable('libhandy_vapi')
]
)
libhandy_vapi = meson.project_build_root() / 'subprojects' / 'libhandy' / 'src'
endif
# Optional dependencies
appstreamcli = find_program('appstreamcli', required: false)
desktop_file_validate = find_program('desktop-file-validate', required: false)

View file

@ -278,6 +278,22 @@
}
]
},
{
"name": "libspelling",
"buildsystem": "meson",
"config-opts": [
"-Dintrospection=enabled",
"-Dvapi=true",
"-Ddocs=false"
],
"sources": [
{
"type": "git",
"url": "https://gitlab.gnome.org/GNOME/libspelling.git",
"branch": "main"
}
]
},
{
"name": "geary",
"buildsystem": "meson",

View file

@ -474,5 +474,5 @@ ui/conversation-viewer.ui
ui/find_bar.glade
ui/folder-popover.ui
ui/gtk/help-overlay.ui
ui/password-dialog.glade
ui/password-dialog.ui
ui/problem-details-dialog.ui

View file

@ -2054,7 +2054,7 @@ msgstr ""
#: src/client/dialogs/certificate-warning-dialog.vala:58
msgid "Geary will not add or update this email account."
msgstr "Geary no afegirà o actualitzarà aquest compte de correu electrònic."
msgstr "Geary no va afegir o actualitzar aquest compte de correu electrònic."
#: src/client/dialogs/certificate-warning-dialog.vala:63
msgid ""
@ -2905,7 +2905,7 @@ msgstr "Confia en aques_t servidor"
#: ui/certificate_warning_dialog.glade:52
msgid "_Dont Trust This Server"
msgstr "No confieu en aquest servi_dor"
msgstr "No confieu aquest servi_dor"
#: ui/composer-editor.ui:89
msgid "Bold text"
@ -3241,7 +3241,7 @@ msgstr "_Quant a Geary"
#: ui/conversation-contact-popover.ui:134
msgid "New Conversation…"
msgstr "Conversa nova…"
msgstr "Mou la conversa…"
#: ui/conversation-contact-popover.ui:146
msgid "Copy Email Address"

116
po/cs.po
View file

@ -11,8 +11,8 @@ msgid ""
msgstr ""
"Project-Id-Version: geary\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues/\n"
"POT-Creation-Date: 2025-12-12 00:02+0000\n"
"PO-Revision-Date: 2026-02-09 18:08+0100\n"
"POT-Creation-Date: 2025-10-22 09:10+0000\n"
"PO-Revision-Date: 2025-10-27 09:18+0100\n"
"Last-Translator: Daniel Rusek <mail@asciiwolf.com>\n"
"Language-Team: čeština <gnome-cs-list@gnome.org>\n"
"Language: cs\n"
@ -20,7 +20,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
"X-Generator: Poedit 3.8\n"
"X-Generator: Poedit 3.7\n"
#: desktop/geary-attach.contract.desktop.in:2
msgid "Send by email"
@ -48,7 +48,7 @@ msgstr "E-mail"
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
#: desktop/org.gnome.Geary.metainfo.xml.in.in:15
#: src/client/application/application-client.vala:18
#: src/client/application/application-client.vala:33
msgid "Send and receive email"
msgstr "Odesílejte a přijímejte e-maily"
@ -333,12 +333,12 @@ msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr "Kompatibilitu s GMail, Yahoo! Mail, Outlook.com a dalšími servery IMAP"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:50
#: desktop/org.gnome.Geary.metainfo.xml.in.in:51
msgid "Geary displaying a conversation"
msgstr "Geary zobrazující konverzaci"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:55
#: desktop/org.gnome.Geary.metainfo.xml.in.in:56
msgid "Geary showing the rich text composer"
msgstr "Geary se zobrazeným editorem formátovaného textu"
@ -825,140 +825,140 @@ msgstr "Soubor již existuje v „%s“. Nahrazením přepíšete jeho obsah."
msgid "_Replace"
msgstr "Nah_radit"
#: src/client/application/application-client.vala:19
#: src/client/application/application-client.vala:34
msgid "Copyright © 2016 Software Freedom Conservancy Inc."
msgstr "Copyright © 2016 Software Freedom Conservancy Inc."
#: src/client/application/application-client.vala:20
#: src/client/application/application-client.vala:35
msgid "Copyright © 2016-2021 Geary Development Team."
msgstr "Copyright © 20162020 Vývojářský tým aplikace Geary"
#: src/client/application/application-client.vala:37
msgid "Visit the Geary web site"
msgstr "Navštívit webové stránky Geary"
#. / Command line option
#: src/client/application/application-client.vala:77
#: src/client/application/application-client.vala:101
msgid "Print debug logging"
msgstr "Vypisovat ladicí záznamy"
#. / Command line option
#: src/client/application/application-client.vala:80
#: src/client/application/application-client.vala:104
msgid "Enable WebKitGTK Inspector in web views"
msgstr "Povolit inspektora WebKitGTK v zobrazeních formátovaných pomocí HTML"
#. / Command line option
#: src/client/application/application-client.vala:83
#: src/client/application/application-client.vala:107
msgid "Log conversation monitoring"
msgstr "Zaznamenávat sledování konverzace"
#. / Command line option
#: src/client/application/application-client.vala:86
#: src/client/application/application-client.vala:110
msgid "Log IMAP network deserialization"
msgstr "Zaznamenávat síťové deserializace protokolu IMAP"
#. / Command line option. "Normalization" can also be called
#. / "synchronization".
#: src/client/application/application-client.vala:90
#: src/client/application/application-client.vala:114
msgid "Log folder normalization"
msgstr "Zaznamenávat normalizace složky"
#. / Command line option
#: src/client/application/application-client.vala:93
#: src/client/application/application-client.vala:117
msgid "Log IMAP network activity"
msgstr "Zaznamenávat síťové aktivity IMAP"
#. / Command line option. The IMAP replay queue is how changes
#. / on the server are replicated on the client. It could
#. / also be called the IMAP events queue.
#: src/client/application/application-client.vala:98
#: src/client/application/application-client.vala:122
msgid "Log IMAP replay queue"
msgstr "Zaznamenávat frontu událostí IMAP"
#. / Command line option
#: src/client/application/application-client.vala:101
#: src/client/application/application-client.vala:125
msgid "Log SMTP network activity"
msgstr "Zaznamenávat síťové aktivity SMTP"
#. / Command line option
#: src/client/application/application-client.vala:104
#: src/client/application/application-client.vala:128
msgid "Log database queries (generates lots of messages)"
msgstr "Zaznamenávat databázové dotazy (generuje velké množství zpráv)"
#. / Command line option
#: src/client/application/application-client.vala:107
#: src/client/application/application-client.vala:131
msgid "Perform a graceful quit"
msgstr "Korektně ukončit"
#: src/client/application/application-client.vala:109
#: src/client/application/application-client.vala:133
msgid "Open a new window"
msgstr "Otevřít nové okno"
#. / Command line option
#: src/client/application/application-client.vala:112
#: src/client/application/application-client.vala:136
msgid "Revoke all pinned TLS server certificates"
msgstr "Odvolat všechny spjaté serverové certifikáty TLS"
#. / Command line option
#: src/client/application/application-client.vala:115
#: src/client/application/application-client.vala:139
msgid "Display program version"
msgstr "Zobrazit verzi programu"
#. / Application runtime information label
#: src/client/application/application-client.vala:243
#: src/client/application/application-client.vala:267
msgid "Geary version"
msgstr "Verze Geary"
#. / Application runtime information label
#: src/client/application/application-client.vala:245
#: src/client/application/application-client.vala:269
msgid "Geary revision"
msgstr "Revize Geary"
#. / Application runtime information label
#: src/client/application/application-client.vala:247
#: src/client/application/application-client.vala:271
msgid "GTK version"
msgstr "Verze GTK"
#. / Applciation runtime information label
#: src/client/application/application-client.vala:254
#: src/client/application/application-client.vala:278
msgid "GLib version"
msgstr "Verze GLib"
#. / Application runtime information label
#: src/client/application/application-client.vala:261
#: src/client/application/application-client.vala:285
msgid "WebKitGTK version"
msgstr "Verze WebKitGTK"
#. / Application runtime information label
#: src/client/application/application-client.vala:268
#: src/client/application/application-client.vala:292
msgid "Desktop environment"
msgstr "Uživatelské prostředí"
#. Translators: This is the file type displayed for
#. attachments with unknown file types.
#: src/client/application/application-client.vala:270
#: src/client/application/application-client.vala:276
#: src/client/application/application-client.vala:282
#: src/client/application/application-client.vala:294
#: src/client/application/application-client.vala:300
#: src/client/application/application-client.vala:306
#: src/client/components/components-attachment-pane.vala:88
msgid "Unknown"
msgstr "Neznámý"
#. / Application runtime information label
#: src/client/application/application-client.vala:274
#: src/client/application/application-client.vala:298
msgid "Distribution name"
msgstr "Název distribuce"
#. / Application runtime information label
#: src/client/application/application-client.vala:280
#: src/client/application/application-client.vala:304
msgid "Distribution release"
msgstr "Vydání distribuce"
#. / Application runtime information label
#: src/client/application/application-client.vala:286
#: src/client/application/application-client.vala:310
msgid "Installation prefix"
msgstr "Prefix instalace"
#: src/client/application/application-client.vala:544
msgid "Visit the Geary web site"
msgstr "Navštívit webové stránky Geary"
#: src/client/application/application-client.vala:545
#: src/client/application/application-client.vala:569
#, c-format
msgid "About %s"
msgstr "O aplikaci %s"
@ -966,7 +966,7 @@ msgstr "O aplikaci %s"
#. Translators: add your name and email address to receive
#. credit in the About dialog For example: Yamada Taro
#. <yamada.taro@example.com>
#: src/client/application/application-client.vala:549
#: src/client/application/application-client.vala:573
msgid "translator-credits"
msgstr ""
"Petr Šimáček <petr.simacek@gmail.com>\n"
@ -974,7 +974,7 @@ msgstr ""
#. / Command line warning, string substitution
#. / is the given argument
#: src/client/application/application-client.vala:1078
#: src/client/application/application-client.vala:1102
#, c-format
msgid "Unrecognised program argument: “%s”"
msgstr "Nerozpoznaný argument aplikace: „%s“"
@ -1548,7 +1548,7 @@ msgstr "Zkusit z_novu"
#. / Translators: Search entry placeholder text
#: src/client/components/components-search-bar.vala:12
#: src/client/folder-list/folder-list-search-branch.vala:53
#: src/client/util/util-i18n.vala:294
#: src/client/util/util-i18n.vala:298
msgid "Search"
msgstr "Hledat"
@ -2737,43 +2737,43 @@ msgctxt "Abbreviation for kilobyte"
msgid "KB"
msgstr "kB"
#: src/client/util/util-i18n.vala:267
#: src/client/util/util-i18n.vala:271
msgid "Inbox"
msgstr "Doručené"
#: src/client/util/util-i18n.vala:270
#: src/client/util/util-i18n.vala:274
msgid "Drafts"
msgstr "Koncepty"
#: src/client/util/util-i18n.vala:273
#: src/client/util/util-i18n.vala:277
msgid "Sent"
msgstr "Odeslané"
#: src/client/util/util-i18n.vala:276
#: src/client/util/util-i18n.vala:280
msgid "Starred"
msgstr "S hvězdičkou"
#: src/client/util/util-i18n.vala:279
#: src/client/util/util-i18n.vala:283
msgid "Important"
msgstr "Důležité"
#: src/client/util/util-i18n.vala:282
#: src/client/util/util-i18n.vala:286
msgid "All Mail"
msgstr "Všechny zprávy"
#: src/client/util/util-i18n.vala:285
#: src/client/util/util-i18n.vala:289
msgid "Junk"
msgstr "Nevyžádané"
#: src/client/util/util-i18n.vala:288
#: src/client/util/util-i18n.vala:292
msgid "Trash"
msgstr "Koš"
#: src/client/util/util-i18n.vala:291
#: src/client/util/util-i18n.vala:295
msgid "Outbox"
msgstr "K odeslání"
#: src/client/util/util-i18n.vala:297
#: src/client/util/util-i18n.vala:301
msgid "Archive"
msgstr "Archiv"
@ -3200,16 +3200,14 @@ msgstr "Přepnout vyhledávací lištu"
#: ui/components-inspector-error-view.ui:27
msgid ""
"If the problem is serious or persists, please save and send these details to "
"one of the <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">contact channels</a> or attach to a <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">new bug report</a>."
"one of the <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">contact "
"channels</a> or attach to a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">new bug report</a>."
msgstr ""
"Pokud je problém vážný, nebo přetrvává, uložte a odešlete prosím tyto údaje "
"na jeden z <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">kontaktních kanálů</a>, nebo zadejte <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">nové hlášení chyby</a>."
"na jeden z <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">kontaktních "
"kanálů</a>, nebo zadejte <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">nové hlášení chyby</a>."
#: ui/components-inspector-error-view.ui:42
msgid "Details:"

3106
po/el.po

File diff suppressed because it is too large Load diff

1308
po/gl.po

File diff suppressed because it is too large Load diff

136
po/hu.po
View file

@ -1,18 +1,18 @@
# Hungarian translation for geary.
# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026 Free Software Foundation, Inc.
# Copyright (C) 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025 Free Software Foundation, Inc.
# This file is distributed under the same license as the geary package.
#
# lukibeni <lukacs.bence1 at gmail dot com>, 2012, 2013.
# metalsasi <metalsasi at gmail dot com>, 2012.
# Balázs Úr <ur.balazs at fsf dot hu>, 2014, 2015, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025, 2026.
# Balázs Úr <ur.balazs at fsf dot hu>, 2014, 2015, 2017, 2018, 2019, 2020, 2021, 2022, 2023, 2024, 2025.
# Gabor Kelemen <kelemeng at gnome dot hu>, 2014.
# Balázs Meskó <mesko.balazs at fsf dot hu>, 2018, 2020, 2021.
msgid ""
msgstr ""
"Project-Id-Version: geary-master\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues/\n"
"POT-Creation-Date: 2025-12-12 00:02+0000\n"
"PO-Revision-Date: 2026-02-21 19:12+0100\n"
"POT-Creation-Date: 2025-08-27 08:07+0000\n"
"PO-Revision-Date: 2025-09-09 21:30+0200\n"
"Last-Translator: Balázs Úr <ur.balazs at fsf dot hu>\n"
"Language-Team: Hungarian <openscope at fsf dot hu>\n"
"Language: hu\n"
@ -20,7 +20,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Lokalize 25.08.1\n"
"X-Generator: Lokalize 24.12.3\n"
#: desktop/geary-attach.contract.desktop.in:2
msgid "Send by email"
@ -48,7 +48,7 @@ msgstr "E-mail"
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
#: desktop/org.gnome.Geary.metainfo.xml.in.in:15
#: src/client/application/application-client.vala:18
#: src/client/application/application-client.vala:33
msgid "Send and receive email"
msgstr "E-mailek küldése és fogadása"
@ -128,8 +128,8 @@ msgid ""
"Overrides the original colors in HTML messages to integrate better with the "
"app theme."
msgstr ""
"Felülbírálja a HTML-üzenetekben lévő eredeti színeket, hogy jobban "
"illeszkedjenek az alkalmazás témájához."
"Felülbírálja a HTML-üzenetekben lévő eredeti színeket, hogy jobban illeszkedje"
"nek az alkalmazás témájához."
#: desktop/org.gnome.Geary.gschema.xml:50
msgid "Move messages by default"
@ -331,12 +331,12 @@ msgstr ""
"kiszolgálókkal"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:50
#: desktop/org.gnome.Geary.metainfo.xml.in.in:51
msgid "Geary displaying a conversation"
msgstr "A Geary egy beszélgetést jelenít meg"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:55
#: desktop/org.gnome.Geary.metainfo.xml.in.in:56
msgid "Geary showing the rich text composer"
msgstr "A Geary gazdag szövegszerkesztője"
@ -824,140 +824,140 @@ msgstr ""
msgid "_Replace"
msgstr "_Csere"
#: src/client/application/application-client.vala:19
#: src/client/application/application-client.vala:34
msgid "Copyright © 2016 Software Freedom Conservancy Inc."
msgstr "Copyright © 2016 Software Freedom Conservancy Inc."
#: src/client/application/application-client.vala:20
#: src/client/application/application-client.vala:35
msgid "Copyright © 2016-2021 Geary Development Team."
msgstr "Copyright © 2016-2021 A Geary fejlesztőcsapat."
#: src/client/application/application-client.vala:37
msgid "Visit the Geary web site"
msgstr "Keresse fel a Geary weboldalát"
#. / Command line option
#: src/client/application/application-client.vala:77
#: src/client/application/application-client.vala:101
msgid "Print debug logging"
msgstr "Hibakeresési naplózás kiírása"
#. / Command line option
#: src/client/application/application-client.vala:80
#: src/client/application/application-client.vala:104
msgid "Enable WebKitGTK Inspector in web views"
msgstr "WebKitGTK vizsgáló engedélyezése a webes nézetekben"
#. / Command line option
#: src/client/application/application-client.vala:83
#: src/client/application/application-client.vala:107
msgid "Log conversation monitoring"
msgstr "Beszélgetésfigyelés naplózása"
#. / Command line option
#: src/client/application/application-client.vala:86
#: src/client/application/application-client.vala:110
msgid "Log IMAP network deserialization"
msgstr "IMAP hálózati visszafejtés naplózása"
#. / Command line option. "Normalization" can also be called
#. / "synchronization".
#: src/client/application/application-client.vala:90
#: src/client/application/application-client.vala:114
msgid "Log folder normalization"
msgstr "Mappa-normalizálás naplózása"
#. / Command line option
#: src/client/application/application-client.vala:93
#: src/client/application/application-client.vala:117
msgid "Log IMAP network activity"
msgstr "IMAP hálózati tevékenység naplózása"
#. / Command line option. The IMAP replay queue is how changes
#. / on the server are replicated on the client. It could
#. / also be called the IMAP events queue.
#: src/client/application/application-client.vala:98
#: src/client/application/application-client.vala:122
msgid "Log IMAP replay queue"
msgstr "IMAP eseménysor naplózása"
#. / Command line option
#: src/client/application/application-client.vala:101
#: src/client/application/application-client.vala:125
msgid "Log SMTP network activity"
msgstr "SMTP hálózati tevékenység naplózása"
#. / Command line option
#: src/client/application/application-client.vala:104
#: src/client/application/application-client.vala:128
msgid "Log database queries (generates lots of messages)"
msgstr "Adatbázis lekérdezések naplózása (sok üzenetet állít elő)"
#. / Command line option
#: src/client/application/application-client.vala:107
#: src/client/application/application-client.vala:131
msgid "Perform a graceful quit"
msgstr "Elegáns kilépés végrehajtása"
#: src/client/application/application-client.vala:109
#: src/client/application/application-client.vala:133
msgid "Open a new window"
msgstr "Új ablak megnyitása"
#. / Command line option
#: src/client/application/application-client.vala:112
#: src/client/application/application-client.vala:136
msgid "Revoke all pinned TLS server certificates"
msgstr "Az összes rögzített TLS kiszolgáló tanúsítvány visszavonása"
#. / Command line option
#: src/client/application/application-client.vala:115
#: src/client/application/application-client.vala:139
msgid "Display program version"
msgstr "A program verziójának megjelenítése"
#. / Application runtime information label
#: src/client/application/application-client.vala:243
#: src/client/application/application-client.vala:267
msgid "Geary version"
msgstr "Geary verzió"
#. / Application runtime information label
#: src/client/application/application-client.vala:245
#: src/client/application/application-client.vala:269
msgid "Geary revision"
msgstr "Geary revízió"
#. / Application runtime information label
#: src/client/application/application-client.vala:247
#: src/client/application/application-client.vala:271
msgid "GTK version"
msgstr "GTK verzió"
#. / Applciation runtime information label
#: src/client/application/application-client.vala:254
#: src/client/application/application-client.vala:278
msgid "GLib version"
msgstr "GLib verzió"
#. / Application runtime information label
#: src/client/application/application-client.vala:261
#: src/client/application/application-client.vala:285
msgid "WebKitGTK version"
msgstr "WebKitGTK verzió"
#. / Application runtime information label
#: src/client/application/application-client.vala:268
#: src/client/application/application-client.vala:292
msgid "Desktop environment"
msgstr "Asztali környezet"
#. Translators: This is the file type displayed for
#. attachments with unknown file types.
#: src/client/application/application-client.vala:270
#: src/client/application/application-client.vala:276
#: src/client/application/application-client.vala:282
#: src/client/application/application-client.vala:294
#: src/client/application/application-client.vala:300
#: src/client/application/application-client.vala:306
#: src/client/components/components-attachment-pane.vala:88
msgid "Unknown"
msgstr "Ismeretlen"
#. / Application runtime information label
#: src/client/application/application-client.vala:274
#: src/client/application/application-client.vala:298
msgid "Distribution name"
msgstr "Disztribúció neve"
#. / Application runtime information label
#: src/client/application/application-client.vala:280
#: src/client/application/application-client.vala:304
msgid "Distribution release"
msgstr "Disztribúció kiadása"
#. / Application runtime information label
#: src/client/application/application-client.vala:286
#: src/client/application/application-client.vala:310
msgid "Installation prefix"
msgstr "Telepítési előtag"
#: src/client/application/application-client.vala:544
msgid "Visit the Geary web site"
msgstr "Keresse fel a Geary weboldalát"
#: src/client/application/application-client.vala:545
#: src/client/application/application-client.vala:569
#, c-format
msgid "About %s"
msgstr "%s névjegye"
@ -965,7 +965,7 @@ msgstr "%s névjegye"
#. Translators: add your name and email address to receive
#. credit in the About dialog For example: Yamada Taro
#. <yamada.taro@example.com>
#: src/client/application/application-client.vala:549
#: src/client/application/application-client.vala:573
msgid "translator-credits"
msgstr ""
"Kelemen Gábor <kelemeng at gnome dot hu>\n"
@ -975,7 +975,7 @@ msgstr ""
#. / Command line warning, string substitution
#. / is the given argument
#: src/client/application/application-client.vala:1078
#: src/client/application/application-client.vala:1102
#, c-format
msgid "Unrecognised program argument: “%s”"
msgstr "Felismerhetetlen programargumentum: „%s”"
@ -1449,8 +1449,8 @@ msgid ""
"Overrides the original colors in HTML messages to integrate better with the "
"app theme. Requires restart."
msgstr ""
"Felülbírálja a HTML-üzenetekben lévő eredeti színeket, hogy jobban "
"illeszkedjenek az alkalmazás témájához. Újraindítást igényel."
"Felülbírálja a HTML-üzenetekben lévő eredeti színeket, hogy jobban illeszkedje"
"nek az alkalmazás témájához. Újraindítást igényel."
#. / Translators: Preferences page title
#: src/client/components/components-preferences-window.vala:203
@ -1534,7 +1534,7 @@ msgstr "Ú_jra"
#. / Translators: Search entry placeholder text
#: src/client/components/components-search-bar.vala:12
#: src/client/folder-list/folder-list-search-branch.vala:53
#: src/client/util/util-i18n.vala:294
#: src/client/util/util-i18n.vala:298
msgid "Search"
msgstr "Keresés"
@ -2718,43 +2718,43 @@ msgctxt "Abbreviation for kilobyte"
msgid "KB"
msgstr "KB"
#: src/client/util/util-i18n.vala:267
#: src/client/util/util-i18n.vala:271
msgid "Inbox"
msgstr "Beérkezett üzenetek"
#: src/client/util/util-i18n.vala:270
#: src/client/util/util-i18n.vala:274
msgid "Drafts"
msgstr "Piszkozatok"
#: src/client/util/util-i18n.vala:273
#: src/client/util/util-i18n.vala:277
msgid "Sent"
msgstr "Elküldött"
#: src/client/util/util-i18n.vala:276
#: src/client/util/util-i18n.vala:280
msgid "Starred"
msgstr "Csillagozott"
#: src/client/util/util-i18n.vala:279
#: src/client/util/util-i18n.vala:283
msgid "Important"
msgstr "Fontos"
#: src/client/util/util-i18n.vala:282
#: src/client/util/util-i18n.vala:286
msgid "All Mail"
msgstr "Minden levél"
#: src/client/util/util-i18n.vala:285
#: src/client/util/util-i18n.vala:289
msgid "Junk"
msgstr "Levélszemét"
#: src/client/util/util-i18n.vala:288
#: src/client/util/util-i18n.vala:292
msgid "Trash"
msgstr "Kuka"
#: src/client/util/util-i18n.vala:291
#: src/client/util/util-i18n.vala:295
msgid "Outbox"
msgstr "Postázandó"
#: src/client/util/util-i18n.vala:297
#: src/client/util/util-i18n.vala:301
msgid "Archive"
msgstr "Archívum"
@ -3175,23 +3175,17 @@ msgid "Toggle find bar"
msgstr "Keresősáv ki- vagy bekapcsolása"
#: ui/components-inspector-error-view.ui:27
#| msgid ""
#| "If the problem is serious or persists, please save and send these details "
#| "to one of the <a href=\"https://wiki.gnome.org/Apps/Geary/"
#| "Contact\">contact channels</a> or attach to a <a href=\"https://"
#| "wiki.gnome.org/Apps/Geary/ReportingABug\">new bug report</a>."
msgid ""
"If the problem is serious or persists, please save and send these details to "
"one of the <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">contact channels</a> or attach to a <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">new bug report</a>."
"one of the <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">contact "
"channels</a> or attach to a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">new bug report</a>."
msgstr ""
"Ha a probléma komoly, és továbbra is fennáll, akkor mentse el és küldje el a r"
"észleteket a <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/Contact\">"
"kapcsoalti csatornák egyikére</a>, vagy mellékelje egy <a href=\"https://gitla"
"b.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-Features\">új hi"
"bajelentéshez</a>."
"Ha a probléma komoly, és továbbra is fennáll, akkor mentse el és küldje el a "
"részleteket a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"Contact\">kapcsoalti csatornák egyikére</a>, vagy mellékelje egy <a "
"href=\"https://wiki.gnome.org/Apps/Geary/ReportingABug\">új hibajelentéshez</"
"a>."
#: ui/components-inspector-error-view.ui:42
msgid "Details:"

8566
po/kk.po

File diff suppressed because it is too large Load diff

View file

@ -22,22 +22,21 @@
# Alex Jr <alexjrsh@proton.me>, 2023.
# Hugo Fortini <hugofortinipbi@gmail.com>, 2024.
# Álvaro Burns <>, 2025.
# Juliano de Souza Camargo <julianosc@pm.me>, 2025.
#
msgid ""
msgstr ""
"Project-Id-Version: geary\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues/\n"
"POT-Creation-Date: 2025-12-15 00:46+0000\n"
"PO-Revision-Date: 2025-12-15 14:20-0300\n"
"Last-Translator: Juliano de Souza Camargo <julianosc@pm.me>\n"
"POT-Creation-Date: 2025-06-09 19:33+0000\n"
"PO-Revision-Date: 2025-06-11 13:57-0300\n"
"Last-Translator: Álvaro Burns <>\n"
"Language-Team: Brazilian Portuguese <https://br.gnome.org/traducao/>\n"
"Language: pt_BR\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Gtranslator 49.0\n"
"X-Generator: Gtranslator 48.0\n"
"X-Project-Style: gnome\n"
"X-DL-Team: pt_BR\n"
"X-DL-Module: geary\n"
@ -45,54 +44,115 @@ msgstr ""
"X-DL-Domain: po\n"
"X-DL-State: Translating\n"
#: desktop/geary-attach.contract.desktop.in:2
#: desktop/geary-attach.contract.desktop.in:3
msgid "Send by email"
msgstr "Enviar por e-mail"
#: desktop/geary-attach.contract.desktop.in:5
#: desktop/geary-attach.contract.desktop.in:6
msgid "Send files using Geary"
msgstr "Envie arquivos usando Geary"
#. Translators: The application name
#: desktop/geary-autostart.desktop.in.in:2
#: desktop/org.gnome.Geary.desktop.in.in:2
#: desktop/org.gnome.Geary.metainfo.xml.in.in:11
#: desktop/geary-autostart.desktop.in.in:3
#: desktop/org.gnome.Geary.appdata.xml.in.in:11
#: desktop/org.gnome.Geary.desktop.in.in:3
#: src/client/accounts/accounts-editor-servers-pane.vala:551
#: src/client/application/application-main-window.vala:710
msgid "Geary"
msgstr "Geary"
#: desktop/geary-autostart.desktop.in.in:3
#: desktop/org.gnome.Geary.desktop.in.in:3
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
msgid "Email"
msgstr "E-mail"
#. Translators: The application's summary / tagline
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
#: desktop/org.gnome.Geary.metainfo.xml.in.in:15
#: src/client/application/application-client.vala:18
#: desktop/geary-autostart.desktop.in.in:5
#: desktop/org.gnome.Geary.appdata.xml.in.in:15
#: desktop/org.gnome.Geary.desktop.in.in:5
#: src/client/application/application-client.vala:33
msgid "Send and receive email"
msgstr "Envie e receba e-mail"
#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon.
#: desktop/geary-autostart.desktop.in.in:6
#: desktop/geary-autostart.desktop.in.in:7
msgid "Email;E-mail;Mail;"
msgstr ""
"Email;E-mail;email;emails;e-"
"mail;Mail;Mensagem;Mensagens;mensagem;mensagens;Correio eletrônico;correio "
"eletrônico;"
"Email;E-mail;email;emails;e-mail;Mail;Mensagem;Mensagens;mensagem;mensagens;"
"Correio eletrônico;correio eletrônico;"
#. Translators: The development team's name
#: desktop/org.gnome.Geary.appdata.xml.in.in:13
msgid "Geary Development Team"
msgstr "Equipe de Desenvolvimento do Geary"
#: desktop/org.gnome.Geary.appdata.xml.in.in:17
msgid ""
"Geary is an email application built around conversations, for the GNOME "
"desktop. It allows you to read, find and send email with a straightforward, "
"modern interface."
msgstr ""
"Geary é um aplicativo de e-mail construído em conversas, para o ambiente do "
"GNOME. Ele permite que você leia, localize e envie e-mail com uma interface "
"simples e moderna."
#: desktop/org.gnome.Geary.appdata.xml.in.in:22
msgid ""
"Conversations allow you to read a complete discussion without having to find "
"and click from message to message."
msgstr ""
"As conversas permitem que você leia uma discussão completa sem ter que "
"procurar e clicar de mensagem em mensagem."
#: desktop/org.gnome.Geary.appdata.xml.in.in:26
msgid "Gearys features include:"
msgstr "Os recursos do Geary incluem:"
#: desktop/org.gnome.Geary.appdata.xml.in.in:28
msgid "Quick email account setup"
msgstr "Configuração rápida de conta de e-mail"
#: desktop/org.gnome.Geary.appdata.xml.in.in:29
msgid "Shows related messages together in conversations"
msgstr "Mostrar mensagens relacionadas juntas, nas conversas"
#: desktop/org.gnome.Geary.appdata.xml.in.in:30
msgid "Fast, full text and keyword search"
msgstr "Pesquisa rápida, de texto completo e de palavra-chave"
#: desktop/org.gnome.Geary.appdata.xml.in.in:31
msgid "Full-featured HTML and plain text message composer"
msgstr "Compositor repleto de recursos para mensagens em HTML e texto simples"
#: desktop/org.gnome.Geary.appdata.xml.in.in:32
msgid "Desktop notification of new mail"
msgstr "Notificação na área de trabalho de novo e-mail"
#: desktop/org.gnome.Geary.appdata.xml.in.in:33
msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr ""
"Compatível com GMail, Yahoo! Mail, Outlook.com e outros servidores IMAP"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.appdata.xml.in.in:51
msgid "Geary displaying a conversation"
msgstr "Geary exibindo uma conversa"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.appdata.xml.in.in:56
msgid "Geary showing the rich text composer"
msgstr "Geary mostrando um compositor de texto rico"
#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon.
#: desktop/org.gnome.Geary.desktop.in.in:6
#: desktop/org.gnome.Geary.desktop.in.in:7
msgid "Mail;E-mail;email;IMAP;GMail;Yahoo;Hotmail;Outlook;"
msgstr "Correio;E-mail;email;IMAP;GMail;Yahoo;Hotmail;Outlook;"
#: desktop/org.gnome.Geary.desktop.in.in:23
#: desktop/org.gnome.Geary.desktop.in.in:24
msgid "Compose Message"
msgstr "Escrever mensagem"
#: desktop/org.gnome.Geary.desktop.in.in:27
#: desktop/org.gnome.Geary.desktop.in.in:28
msgid "New Window"
msgstr "Nova janela"
@ -147,10 +207,14 @@ msgid "True if we should display a short preview of each message."
msgstr "Verdadeiro se devemos exibir uma visualização curta de cada mensagem."
#: desktop/org.gnome.Geary.gschema.xml:44
msgid "Unset colors provided in HTML emails"
msgstr "Não definir cores fornecidas em e-mails HTML"
#: desktop/org.gnome.Geary.gschema.xml:45
msgid "Override colors in HTML emails"
msgstr "Sobrepor cores em e-mails HTML"
#: desktop/org.gnome.Geary.gschema.xml:45
#: desktop/org.gnome.Geary.gschema.xml:46
msgid ""
"Overrides the original colors in HTML messages to integrate better with the "
"app theme."
@ -158,19 +222,19 @@ msgstr ""
"Sobrepor a cor original em mensagens HTML para melhor integração com tema do "
"aplicativo."
#: desktop/org.gnome.Geary.gschema.xml:50
#: desktop/org.gnome.Geary.gschema.xml:51
msgid "Move messages by default"
msgstr "Mover mensagens por padrão"
#: desktop/org.gnome.Geary.gschema.xml:51
#: desktop/org.gnome.Geary.gschema.xml:52
msgid "When tagging a message, move it to destination folder."
msgstr "Ao marcar uma mensagem, mova-a para a pasta de destino."
#: desktop/org.gnome.Geary.gschema.xml:56
#: desktop/org.gnome.Geary.gschema.xml:57
msgid "Use single key shortcuts"
msgstr "Usar atalhos de tecla única"
#: desktop/org.gnome.Geary.gschema.xml:57
#: desktop/org.gnome.Geary.gschema.xml:58
msgid ""
"Enables shortcuts for email actions that do not require pressing <Ctrl> to "
"emulate those used by Gmail."
@ -178,11 +242,11 @@ msgstr ""
"Habilita atalhos de teclado para ações de e-mail que não exigem pressionar "
"<Ctrl> para emular aqueles usados pelo Gmail."
#: desktop/org.gnome.Geary.gschema.xml:64
#: desktop/org.gnome.Geary.gschema.xml:65
msgid "Languages that shall be used in the spell checker"
msgstr "Idiomas que devem ser usados na verificação ortográfica"
#: desktop/org.gnome.Geary.gschema.xml:65
#: desktop/org.gnome.Geary.gschema.xml:66
msgid ""
"A list of POSIX locales, with the empty list disabling spell checking and "
"the null list using desktop languages by default."
@ -190,12 +254,12 @@ msgstr ""
"Uma lista de localidades POSIX, com a lista vazia desabilitando verificação "
"ortográfica e a lista nula usando idiomas de desktop por padrão."
#: desktop/org.gnome.Geary.gschema.xml:72
#: desktop/org.gnome.Geary.gschema.xml:73
msgid "Languages that are displayed in the spell checker popover"
msgstr ""
"Idiomas que são exibidos na janela sobreposta de verificação ortográfica"
#: desktop/org.gnome.Geary.gschema.xml:73
#: desktop/org.gnome.Geary.gschema.xml:74
msgid ""
"List of languages that are always displayed in the popover of the spell "
"checker."
@ -203,69 +267,69 @@ msgstr ""
"Lista de idiomas que sempre são exibidos na janela sobreposta da verificação "
"ortográfica."
#: desktop/org.gnome.Geary.gschema.xml:78
#: desktop/org.gnome.Geary.gschema.xml:79
msgid "Run application in background on logon and when closed"
msgstr "Executa o aplicativo em segundo plano ao iniciar sessão e ao fechar"
#: desktop/org.gnome.Geary.gschema.xml:79
#: desktop/org.gnome.Geary.gschema.xml:80
msgid "True to run application in background."
msgstr "Verdadeiro para executar o aplicativo em segundo plano."
#: desktop/org.gnome.Geary.gschema.xml:84
#: desktop/org.gnome.Geary.gschema.xml:85
msgid "Ask when opening an attachment"
msgstr "Perguntar ao abrir um anexo"
#: desktop/org.gnome.Geary.gschema.xml:85
#: desktop/org.gnome.Geary.gschema.xml:86
msgid "True to ask when opening an attachment."
msgstr "Verdadeiro para perguntar ao abrir um anexo."
#: desktop/org.gnome.Geary.gschema.xml:90
#: desktop/org.gnome.Geary.gschema.xml:91
msgid "Whether to compose emails in HTML"
msgstr "Se deve compor e-mails em HTML"
#: desktop/org.gnome.Geary.gschema.xml:91
#: desktop/org.gnome.Geary.gschema.xml:92
msgid "True to compose emails in HTML; false for plain text."
msgstr "Verdadeiro para compor e-mails em HTML; falso para texto simples."
#: desktop/org.gnome.Geary.gschema.xml:96
#: desktop/org.gnome.Geary.gschema.xml:97
msgid "Advisory strategy for full-text searching"
msgstr "Estratégia consultiva para pesquisa de texto completo"
#: desktop/org.gnome.Geary.gschema.xml:97
#: desktop/org.gnome.Geary.gschema.xml:98
msgid ""
"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”."
msgstr ""
"Valores aceitáveis são “exact”, “conservative”, “aggressive” e “horizon”."
#: desktop/org.gnome.Geary.gschema.xml:102
#: desktop/org.gnome.Geary.gschema.xml:103
msgid "Zoom of conversation viewer"
msgstr "Ampliação do visualizador de conversa"
#: desktop/org.gnome.Geary.gschema.xml:103
#: desktop/org.gnome.Geary.gschema.xml:104
msgid "The zoom to apply on the conservation view."
msgstr "A ampliação a ser aplicada no visualizador de conversa."
#: desktop/org.gnome.Geary.gschema.xml:108
#: desktop/org.gnome.Geary.gschema.xml:109
msgid "Size of detached composer window"
msgstr "Tamanho da janela do compositor destacada"
#: desktop/org.gnome.Geary.gschema.xml:109
#: desktop/org.gnome.Geary.gschema.xml:110
msgid "The last recorded size of the detached composer window."
msgstr "O último tamanho registrado da janela do compositor destacada."
#: desktop/org.gnome.Geary.gschema.xml:114
#: desktop/org.gnome.Geary.gschema.xml:115
msgid "Allow images for these domains"
msgstr "Permitir imagens destes domínios"
#: desktop/org.gnome.Geary.gschema.xml:115
#: desktop/org.gnome.Geary.gschema.xml:116
msgid "Images from these domains will be trusted"
msgstr "Imagens destes domínios serão confiáveis"
#: desktop/org.gnome.Geary.gschema.xml:120
#: desktop/org.gnome.Geary.gschema.xml:121
msgid "Undo sending email delay"
msgstr "Desfazer atraso de envio de e-mail"
#: desktop/org.gnome.Geary.gschema.xml:121
#: desktop/org.gnome.Geary.gschema.xml:122
msgid ""
"The number of seconds to wait before sending an email. Set to zero or less "
"to disable."
@ -273,11 +337,11 @@ msgstr ""
"O número de segundos para aguardar antes de eenviar um e-mail. Defina para "
"zero ou menos para desabilitar."
#: desktop/org.gnome.Geary.gschema.xml:127
#: desktop/org.gnome.Geary.gschema.xml:128
msgid "Brief notification display time"
msgstr "Tempo de exibição de notificação breve"
#: desktop/org.gnome.Geary.gschema.xml:128
#: desktop/org.gnome.Geary.gschema.xml:129
msgid ""
"The length of time in seconds for which brief notifications should be "
"displayed."
@ -285,87 +349,25 @@ msgstr ""
"O comprimento de tempo, em segundos, pelo qual as notificações breves devem "
"ser exibidas."
#: desktop/org.gnome.Geary.gschema.xml:134
#: desktop/org.gnome.Geary.gschema.xml:135
msgid "List of optional plugins"
msgstr "Lista de plugins opcionais"
#: desktop/org.gnome.Geary.gschema.xml:135
#: desktop/org.gnome.Geary.gschema.xml:136
msgid "Plugins listed here will be loaded on startup."
msgstr "Plugins listados aqui serão carregados na inicialização."
#: desktop/org.gnome.Geary.gschema.xml:140
#: desktop/org.gnome.Geary.gschema.xml:141
msgid "Whether we migrated the old settings"
msgstr "Se migramos as configurações antigas"
#: desktop/org.gnome.Geary.gschema.xml:141
#: desktop/org.gnome.Geary.gschema.xml:142
msgid ""
"False to check for the old “org.yorba.geary”-schema and copy its values."
msgstr ""
"Falso para verificar pelo antigo esquema “org.yorba.geary” e copiar seus "
"valores."
#. Translators: The development team's name
#: desktop/org.gnome.Geary.metainfo.xml.in.in:13
msgid "Geary Development Team"
msgstr "Equipe de Desenvolvimento do Geary"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:17
msgid ""
"Geary is an email application built around conversations, for the GNOME "
"desktop. It allows you to read, find and send email with a straightforward, "
"modern interface."
msgstr ""
"Geary é um aplicativo de e-mail construído em conversas, para o ambiente do "
"GNOME. Ele permite que você leia, localize e envie e-mail com uma interface "
"simples e moderna."
#: desktop/org.gnome.Geary.metainfo.xml.in.in:22
msgid ""
"Conversations allow you to read a complete discussion without having to find "
"and click from message to message."
msgstr ""
"As conversas permitem que você leia uma discussão completa sem ter que "
"procurar e clicar de mensagem em mensagem."
#: desktop/org.gnome.Geary.metainfo.xml.in.in:26
msgid "Gearys features include:"
msgstr "Os recursos do Geary incluem:"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:28
msgid "Quick email account setup"
msgstr "Configuração rápida de conta de e-mail"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:29
msgid "Shows related messages together in conversations"
msgstr "Mostrar mensagens relacionadas juntas, nas conversas"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:30
msgid "Fast, full text and keyword search"
msgstr "Pesquisa rápida, de texto completo e de palavra-chave"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:31
msgid "Full-featured HTML and plain text message composer"
msgstr "Compositor repleto de recursos para mensagens em HTML e texto simples"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:32
msgid "Desktop notification of new mail"
msgstr "Notificação na área de trabalho de novo e-mail"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:33
msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr ""
"Compatível com GMail, Yahoo! Mail, Outlook.com e outros servidores IMAP"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:50
msgid "Geary displaying a conversation"
msgstr "Geary exibindo uma conversa"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:55
msgid "Geary showing the rich text composer"
msgstr "Geary mostrando um compositor de texto rico"
#. Translators: In-app notification label, when
#. the app had a problem pinning an otherwise
#. untrusted TLS certificate
@ -849,140 +851,140 @@ msgstr ""
msgid "_Replace"
msgstr "_Substituir"
#: src/client/application/application-client.vala:19
#: src/client/application/application-client.vala:34
msgid "Copyright © 2016 Software Freedom Conservancy Inc."
msgstr "Copyright © 2016 Software Freedom Conservancy Inc."
#: src/client/application/application-client.vala:20
#: src/client/application/application-client.vala:35
msgid "Copyright © 2016-2021 Geary Development Team."
msgstr "Copyright © 2016-2021 Geary Development Team."
#: src/client/application/application-client.vala:37
msgid "Visit the Geary web site"
msgstr "Visite o website do Geary"
#. / Command line option
#: src/client/application/application-client.vala:77
#: src/client/application/application-client.vala:101
msgid "Print debug logging"
msgstr "Emite log de depuração"
#. / Command line option
#: src/client/application/application-client.vala:80
#: src/client/application/application-client.vala:104
msgid "Enable WebKitGTK Inspector in web views"
msgstr "Habilita Inspetor WebKitGTK em exibições web"
#. / Command line option
#: src/client/application/application-client.vala:83
#: src/client/application/application-client.vala:107
msgid "Log conversation monitoring"
msgstr "Registra monitoramento de conversa"
#. / Command line option
#: src/client/application/application-client.vala:86
#: src/client/application/application-client.vala:110
msgid "Log IMAP network deserialization"
msgstr "Registra desserialização de rede IMAP"
#. / Command line option. "Normalization" can also be called
#. / "synchronization".
#: src/client/application/application-client.vala:90
#: src/client/application/application-client.vala:114
msgid "Log folder normalization"
msgstr "Registra normalização de pasta"
#. / Command line option
#: src/client/application/application-client.vala:93
#: src/client/application/application-client.vala:117
msgid "Log IMAP network activity"
msgstr "Registra atividade de rede de IMAP"
#. / Command line option. The IMAP replay queue is how changes
#. / on the server are replicated on the client. It could
#. / also be called the IMAP events queue.
#: src/client/application/application-client.vala:98
#: src/client/application/application-client.vala:122
msgid "Log IMAP replay queue"
msgstr "Registra lista de repetição de IMAP"
#. / Command line option
#: src/client/application/application-client.vala:101
#: src/client/application/application-client.vala:125
msgid "Log SMTP network activity"
msgstr "Registra atividade de rede de STMP"
#. / Command line option
#: src/client/application/application-client.vala:104
#: src/client/application/application-client.vala:128
msgid "Log database queries (generates lots of messages)"
msgstr "Registra consultas ao banco de dados (gera muitas mensagens)"
#. / Command line option
#: src/client/application/application-client.vala:107
#: src/client/application/application-client.vala:131
msgid "Perform a graceful quit"
msgstr "Realiza uma saída graciosa"
#: src/client/application/application-client.vala:109
#: src/client/application/application-client.vala:133
msgid "Open a new window"
msgstr "Abre uma nova janela"
#. / Command line option
#: src/client/application/application-client.vala:112
#: src/client/application/application-client.vala:136
msgid "Revoke all pinned TLS server certificates"
msgstr "Revoga todos os certificados de servidor TLS fixados"
#. / Command line option
#: src/client/application/application-client.vala:115
#: src/client/application/application-client.vala:139
msgid "Display program version"
msgstr "Mostra versão do programa"
#. / Application runtime information label
#: src/client/application/application-client.vala:243
#: src/client/application/application-client.vala:267
msgid "Geary version"
msgstr "Versão do Geary"
#. / Application runtime information label
#: src/client/application/application-client.vala:245
#: src/client/application/application-client.vala:269
msgid "Geary revision"
msgstr "Revisão do Geary"
#. / Application runtime information label
#: src/client/application/application-client.vala:247
#: src/client/application/application-client.vala:271
msgid "GTK version"
msgstr "Versão do GTK"
#. / Applciation runtime information label
#: src/client/application/application-client.vala:254
#: src/client/application/application-client.vala:278
msgid "GLib version"
msgstr "Versão do GLib"
#. / Application runtime information label
#: src/client/application/application-client.vala:261
#: src/client/application/application-client.vala:285
msgid "WebKitGTK version"
msgstr "Versão do WebKitGTK"
#. / Application runtime information label
#: src/client/application/application-client.vala:268
#: src/client/application/application-client.vala:292
msgid "Desktop environment"
msgstr "Ambiente"
#. Translators: This is the file type displayed for
#. attachments with unknown file types.
#: src/client/application/application-client.vala:270
#: src/client/application/application-client.vala:276
#: src/client/application/application-client.vala:282
#: src/client/application/application-client.vala:294
#: src/client/application/application-client.vala:300
#: src/client/application/application-client.vala:306
#: src/client/components/components-attachment-pane.vala:88
msgid "Unknown"
msgstr "Desconhecido"
#. / Application runtime information label
#: src/client/application/application-client.vala:274
#: src/client/application/application-client.vala:298
msgid "Distribution name"
msgstr "Nome da distribuição"
#. / Application runtime information label
#: src/client/application/application-client.vala:280
#: src/client/application/application-client.vala:304
msgid "Distribution release"
msgstr "Lançamento da distribuição"
#. / Application runtime information label
#: src/client/application/application-client.vala:286
#: src/client/application/application-client.vala:310
msgid "Installation prefix"
msgstr "Prefixo de lançamento"
#: src/client/application/application-client.vala:544
msgid "Visit the Geary web site"
msgstr "Visite o website do Geary"
#: src/client/application/application-client.vala:545
#: src/client/application/application-client.vala:569
#, c-format
msgid "About %s"
msgstr "Sobre %s"
@ -990,7 +992,7 @@ msgstr "Sobre %s"
#. Translators: add your name and email address to receive
#. credit in the About dialog For example: Yamada Taro
#. <yamada.taro@example.com>
#: src/client/application/application-client.vala:549
#: src/client/application/application-client.vala:573
msgid "translator-credits"
msgstr ""
"Leonardo Lemos <leonardolemos@live.com>\n"
@ -1001,7 +1003,7 @@ msgstr ""
#. / Command line warning, string substitution
#. / is the given argument
#: src/client/application/application-client.vala:1078
#: src/client/application/application-client.vala:1102
#, c-format
msgid "Unrecognised program argument: “%s”"
msgstr "Argumento do programa não reconhecida “%s”"
@ -1559,7 +1561,7 @@ msgstr "_Tentar novamente"
#. / Translators: Search entry placeholder text
#: src/client/components/components-search-bar.vala:12
#: src/client/folder-list/folder-list-search-branch.vala:53
#: src/client/util/util-i18n.vala:294
#: src/client/util/util-i18n.vala:298
msgid "Search"
msgstr "Pesquisar"
@ -2231,11 +2233,11 @@ msgid_plural "New messages"
msgstr[0] "Nova mensagem"
msgstr[1] "Novas mensagens"
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:3
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:4
msgid "Email Templates"
msgstr "Modelos de e-mail"
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:4
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:5
msgid "Create reusable templates for sending email"
msgstr "Crie modelos reusáveis para enviar e-mails"
@ -2289,14 +2291,14 @@ msgstr "Editar"
#. / merge in composer
#. Translators: The name of the folder used to
#. display merged email
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:4
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:5
#: src/client/plugin/mail-merge/mail-merge.vala:288
#: src/client/plugin/mail-merge/mail-merge.vala:395
#: src/client/plugin/mail-merge/mail-merge.vala:494
msgid "Mail Merge"
msgstr "Mesclar e-mail"
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:5
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:6
msgid "Fill in and send email templates using a spreadsheet"
msgstr "Preencha e envie modelos de e-mails usando uma planilha"
@ -2345,11 +2347,11 @@ msgstr "Inserir campo"
msgid "Comma separated values (CSV)"
msgstr "Valores separados por vírgula (CSV)"
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:3
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:4
msgid "Messaging Menu"
msgstr "Menu de mensagens"
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:4
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:5
msgid "Displays Unity Messaging Menu notifications for new email"
msgstr "Exibe notificações do menu de mensageria do Unity para novos e-mails"
@ -2358,11 +2360,11 @@ msgstr "Exibe notificações do menu de mensageria do Unity para novos e-mails"
msgid "%s — New Messages"
msgstr "%s — Novas Mensagens"
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:3
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:4
msgid "Sent Sound"
msgstr "Som de envio"
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:4
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:5
msgid "Plays the desktop sent-mail sound when an email is sent"
msgstr ""
"Reproduz o som de envio de e-mail do ambiente quando um e-mail é enviado"
@ -2731,43 +2733,43 @@ msgctxt "Abbreviation for kilobyte"
msgid "KB"
msgstr "KB"
#: src/client/util/util-i18n.vala:267
#: src/client/util/util-i18n.vala:271
msgid "Inbox"
msgstr "Caixa de entrada"
#: src/client/util/util-i18n.vala:270
#: src/client/util/util-i18n.vala:274
msgid "Drafts"
msgstr "Rascunhos"
#: src/client/util/util-i18n.vala:273
#: src/client/util/util-i18n.vala:277
msgid "Sent"
msgstr "Enviado"
#: src/client/util/util-i18n.vala:276
#: src/client/util/util-i18n.vala:280
msgid "Starred"
msgstr "Com estrela"
#: src/client/util/util-i18n.vala:279
#: src/client/util/util-i18n.vala:283
msgid "Important"
msgstr "Importante"
#: src/client/util/util-i18n.vala:282
#: src/client/util/util-i18n.vala:286
msgid "All Mail"
msgstr "Todos os e-mails"
#: src/client/util/util-i18n.vala:285
#: src/client/util/util-i18n.vala:289
msgid "Junk"
msgstr "Lixo eletrônico"
#: src/client/util/util-i18n.vala:288
#: src/client/util/util-i18n.vala:292
msgid "Trash"
msgstr "Lixeira"
#: src/client/util/util-i18n.vala:291
#: src/client/util/util-i18n.vala:295
msgid "Outbox"
msgstr "Caixa de saída"
#: src/client/util/util-i18n.vala:297
#: src/client/util/util-i18n.vala:301
msgid "Archive"
msgstr "Arquivar"
@ -2829,18 +2831,18 @@ msgstr ""
"Arquivo | Arquivos | Arquivado | Arquivados | Arquivamento | Archive | "
"Archives"
#: src/engine/rfc822/rfc822-message.vala:575
#: src/engine/rfc822/rfc822-message.vala:572
#, c-format
msgid "Could not determine mime type for “%s”."
msgstr "Não foi possível determinar o tipo mime para “%s”."
#: src/engine/rfc822/rfc822-message.vala:586
#: src/engine/rfc822/rfc822-message.vala:583
#, c-format
msgid "Could not determine content type for mime type “%s” on “%s”."
msgstr ""
"Não foi possível determinar o tipo de conteúdo para o tipo mime “%s” em “%s”."
#: src/engine/rfc822/rfc822-message.vala:1039
#: src/engine/rfc822/rfc822-message.vala:1036
msgid "(no subject)"
msgstr "(sem assunto)"
@ -3193,16 +3195,14 @@ msgstr "Ativa/desativa a barra de busca"
#: ui/components-inspector-error-view.ui:27
msgid ""
"If the problem is serious or persists, please save and send these details to "
"one of the <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">contact channels</a> or attach to a <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">new bug report</a>."
"one of the <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">contact "
"channels</a> or attach to a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">new bug report</a>."
msgstr ""
"Se o problema for sério ou persistir, por favor salve e envie esses detalhes "
"para um dos <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">canais de contato</a> ou anexe a um <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">novo relatório de erro</a>."
"para um dos <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">canais de "
"contato</a> ou anexe a um <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">novo relatório de erro</a>."
#: ui/components-inspector-error-view.ui:42
msgid "Details:"
@ -3782,9 +3782,6 @@ msgstr "Lemb_rar senha"
msgid "_Authenticate"
msgstr "_Autenticar"
#~ msgid "Unset colors provided in HTML emails"
#~ msgstr "Não definir cores fornecidas em e-mails HTML"
#, c-format
#~ msgid "Email to %s saved"
#~ msgstr "E-mail para %s salvo"

120
po/sv.po
View file

@ -19,8 +19,8 @@ msgid ""
msgstr ""
"Project-Id-Version: geary\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues/\n"
"POT-Creation-Date: 2025-12-11 23:00+0000\n"
"PO-Revision-Date: 2025-12-21 22:48+0100\n"
"POT-Creation-Date: 2025-07-21 08:41+0000\n"
"PO-Revision-Date: 2025-07-02 22:58+0200\n"
"Last-Translator: Anders Jonsson <anders.jonsson@norsjovallen.se>\n"
"Language-Team: Swedish <tp-sv@listor.tp-sv.se>\n"
"Language: sv\n"
@ -28,7 +28,7 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"X-Generator: Poedit 3.8\n"
"X-Generator: Poedit 3.6\n"
#: desktop/geary-attach.contract.desktop.in:2
msgid "Send by email"
@ -56,7 +56,7 @@ msgstr "E-post"
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
#: desktop/org.gnome.Geary.metainfo.xml.in.in:15
#: src/client/application/application-client.vala:18
#: src/client/application/application-client.vala:33
msgid "Send and receive email"
msgstr "Skicka och ta emot e-post"
@ -333,12 +333,12 @@ msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr "Kompatibel med GMail, Yahoo! Mail, Outlook.com och andra IMAP-servrar"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:50
#: desktop/org.gnome.Geary.metainfo.xml.in.in:51
msgid "Geary displaying a conversation"
msgstr "Geary visande en konversation"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:55
#: desktop/org.gnome.Geary.metainfo.xml.in.in:56
msgid "Geary showing the rich text composer"
msgstr "Geary visande rich text-redigeraren"
@ -826,140 +826,140 @@ msgstr ""
msgid "_Replace"
msgstr "E_rsätt"
#: src/client/application/application-client.vala:19
#: src/client/application/application-client.vala:34
msgid "Copyright © 2016 Software Freedom Conservancy Inc."
msgstr "Copyright © 2016 Software Freedom Conservancy Inc."
#: src/client/application/application-client.vala:20
#: src/client/application/application-client.vala:35
msgid "Copyright © 2016-2021 Geary Development Team."
msgstr "Copyright © 2016-2021 Gearys utvecklingsgrupp."
#: src/client/application/application-client.vala:37
msgid "Visit the Geary web site"
msgstr "Besök Gearys webbplats"
#. / Command line option
#: src/client/application/application-client.vala:77
#: src/client/application/application-client.vala:101
msgid "Print debug logging"
msgstr "Skriv ut felsökningsloggning"
#. / Command line option
#: src/client/application/application-client.vala:80
#: src/client/application/application-client.vala:104
msgid "Enable WebKitGTK Inspector in web views"
msgstr "Aktivera WebKitGTK-inspektör i webbvyer"
#. / Command line option
#: src/client/application/application-client.vala:83
#: src/client/application/application-client.vala:107
msgid "Log conversation monitoring"
msgstr "Logga konversationsövervakning"
#. / Command line option
#: src/client/application/application-client.vala:86
#: src/client/application/application-client.vala:110
msgid "Log IMAP network deserialization"
msgstr "Logga avserialisering av IMAP-nätverksdata"
#. / Command line option. "Normalization" can also be called
#. / "synchronization".
#: src/client/application/application-client.vala:90
#: src/client/application/application-client.vala:114
msgid "Log folder normalization"
msgstr "Logga mappsynkronisering"
#. / Command line option
#: src/client/application/application-client.vala:93
#: src/client/application/application-client.vala:117
msgid "Log IMAP network activity"
msgstr "Logga IMAP-nätverksaktivitet"
#. / Command line option. The IMAP replay queue is how changes
#. / on the server are replicated on the client. It could
#. / also be called the IMAP events queue.
#: src/client/application/application-client.vala:98
#: src/client/application/application-client.vala:122
msgid "Log IMAP replay queue"
msgstr "Logga IMAP-händelser"
#. / Command line option
#: src/client/application/application-client.vala:101
#: src/client/application/application-client.vala:125
msgid "Log SMTP network activity"
msgstr "Logga SMTP-nätverksaktivitet"
#. / Command line option
#: src/client/application/application-client.vala:104
#: src/client/application/application-client.vala:128
msgid "Log database queries (generates lots of messages)"
msgstr "Logga databasförfrågningar (detta genererar många meddelanden)"
#. / Command line option
#: src/client/application/application-client.vala:107
#: src/client/application/application-client.vala:131
msgid "Perform a graceful quit"
msgstr "Avsluta"
#: src/client/application/application-client.vala:109
#: src/client/application/application-client.vala:133
msgid "Open a new window"
msgstr "Öppna ett nytt fönster"
#. / Command line option
#: src/client/application/application-client.vala:112
#: src/client/application/application-client.vala:136
msgid "Revoke all pinned TLS server certificates"
msgstr "Återkalla alla nålade TLS-servercertifikat"
#. / Command line option
#: src/client/application/application-client.vala:115
#: src/client/application/application-client.vala:139
msgid "Display program version"
msgstr "Visa programversion"
#. / Application runtime information label
#: src/client/application/application-client.vala:243
#: src/client/application/application-client.vala:267
msgid "Geary version"
msgstr "Geary-version"
#. / Application runtime information label
#: src/client/application/application-client.vala:245
#: src/client/application/application-client.vala:269
msgid "Geary revision"
msgstr "Geary-revision"
#. / Application runtime information label
#: src/client/application/application-client.vala:247
#: src/client/application/application-client.vala:271
msgid "GTK version"
msgstr "GTK-version"
#. / Applciation runtime information label
#: src/client/application/application-client.vala:254
#: src/client/application/application-client.vala:278
msgid "GLib version"
msgstr "GLib-version"
#. / Application runtime information label
#: src/client/application/application-client.vala:261
#: src/client/application/application-client.vala:285
msgid "WebKitGTK version"
msgstr "WebKitGTK-version"
#. / Application runtime information label
#: src/client/application/application-client.vala:268
#: src/client/application/application-client.vala:292
msgid "Desktop environment"
msgstr "Skrivbordsmiljö"
#. Translators: This is the file type displayed for
#. attachments with unknown file types.
#: src/client/application/application-client.vala:270
#: src/client/application/application-client.vala:276
#: src/client/application/application-client.vala:282
#: src/client/application/application-client.vala:294
#: src/client/application/application-client.vala:300
#: src/client/application/application-client.vala:306
#: src/client/components/components-attachment-pane.vala:88
msgid "Unknown"
msgstr "Okänd"
#. / Application runtime information label
#: src/client/application/application-client.vala:274
#: src/client/application/application-client.vala:298
msgid "Distribution name"
msgstr "Distributionsnamn"
#. / Application runtime information label
#: src/client/application/application-client.vala:280
#: src/client/application/application-client.vala:304
msgid "Distribution release"
msgstr "Distributionsutgåva"
#. / Application runtime information label
#: src/client/application/application-client.vala:286
#: src/client/application/application-client.vala:310
msgid "Installation prefix"
msgstr "Installationsprefix"
#: src/client/application/application-client.vala:544
msgid "Visit the Geary web site"
msgstr "Besök Gearys webbplats"
#: src/client/application/application-client.vala:545
#: src/client/application/application-client.vala:569
#, c-format
msgid "About %s"
msgstr "Om %s"
@ -967,7 +967,7 @@ msgstr "Om %s"
#. Translators: add your name and email address to receive
#. credit in the About dialog For example: Yamada Taro
#. <yamada.taro@example.com>
#: src/client/application/application-client.vala:549
#: src/client/application/application-client.vala:573
msgid "translator-credits"
msgstr ""
"Joachim Johansson <joachim.j@gmail.com>\n"
@ -981,7 +981,7 @@ msgstr ""
#. / Command line warning, string substitution
#. / is the given argument
#: src/client/application/application-client.vala:1078
#: src/client/application/application-client.vala:1102
#, c-format
msgid "Unrecognised program argument: “%s”"
msgstr "Okänt programargument: ”%s”"
@ -1538,7 +1538,7 @@ msgstr "_Försök igen"
#. / Translators: Search entry placeholder text
#: src/client/components/components-search-bar.vala:12
#: src/client/folder-list/folder-list-search-branch.vala:53
#: src/client/util/util-i18n.vala:294
#: src/client/util/util-i18n.vala:298
msgid "Search"
msgstr "Sök"
@ -2709,43 +2709,43 @@ msgctxt "Abbreviation for kilobyte"
msgid "KB"
msgstr "KB"
#: src/client/util/util-i18n.vala:267
#: src/client/util/util-i18n.vala:271
msgid "Inbox"
msgstr "Inkorg"
#: src/client/util/util-i18n.vala:270
#: src/client/util/util-i18n.vala:274
msgid "Drafts"
msgstr "Utkast"
#: src/client/util/util-i18n.vala:273
#: src/client/util/util-i18n.vala:277
msgid "Sent"
msgstr "Skickat"
#: src/client/util/util-i18n.vala:276
#: src/client/util/util-i18n.vala:280
msgid "Starred"
msgstr "Stjärnmärkt"
#: src/client/util/util-i18n.vala:279
#: src/client/util/util-i18n.vala:283
msgid "Important"
msgstr "Viktigt"
#: src/client/util/util-i18n.vala:282
#: src/client/util/util-i18n.vala:286
msgid "All Mail"
msgstr "Alla brev"
#: src/client/util/util-i18n.vala:285
#: src/client/util/util-i18n.vala:289
msgid "Junk"
msgstr "Skräppost"
#: src/client/util/util-i18n.vala:288
#: src/client/util/util-i18n.vala:292
msgid "Trash"
msgstr "Papperskorg"
#: src/client/util/util-i18n.vala:291
#: src/client/util/util-i18n.vala:295
msgid "Outbox"
msgstr "Utkorg"
#: src/client/util/util-i18n.vala:297
#: src/client/util/util-i18n.vala:301
msgid "Archive"
msgstr "Arkiv"
@ -2807,17 +2807,17 @@ msgstr "Borttaget"
msgid "Archive | Archives"
msgstr "Arkiv | Archive | Archives"
#: src/engine/rfc822/rfc822-message.vala:575
#: src/engine/rfc822/rfc822-message.vala:572
#, c-format
msgid "Could not determine mime type for “%s”."
msgstr "Kunde inte avgöra mimetyp för ”%s”."
#: src/engine/rfc822/rfc822-message.vala:586
#: src/engine/rfc822/rfc822-message.vala:583
#, c-format
msgid "Could not determine content type for mime type “%s” on “%s”."
msgstr "Kunde inte avgöra innehållstyp för mimetypen ”%s” på ”%s”."
#: src/engine/rfc822/rfc822-message.vala:1039
#: src/engine/rfc822/rfc822-message.vala:1036
msgid "(no subject)"
msgstr "(inget ämne)"
@ -3170,16 +3170,14 @@ msgstr "Växla visning av sökrad"
#: ui/components-inspector-error-view.ui:27
msgid ""
"If the problem is serious or persists, please save and send these details to "
"one of the <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">contact channels</a> or attach to a <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">new bug report</a>."
"one of the <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">contact "
"channels</a> or attach to a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">new bug report</a>."
msgstr ""
"Om problemet är allvarligt eller kvarstår, spara och skicka dessa detaljer "
"till en av <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"till en av <a href=\"https://wiki.gnome.org/Apps/Geary/"
"Contact\">kontaktvägarna</a> eller bifoga dem till en <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">ny felrapport</a>."
"wiki.gnome.org/Apps/Geary/ReportingABug\">ny felrapport</a>."
#: ui/components-inspector-error-view.ui:42
msgid "Details:"

357
po/tr.po
View file

@ -15,63 +15,124 @@ msgid ""
msgstr ""
"Project-Id-Version: geary.mainline\n"
"Report-Msgid-Bugs-To: https://gitlab.gnome.org/GNOME/geary/issues/\n"
"POT-Creation-Date: 2025-12-15 17:23+0000\n"
"PO-Revision-Date: 2025-12-20 17:22+0100\n"
"Last-Translator: Mert Özçelik <mertozcelikksk@gmail.com>\n"
"POT-Creation-Date: 2025-06-09 19:33+0000\n"
"PO-Revision-Date: 2025-06-14 08:00+0300\n"
"Last-Translator: Emin Tufan Çetin <etcetin@gmail.com>\n"
"Language-Team: Turkish <takim@gnome.org.tr>\n"
"Language: tr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=1; plural=0;\n"
"X-Generator: Poedit 3.8\n"
"X-Generator: Poedit 3.4.3\n"
#: desktop/geary-attach.contract.desktop.in:2
#: desktop/geary-attach.contract.desktop.in:3
msgid "Send by email"
msgstr "E-posta ile gönder"
#: desktop/geary-attach.contract.desktop.in:5
#: desktop/geary-attach.contract.desktop.in:6
msgid "Send files using Geary"
msgstr "Dosyaları Geary kullanarak gönderin"
#. Translators: The application name
#: desktop/geary-autostart.desktop.in.in:2
#: desktop/org.gnome.Geary.desktop.in.in:2
#: desktop/org.gnome.Geary.metainfo.xml.in.in:11
#: desktop/geary-autostart.desktop.in.in:3
#: desktop/org.gnome.Geary.appdata.xml.in.in:11
#: desktop/org.gnome.Geary.desktop.in.in:3
#: src/client/accounts/accounts-editor-servers-pane.vala:551
#: src/client/application/application-main-window.vala:710
msgid "Geary"
msgstr "Geary"
#: desktop/geary-autostart.desktop.in.in:3
#: desktop/org.gnome.Geary.desktop.in.in:3
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
msgid "Email"
msgstr "E-posta"
#. Translators: The application's summary / tagline
#: desktop/geary-autostart.desktop.in.in:4
#: desktop/org.gnome.Geary.desktop.in.in:4
#: desktop/org.gnome.Geary.metainfo.xml.in.in:15
#: src/client/application/application-client.vala:18
#: desktop/geary-autostart.desktop.in.in:5
#: desktop/org.gnome.Geary.appdata.xml.in.in:15
#: desktop/org.gnome.Geary.desktop.in.in:5
#: src/client/application/application-client.vala:33
msgid "Send and receive email"
msgstr "E-posta gönder ve al"
#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon.
#: desktop/geary-autostart.desktop.in.in:6
#: desktop/geary-autostart.desktop.in.in:7
msgid "Email;E-mail;Mail;"
msgstr "Eposta;E-posta;Posta;E Posta;Elektronik Posta;Mektup;"
#. Translators: The development team's name
#: desktop/org.gnome.Geary.appdata.xml.in.in:13
msgid "Geary Development Team"
msgstr "Geary Geliştirme Takımı"
#: desktop/org.gnome.Geary.appdata.xml.in.in:17
msgid ""
"Geary is an email application built around conversations, for the GNOME "
"desktop. It allows you to read, find and send email with a straightforward, "
"modern interface."
msgstr ""
"Geary, GNOME masaüstü için konuşmalar etrafında inşa edilmiş bir e-posta "
"uygulamasıdır. Dolambaçsız çağdaş bir arayüzle e-posta okumanızı, bulmanızı "
"ve göndermenizi sağlar."
#: desktop/org.gnome.Geary.appdata.xml.in.in:22
msgid ""
"Conversations allow you to read a complete discussion without having to find "
"and click from message to message."
msgstr ""
"Konuşmalar, arayıp bulmanızı ve iletiden iletiye tıklamanızı gerektirmeden "
"tüm tartışmayı okumanızı sağlar."
#: desktop/org.gnome.Geary.appdata.xml.in.in:26
msgid "Gearys features include:"
msgstr "Gearynin özellikleri şunlardır:"
#: desktop/org.gnome.Geary.appdata.xml.in.in:28
msgid "Quick email account setup"
msgstr "Hızlı e-posta hesabı kurulumu"
#: desktop/org.gnome.Geary.appdata.xml.in.in:29
msgid "Shows related messages together in conversations"
msgstr "İlişkili iletileri konuşmalarda bir arada gösterir"
#: desktop/org.gnome.Geary.appdata.xml.in.in:30
msgid "Fast, full text and keyword search"
msgstr "Hızlı, tam metin ve anahtar sözcük arama"
#: desktop/org.gnome.Geary.appdata.xml.in.in:31
msgid "Full-featured HTML and plain text message composer"
msgstr "Tam donanımlı HTML ve düz metin ileti oluşturucu"
#: desktop/org.gnome.Geary.appdata.xml.in.in:32
msgid "Desktop notification of new mail"
msgstr "Yeni postanın masaüstü bildirimi"
#: desktop/org.gnome.Geary.appdata.xml.in.in:33
msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr "GMail, Yahoo! Mail, Outlook.com ve diğer IMAP sunucularıyla uyumludur"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.appdata.xml.in.in:51
msgid "Geary displaying a conversation"
msgstr "Geary bir konuşmayı gösteriyor"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.appdata.xml.in.in:56
msgid "Geary showing the rich text composer"
msgstr "Geary zengin metin oluşturucusunu gösteriyor"
#. Translators: These are desktop search terms. Do not translate semicolons, end line with a semicolon.
#: desktop/org.gnome.Geary.desktop.in.in:6
#: desktop/org.gnome.Geary.desktop.in.in:7
msgid "Mail;E-mail;email;IMAP;GMail;Yahoo;Hotmail;Outlook;"
msgstr ""
"Posta;E-posta;eposta;Mail;E-mail;email;IMAP;GMail;Yahoo;Hotmail;Outlook;"
#: desktop/org.gnome.Geary.desktop.in.in:23
#: desktop/org.gnome.Geary.desktop.in.in:24
msgid "Compose Message"
msgstr "İleti Oluştur"
#: desktop/org.gnome.Geary.desktop.in.in:27
#: desktop/org.gnome.Geary.desktop.in.in:28
msgid "New Window"
msgstr "Yeni Pencere"
@ -124,10 +185,14 @@ msgid "True if we should display a short preview of each message."
msgstr "Her iletinin kısa bir ön izlemesini göstermemiz gerekiyorsa doğru."
#: desktop/org.gnome.Geary.gschema.xml:44
msgid "Unset colors provided in HTML emails"
msgstr "HTML e-postalarda sağlanan renkleri kaldır"
#: desktop/org.gnome.Geary.gschema.xml:45
msgid "Override colors in HTML emails"
msgstr "HTML e-postalardaki renkleri çiğne"
#: desktop/org.gnome.Geary.gschema.xml:45
#: desktop/org.gnome.Geary.gschema.xml:46
msgid ""
"Overrides the original colors in HTML messages to integrate better with the "
"app theme."
@ -135,19 +200,19 @@ msgstr ""
"Uygulama temasıyla daha iyi tümleşim için HTML iletilerindeki özgün renkleri "
"çiğner."
#: desktop/org.gnome.Geary.gschema.xml:50
#: desktop/org.gnome.Geary.gschema.xml:51
msgid "Move messages by default"
msgstr "Öntanımlı olarak iletileri taşı"
#: desktop/org.gnome.Geary.gschema.xml:51
#: desktop/org.gnome.Geary.gschema.xml:52
msgid "When tagging a message, move it to destination folder."
msgstr "İleti etiketlendiğinde, onu hedef klasöre taşı."
#: desktop/org.gnome.Geary.gschema.xml:56
#: desktop/org.gnome.Geary.gschema.xml:57
msgid "Use single key shortcuts"
msgstr "Tek tuşlu kısayolları kullan"
#: desktop/org.gnome.Geary.gschema.xml:57
#: desktop/org.gnome.Geary.gschema.xml:58
msgid ""
"Enables shortcuts for email actions that do not require pressing <Ctrl> to "
"emulate those used by Gmail."
@ -155,11 +220,11 @@ msgstr ""
"Gmailʼin kullandığına benzemek için e-posta eylemlerinde <Ctrl>ʼye basmayı "
"gerektirmeyen kısayolları etkinleştirir."
#: desktop/org.gnome.Geary.gschema.xml:64
#: desktop/org.gnome.Geary.gschema.xml:65
msgid "Languages that shall be used in the spell checker"
msgstr "Yazım denetleyicide kullanılacak diller"
#: desktop/org.gnome.Geary.gschema.xml:65
#: desktop/org.gnome.Geary.gschema.xml:66
msgid ""
"A list of POSIX locales, with the empty list disabling spell checking and "
"the null list using desktop languages by default."
@ -167,11 +232,11 @@ msgstr ""
"POSIX yerellerinin listesi, boş listeyle imla denetimi devre dışı bırakılır "
"ve butlan (null) listeyle öntanımlı olarak masaüstü dillerini kullanılır."
#: desktop/org.gnome.Geary.gschema.xml:72
#: desktop/org.gnome.Geary.gschema.xml:73
msgid "Languages that are displayed in the spell checker popover"
msgstr "Yazım denetleyici açılır penceresinde gösterilecek diller"
#: desktop/org.gnome.Geary.gschema.xml:73
#: desktop/org.gnome.Geary.gschema.xml:74
msgid ""
"List of languages that are always displayed in the popover of the spell "
"checker."
@ -179,70 +244,70 @@ msgstr ""
"Yazım denetleyicinin açılır penceresinde her zaman gösterilecek dillerin "
"listesi."
#: desktop/org.gnome.Geary.gschema.xml:78
#: desktop/org.gnome.Geary.gschema.xml:79
msgid "Run application in background on logon and when closed"
msgstr "Kapatıldığında ve oturum açıldığında uygulamayı arka planda çalıştır"
#: desktop/org.gnome.Geary.gschema.xml:79
#: desktop/org.gnome.Geary.gschema.xml:80
msgid "True to run application in background."
msgstr "Uygulamayı arka planda çalıştırmak için doğru."
#: desktop/org.gnome.Geary.gschema.xml:84
#: desktop/org.gnome.Geary.gschema.xml:85
msgid "Ask when opening an attachment"
msgstr "Ek açarken sor"
#: desktop/org.gnome.Geary.gschema.xml:85
#: desktop/org.gnome.Geary.gschema.xml:86
msgid "True to ask when opening an attachment."
msgstr "Eki açarken sormak için doğru."
#: desktop/org.gnome.Geary.gschema.xml:90
#: desktop/org.gnome.Geary.gschema.xml:91
msgid "Whether to compose emails in HTML"
msgstr "E-postaların HTMLde oluşturulup oluşturulmayacağı"
#: desktop/org.gnome.Geary.gschema.xml:91
#: desktop/org.gnome.Geary.gschema.xml:92
msgid "True to compose emails in HTML; false for plain text."
msgstr "E-postaları HTMLde oluşturmak için doğru, düz metin için yanlış."
#: desktop/org.gnome.Geary.gschema.xml:96
#: desktop/org.gnome.Geary.gschema.xml:97
msgid "Advisory strategy for full-text searching"
msgstr "Tam metin arama için tavsiye niteliğinde izlem"
#: desktop/org.gnome.Geary.gschema.xml:97
#: desktop/org.gnome.Geary.gschema.xml:98
msgid ""
"Acceptable values are “exact”, “conservative”, “aggressive”, and “horizon”."
msgstr ""
"Kabul edilebilir değerler şunlardır: “exact” (birebir), “conservative” "
"(ılımlı), “aggressive” (sert) ve “horizon”."
#: desktop/org.gnome.Geary.gschema.xml:102
#: desktop/org.gnome.Geary.gschema.xml:103
msgid "Zoom of conversation viewer"
msgstr "Konuşma göstericisinin yakınlaşması"
#: desktop/org.gnome.Geary.gschema.xml:103
#: desktop/org.gnome.Geary.gschema.xml:104
msgid "The zoom to apply on the conservation view."
msgstr "Konuşma görünümünde uygulanacak yakınlaşma."
#: desktop/org.gnome.Geary.gschema.xml:108
#: desktop/org.gnome.Geary.gschema.xml:109
msgid "Size of detached composer window"
msgstr "Ayrılan oluşturucu penceresinin boyutu"
#: desktop/org.gnome.Geary.gschema.xml:109
#: desktop/org.gnome.Geary.gschema.xml:110
msgid "The last recorded size of the detached composer window."
msgstr "Ayrılmış oluşturucu penceresinin kaydedilen son boyutu."
#: desktop/org.gnome.Geary.gschema.xml:114
#: desktop/org.gnome.Geary.gschema.xml:115
msgid "Allow images for these domains"
msgstr "Bu alan adları için görüntülere izin ver"
#: desktop/org.gnome.Geary.gschema.xml:115
#: desktop/org.gnome.Geary.gschema.xml:116
msgid "Images from these domains will be trusted"
msgstr "Bu alan adlarından görüntülere güvenilecek"
#: desktop/org.gnome.Geary.gschema.xml:120
#: desktop/org.gnome.Geary.gschema.xml:121
msgid "Undo sending email delay"
msgstr "E-posta göndermeyi geri alma gecikmesi"
#: desktop/org.gnome.Geary.gschema.xml:121
#: desktop/org.gnome.Geary.gschema.xml:122
msgid ""
"The number of seconds to wait before sending an email. Set to zero or less "
"to disable."
@ -250,96 +315,35 @@ msgstr ""
"E-posta gönderilmeden önce beklenecek saniye. Devre dışı bırakmak için sıfır "
"veya daha azına belirleyin."
#: desktop/org.gnome.Geary.gschema.xml:127
#: desktop/org.gnome.Geary.gschema.xml:128
msgid "Brief notification display time"
msgstr "Özet bildirim gösterim zamanı"
#: desktop/org.gnome.Geary.gschema.xml:128
#: desktop/org.gnome.Geary.gschema.xml:129
msgid ""
"The length of time in seconds for which brief notifications should be "
"displayed."
msgstr "Özet bildirimlerin gösterileceği zamanın saniye türünde uzunluğu."
#: desktop/org.gnome.Geary.gschema.xml:134
#: desktop/org.gnome.Geary.gschema.xml:135
msgid "List of optional plugins"
msgstr "İsteğe bağlı eklenti listesi"
#: desktop/org.gnome.Geary.gschema.xml:135
#: desktop/org.gnome.Geary.gschema.xml:136
msgid "Plugins listed here will be loaded on startup."
msgstr "Burada listelenen eklentiler başlangıçta yüklenecek."
#: desktop/org.gnome.Geary.gschema.xml:140
#: desktop/org.gnome.Geary.gschema.xml:141
msgid "Whether we migrated the old settings"
msgstr "Eski ayarları taşıyıp taşımayacağımız"
#: desktop/org.gnome.Geary.gschema.xml:141
#: desktop/org.gnome.Geary.gschema.xml:142
msgid ""
"False to check for the old “org.yorba.geary”-schema and copy its values."
msgstr ""
"Eski “org.yorba.geary”-şemasını denetlemek ve değerlerini kopyalamak için "
"yanlış."
#. Translators: The development team's name
#: desktop/org.gnome.Geary.metainfo.xml.in.in:13
msgid "Geary Development Team"
msgstr "Geary Geliştirme Takımı"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:17
msgid ""
"Geary is an email application built around conversations, for the GNOME "
"desktop. It allows you to read, find and send email with a straightforward, "
"modern interface."
msgstr ""
"Geary, GNOME masaüstü için konuşmalar etrafında inşa edilmiş bir e-posta "
"uygulamasıdır. Dolambaçsız çağdaş bir arayüzle e-posta okumanızı, bulmanızı "
"ve göndermenizi sağlar."
#: desktop/org.gnome.Geary.metainfo.xml.in.in:22
msgid ""
"Conversations allow you to read a complete discussion without having to find "
"and click from message to message."
msgstr ""
"Konuşmalar, arayıp bulmanızı ve iletiden iletiye tıklamanızı gerektirmeden "
"tüm tartışmayı okumanızı sağlar."
#: desktop/org.gnome.Geary.metainfo.xml.in.in:26
msgid "Gearys features include:"
msgstr "Gearynin özellikleri şunlardır:"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:28
msgid "Quick email account setup"
msgstr "Hızlı e-posta hesabı kurulumu"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:29
msgid "Shows related messages together in conversations"
msgstr "İlişkili iletileri konuşmalarda bir arada gösterir"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:30
msgid "Fast, full text and keyword search"
msgstr "Hızlı, tam metin ve anahtar sözcük arama"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:31
msgid "Full-featured HTML and plain text message composer"
msgstr "Tam donanımlı HTML ve düz metin ileti oluşturucu"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:32
msgid "Desktop notification of new mail"
msgstr "Yeni postanın masaüstü bildirimi"
#: desktop/org.gnome.Geary.metainfo.xml.in.in:33
msgid "Compatible with GMail, Yahoo! Mail, Outlook.com and other IMAP servers"
msgstr "GMail, Yahoo! Mail, Outlook.com ve diğer IMAP sunucularıyla uyumludur"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:50
msgid "Geary displaying a conversation"
msgstr "Geary bir konuşmayı gösteriyor"
#. Translators: A screenshot description.
#: desktop/org.gnome.Geary.metainfo.xml.in.in:55
msgid "Geary showing the rich text composer"
msgstr "Geary zengin metin oluşturucusunu gösteriyor"
#. Translators: In-app notification label, when
#. the app had a problem pinning an otherwise
#. untrusted TLS certificate
@ -823,140 +827,140 @@ msgstr ""
msgid "_Replace"
msgstr "_Değiştir"
#: src/client/application/application-client.vala:19
#: src/client/application/application-client.vala:34
msgid "Copyright © 2016 Software Freedom Conservancy Inc."
msgstr "Telif Hakkı © 2016 Software Freedom Conservancy Inc."
#: src/client/application/application-client.vala:20
#: src/client/application/application-client.vala:35
msgid "Copyright © 2016-2021 Geary Development Team."
msgstr "Telif Hakkı © 2016-2021 Geary Geliştirme Takımı."
#: src/client/application/application-client.vala:37
msgid "Visit the Geary web site"
msgstr "Geary web sitesini ziyaret et"
#. / Command line option
#: src/client/application/application-client.vala:77
#: src/client/application/application-client.vala:101
msgid "Print debug logging"
msgstr "Hata ayıklama günlüğünü yazdır"
#. / Command line option
#: src/client/application/application-client.vala:80
#: src/client/application/application-client.vala:104
msgid "Enable WebKitGTK Inspector in web views"
msgstr "Web görünümünde WebKitGTK İnceleyicisiʼni etkinleştir"
#. / Command line option
#: src/client/application/application-client.vala:83
#: src/client/application/application-client.vala:107
msgid "Log conversation monitoring"
msgstr "Konuşma gözetimini kayda al"
#. / Command line option
#: src/client/application/application-client.vala:86
#: src/client/application/application-client.vala:110
msgid "Log IMAP network deserialization"
msgstr "IMAP ağ serisizleştirmeyi kayda al"
#. / Command line option. "Normalization" can also be called
#. / "synchronization".
#: src/client/application/application-client.vala:90
#: src/client/application/application-client.vala:114
msgid "Log folder normalization"
msgstr "Klasör düzgelemeyi kayda al"
#. / Command line option
#: src/client/application/application-client.vala:93
#: src/client/application/application-client.vala:117
msgid "Log IMAP network activity"
msgstr "IMAP ağ etkinliğini günlükle"
#. / Command line option. The IMAP replay queue is how changes
#. / on the server are replicated on the client. It could
#. / also be called the IMAP events queue.
#: src/client/application/application-client.vala:98
#: src/client/application/application-client.vala:122
msgid "Log IMAP replay queue"
msgstr "IMAP tekrar sırasını kayda al"
#. / Command line option
#: src/client/application/application-client.vala:101
#: src/client/application/application-client.vala:125
msgid "Log SMTP network activity"
msgstr "SMTP ağ etkinliğini günlükle"
#. / Command line option
#: src/client/application/application-client.vala:104
#: src/client/application/application-client.vala:128
msgid "Log database queries (generates lots of messages)"
msgstr "Veri tabanı sorgularını kayda al (birçok ileti oluşturur)"
#. / Command line option
#: src/client/application/application-client.vala:107
#: src/client/application/application-client.vala:131
msgid "Perform a graceful quit"
msgstr "Hoş bir çıkış gerçekleştir"
#: src/client/application/application-client.vala:109
#: src/client/application/application-client.vala:133
msgid "Open a new window"
msgstr "Yeni pencere aç"
#. / Command line option
#: src/client/application/application-client.vala:112
#: src/client/application/application-client.vala:136
msgid "Revoke all pinned TLS server certificates"
msgstr "Tüm imlenmiş TLS sunucu sertifikalarını geçersizleştir"
#. / Command line option
#: src/client/application/application-client.vala:115
#: src/client/application/application-client.vala:139
msgid "Display program version"
msgstr "Uygulama sürümünü göster"
#. / Application runtime information label
#: src/client/application/application-client.vala:243
#: src/client/application/application-client.vala:267
msgid "Geary version"
msgstr "Geary sürümü"
#. / Application runtime information label
#: src/client/application/application-client.vala:245
#: src/client/application/application-client.vala:269
msgid "Geary revision"
msgstr "Geary düzeltisi"
#. / Application runtime information label
#: src/client/application/application-client.vala:247
#: src/client/application/application-client.vala:271
msgid "GTK version"
msgstr "GTK sürümü"
#. / Applciation runtime information label
#: src/client/application/application-client.vala:254
#: src/client/application/application-client.vala:278
msgid "GLib version"
msgstr "GLib sürümü"
#. / Application runtime information label
#: src/client/application/application-client.vala:261
#: src/client/application/application-client.vala:285
msgid "WebKitGTK version"
msgstr "WebKitGTK sürümü"
#. / Application runtime information label
#: src/client/application/application-client.vala:268
#: src/client/application/application-client.vala:292
msgid "Desktop environment"
msgstr "Masaüstü ortamı"
#. Translators: This is the file type displayed for
#. attachments with unknown file types.
#: src/client/application/application-client.vala:270
#: src/client/application/application-client.vala:276
#: src/client/application/application-client.vala:282
#: src/client/application/application-client.vala:294
#: src/client/application/application-client.vala:300
#: src/client/application/application-client.vala:306
#: src/client/components/components-attachment-pane.vala:88
msgid "Unknown"
msgstr "Bilinmiyor"
#. / Application runtime information label
#: src/client/application/application-client.vala:274
#: src/client/application/application-client.vala:298
msgid "Distribution name"
msgstr "Dağıtım adı"
#. / Application runtime information label
#: src/client/application/application-client.vala:280
#: src/client/application/application-client.vala:304
msgid "Distribution release"
msgstr "Dağıtım sürümü"
#. / Application runtime information label
#: src/client/application/application-client.vala:286
#: src/client/application/application-client.vala:310
msgid "Installation prefix"
msgstr "Kurulum ön eki"
#: src/client/application/application-client.vala:544
msgid "Visit the Geary web site"
msgstr "Geary web sitesini ziyaret et"
#: src/client/application/application-client.vala:545
#: src/client/application/application-client.vala:569
#, c-format
msgid "About %s"
msgstr "%s hakkında"
@ -964,13 +968,14 @@ msgstr "%s hakkında"
#. Translators: add your name and email address to receive
#. credit in the About dialog For example: Yamada Taro
#. <yamada.taro@example.com>
#: src/client/application/application-client.vala:549
#: src/client/application/application-client.vala:573
msgid "translator-credits"
msgstr "Emin Tufan Çetin <etcetin@gmail.com>"
msgstr ""
"Emin Tufan Çetin <etcetin@gmail.com>"
#. / Command line warning, string substitution
#. / is the given argument
#: src/client/application/application-client.vala:1078
#: src/client/application/application-client.vala:1102
#, c-format
msgid "Unrecognised program argument: “%s”"
msgstr "Tanınmayan program argümanı: “%s”"
@ -1508,7 +1513,7 @@ msgstr "_Yeniden dene"
#. / Translators: Search entry placeholder text
#: src/client/components/components-search-bar.vala:12
#: src/client/folder-list/folder-list-search-branch.vala:53
#: src/client/util/util-i18n.vala:294
#: src/client/util/util-i18n.vala:298
msgid "Search"
msgstr "Ara"
@ -2165,11 +2170,11 @@ msgid "New message"
msgid_plural "New messages"
msgstr[0] "Yeni ileti"
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:3
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:4
msgid "Email Templates"
msgstr "E-Posta Şablonları"
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:4
#: src/client/plugin/email-templates/email-templates.plugin.desktop.in:5
msgid "Create reusable templates for sending email"
msgstr "E-posta gönderimi için yeniden kullanılabilir şablonlar oluştur"
@ -2221,14 +2226,14 @@ msgstr "Düzenle"
#. / merge in composer
#. Translators: The name of the folder used to
#. display merged email
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:4
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:5
#: src/client/plugin/mail-merge/mail-merge.vala:288
#: src/client/plugin/mail-merge/mail-merge.vala:395
#: src/client/plugin/mail-merge/mail-merge.vala:494
msgid "Mail Merge"
msgstr "Adres Mektup Birleştir"
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:5
#: src/client/plugin/mail-merge/mail-merge.plugin.desktop.in:6
msgid "Fill in and send email templates using a spreadsheet"
msgstr "Hesap çizelgesi kullanarak e-posta şablonlarını doldur ve gönder"
@ -2276,11 +2281,11 @@ msgstr "Alan ekle"
msgid "Comma separated values (CSV)"
msgstr "Virgülle ayrılmış değerler (CSV)"
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:3
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:4
msgid "Messaging Menu"
msgstr "İletişim Menüsü"
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:4
#: src/client/plugin/messaging-menu/messaging-menu.plugin.desktop.in:5
msgid "Displays Unity Messaging Menu notifications for new email"
msgstr "Yeni posta için Unity İletişim Menüsü bildirimlerini gösterir"
@ -2289,11 +2294,11 @@ msgstr "Yeni posta için Unity İletişim Menüsü bildirimlerini gösterir"
msgid "%s — New Messages"
msgstr "%s — Yeni İleti"
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:3
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:4
msgid "Sent Sound"
msgstr "Gönderilme Sesi"
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:4
#: src/client/plugin/sent-sound/sent-sound.plugin.desktop.in:5
msgid "Plays the desktop sent-mail sound when an email is sent"
msgstr "E-posta gönderildiğinde masaüstü gönderilme sesi çalınır"
@ -2354,7 +2359,7 @@ msgid "%b %-e"
msgstr "%-e %b"
# https://docs.gtk.org/glib/method.DateTime.format.html
# Örnek:
# Örnek:
# 1 Haziran 2024, 8:30 öö
# 30 Haziran 2024, 8:30 ös
#. / Verbose datetime format for 12-hour time, i.e. November 8, 2010 8:42 am
@ -2674,43 +2679,43 @@ msgctxt "Abbreviation for kilobyte"
msgid "KB"
msgstr "KB"
#: src/client/util/util-i18n.vala:267
#: src/client/util/util-i18n.vala:271
msgid "Inbox"
msgstr "Gelen Kutusu"
#: src/client/util/util-i18n.vala:270
#: src/client/util/util-i18n.vala:274
msgid "Drafts"
msgstr "Taslaklar"
#: src/client/util/util-i18n.vala:273
#: src/client/util/util-i18n.vala:277
msgid "Sent"
msgstr "Gönderilmiş"
#: src/client/util/util-i18n.vala:276
#: src/client/util/util-i18n.vala:280
msgid "Starred"
msgstr "Yıldızlı"
#: src/client/util/util-i18n.vala:279
#: src/client/util/util-i18n.vala:283
msgid "Important"
msgstr "Önemli"
#: src/client/util/util-i18n.vala:282
#: src/client/util/util-i18n.vala:286
msgid "All Mail"
msgstr "Tüm Postalar"
#: src/client/util/util-i18n.vala:285
#: src/client/util/util-i18n.vala:289
msgid "Junk"
msgstr "Gereksiz"
#: src/client/util/util-i18n.vala:288
#: src/client/util/util-i18n.vala:292
msgid "Trash"
msgstr "Çöp"
#: src/client/util/util-i18n.vala:291
#: src/client/util/util-i18n.vala:295
msgid "Outbox"
msgstr "Giden Kutusu"
#: src/client/util/util-i18n.vala:297
#: src/client/util/util-i18n.vala:301
msgid "Archive"
msgstr "Arşiv"
@ -2773,17 +2778,17 @@ msgstr "Silinmiş Öğeler"
msgid "Archive | Archives"
msgstr "Arşiv | Arşivler"
#: src/engine/rfc822/rfc822-message.vala:575
#: src/engine/rfc822/rfc822-message.vala:572
#, c-format
msgid "Could not determine mime type for “%s”."
msgstr "“%s” için mime türü saptanamadı."
#: src/engine/rfc822/rfc822-message.vala:586
#: src/engine/rfc822/rfc822-message.vala:583
#, c-format
msgid "Could not determine content type for mime type “%s” on “%s”."
msgstr "“%2$s” üstündeki “%1$s” mime türü için içerik türü saptanamadı."
#: src/engine/rfc822/rfc822-message.vala:1039
#: src/engine/rfc822/rfc822-message.vala:1036
msgid "(no subject)"
msgstr "(konu yok)"
@ -3136,15 +3141,14 @@ msgstr "Bulma çubuğunu aç"
#: ui/components-inspector-error-view.ui:27
msgid ""
"If the problem is serious or persists, please save and send these details to "
"one of the <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Contact\">contact channels</a> or attach to a <a href=\"https://"
"gitlab.gnome.org/GNOME/geary/-/wikis/Reporting-Bugs-and-Requesting-"
"Features\">new bug report</a>."
"one of the <a href=\"https://wiki.gnome.org/Apps/Geary/Contact\">contact "
"channels</a> or attach to a <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">new bug report</a>."
msgstr ""
"Sorun ciddi veya sürekliyse, lütfen bu ayrıntıları kaydedip ve kopyalayıp <a "
"href=\"https://wiki.gnome.org/Apps/Geary/Contact\">iletişim kanallarına</a> "
"gönderin veya <a href=\"https://gitlab.gnome.org/GNOME/geary/-/wikis/"
"Reporting-Bugs-and-Requesting-Features\">yeni hata bildirimi</a>nde bulunun."
"gönderin veya <a href=\"https://wiki.gnome.org/Apps/Geary/"
"ReportingABug\">yeni hata bildirimi</a>nde bulunun."
#: ui/components-inspector-error-view.ui:42
msgid "Details:"
@ -3722,6 +3726,3 @@ msgstr "Parolayı _anımsa"
#: ui/password-dialog.glade:195
msgid "_Authenticate"
msgstr "_Kimlik Doğrula"
#~ msgid "Unset colors provided in HTML emails"
#~ msgstr "HTML e-postalarda sağlanan renkleri kaldır"

View file

@ -9,25 +9,21 @@
* An account editor pane for adding a new account.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_add_pane.ui")]
internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
internal class Accounts.EditorAddPane : Accounts.EditorPane {
internal Gtk.Widget initial_widget {
get { return this.real_name.value; }
}
/** {@inheritDoc} */
internal bool is_operation_running {
internal override bool is_operation_running {
get { return !this.sensitive; }
protected set { update_operation_ui(value); }
}
/** {@inheritDoc} */
internal GLib.Cancellable? op_cancellable {
internal override Cancellable? op_cancellable {
get; protected set; default = new GLib.Cancellable();
}
protected weak Accounts.Editor editor { get; set; }
protected override weak Accounts.Editor editor { get; set; }
private Geary.ServiceProvider provider;
@ -35,98 +31,55 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
private Geary.Engine engine;
[GtkChild] private unowned Gtk.HeaderBar header;
[GtkChild] private unowned Adw.HeaderBar header;
[GtkChild] private unowned Gtk.Stack stack;
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
[GtkChild] private unowned Adw.PreferencesGroup details_list;
[GtkChild] private unowned Adw.EntryRow name_row;
[GtkChild] private unowned Adw.EntryRow email_row;
[GtkChild] private unowned Components.Validator email_validator;
[GtkChild] private unowned Gtk.ListBox details_list;
[GtkChild] private unowned Gtk.ListBox receiving_list;
[GtkChild] private unowned Gtk.ListBox sending_list;
[GtkChild] private unowned ServiceInformationWidget receiving_service_widget;
[GtkChild] private unowned ServiceInformationWidget sending_service_widget;
[GtkChild] private unowned Gtk.Button action_button;
[GtkChild] private unowned Adw.Spinner action_spinner;
[GtkChild] private unowned Gtk.Button back_button;
[GtkChild] private unowned Gtk.Spinner action_spinner;
private NameRow real_name;
private EmailRow email = new EmailRow();
private string last_valid_email = "";
private string last_valid_hostname = "";
//XXX if this is set, we shuld hide the IMAP/SMTP hostnames/auth
private bool did_auto_config { get; private set; default = false; }
private GLib.Cancellable auto_config_cancellable = new GLib.Cancellable();
private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
private TransportSecurityRow imap_tls = new TransportSecurityRow();
private LoginRow imap_login = new LoginRow();
private PasswordRow imap_password = new PasswordRow();
private HostnameRow smtp_hostname = new HostnameRow(Geary.Protocol.SMTP);
private TransportSecurityRow smtp_tls = new TransportSecurityRow();
private OutgoingAuthRow smtp_auth = new OutgoingAuthRow();
private LoginRow smtp_login = new LoginRow();
private PasswordRow smtp_password = new PasswordRow();
private bool controls_valid = false;
public Components.ValidatorGroup validators { get; construct set; }
static construct {
typeof(Components.ValidatorGroup).ensure();
typeof(Components.Validator).ensure();
typeof(Components.EmailValidator).ensure();
}
internal EditorAddPane(Editor editor) {
this.editor = editor;
Object(editor: editor);
this.provider = Geary.ServiceProvider.OTHER;
this.accounts = editor.application.controller.account_manager;
this.engine = editor.application.engine;
this.stack.set_focus_vadjustment(this.pane_adjustment);
this.name_row.text = this.accounts.get_account_name();
//XXX GTK4 make sure it's validated immediately
this.details_list.set_header_func(Editor.seperator_headers);
this.receiving_list.set_header_func(Editor.seperator_headers);
this.sending_list.set_header_func(Editor.seperator_headers);
this.receiving_service_widget.service = new_imap_service();
this.sending_service_widget.service = new_smtp_service();
this.real_name = new NameRow(this.accounts.get_account_name());
this.details_list.add(this.real_name);
this.details_list.add(this.email);
this.real_name.validator.state_changed.connect(on_validated);
this.real_name.value.activate.connect(on_activated);
this.email.validator.state_changed.connect(on_validated);
this.email.value.activate.connect(on_activated);
this.email.value.changed.connect(on_email_changed);
this.imap_hostname.validator.state_changed.connect(on_validated);
this.imap_hostname.value.activate.connect(on_activated);
this.imap_tls.hide();
this.imap_login.validator.state_changed.connect(on_validated);
this.imap_login.value.activate.connect(on_activated);
this.imap_password.validator.state_changed.connect(on_validated);
this.imap_password.value.activate.connect(on_activated);
this.smtp_hostname.validator.state_changed.connect(on_validated);
this.smtp_hostname.value.activate.connect(on_activated);
this.smtp_tls.hide();
this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
this.smtp_login.validator.state_changed.connect(on_validated);
this.smtp_login.value.activate.connect(on_activated);
this.smtp_password.validator.state_changed.connect(on_validated);
this.smtp_password.value.activate.connect(on_activated);
this.receiving_list.add(this.imap_hostname);
this.receiving_list.add(this.imap_tls);
this.receiving_list.add(this.imap_login);
this.receiving_list.add(this.imap_password);
this.sending_list.add(this.smtp_hostname);
this.sending_list.add(this.smtp_tls);
this.sending_list.add(this.smtp_auth);
}
internal Gtk.HeaderBar get_header() {
return this.header;
// XXX we need to make sure the validators for the service are wired up too
// this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
}
private async void validate_account(GLib.Cancellable? cancellable) {
@ -140,18 +93,18 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
yield this.accounts.new_orphan_account(
this.provider,
new Geary.RFC822.MailboxAddress(
this.real_name.value.text.strip(),
this.email.value.text.strip()
this.name_row.text.strip(),
this.email_row.text.strip()
),
cancellable
);
account.incoming = new_imap_service();
account.outgoing = new_smtp_service();
account.incoming = this.receiving_service_widget.service_mutable;
account.outgoing = this.sending_service_widget.service_mutable;
account.untrusted_host.connect(on_untrusted_host);
if (this.provider == Geary.ServiceProvider.OTHER &&
this.imap_hostname.get_visible()) {
!this.did_auto_config) {
bool imap_valid = false;
bool smtp_valid = false;
@ -162,7 +115,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
imap_valid = true;
} catch (Geary.ImapError.UNAUTHENTICATED err) {
debug("Error authenticating IMAP service: %s", err.message);
to_focus = this.imap_login.value;
to_focus = this.receiving_service_widget;
// Translators: In-app notification label
message = _("Check your receiving login and password");
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
@ -176,8 +129,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
Geary.ErrorContext context = new Geary.ErrorContext(err);
debug("Error validating IMAP service: %s",
context.format_full_error());
this.imap_tls.show();
to_focus = this.imap_hostname.value;
//XXX GTK4 not sure how to design a nice API for this
// this.imap_tls.show();
to_focus = this.receiving_service_widget;
// Translators: In-app notification label
message = _("Check your receiving server details");
}
@ -197,9 +151,9 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
// There was an SMTP auth error, but IMAP already
// succeeded, so the user probably needs to
// specify custom creds here
this.smtp_auth.value.source =
this.receiving_service_widget.service.credentials_requirement =
Geary.Credentials.Requirement.CUSTOM;
to_focus = this.smtp_login.value;
to_focus = this.receiving_service_widget;
// Translators: In-app notification label
message = _("Check your sending login and password");
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
@ -212,8 +166,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
Geary.ErrorContext context = new Geary.ErrorContext(err);
debug("Error validating SMTP service: %s",
context.format_full_error());
this.smtp_tls.show();
to_focus = this.smtp_hostname.value;
to_focus = this.sending_service_widget;
// Translators: In-app notification label
message = _("Check your sending server details");
}
@ -228,7 +181,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
is_valid = true;
} catch (Geary.ImapError.UNAUTHENTICATED err) {
debug("Error authenticating provider: %s", err.message);
to_focus = this.email.value;
to_focus = this.email_row;
// Translators: In-app notification label
message = _("Check your email address and password");
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
@ -248,7 +201,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
if (is_valid) {
try {
yield this.accounts.create_account(account, cancellable);
this.editor.pop();
this.editor.pop_pane();
} catch (GLib.Error err) {
debug("Failed to create new local account: %s", err.message);
is_valid = false;
@ -268,8 +221,8 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
to_focus.grab_focus();
}
if (message != null) {
this.editor.add_notification(
new Components.InAppNotification(
this.editor.add_toast(
new Adw.Toast(
// Translators: In-app notification label, the
// string substitution is a more detailed reason.
_("Account not created: %s").printf(message)
@ -280,63 +233,23 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
}
private Geary.ServiceInformation new_imap_service() {
Geary.ServiceInformation service = new Geary.ServiceInformation(
var service = new Geary.ServiceInformation(
Geary.Protocol.IMAP, this.provider
);
service.credentials = new Geary.Credentials(
Geary.Credentials.Method.PASSWORD,
this.imap_login.value.get_text().strip(),
this.imap_password.value.get_text().strip()
Geary.Credentials.Method.PASSWORD, ""
);
Components.NetworkAddressValidator host =
(Components.NetworkAddressValidator)
this.imap_hostname.validator;
GLib.NetworkAddress address = host.validated_address;
service.host = address.hostname;
service.port = (uint16) address.port;
service.transport_security = this.imap_tls.value.method;
if (service.port == 0) {
service.port = service.get_default_port();
}
return service;
}
private Geary.ServiceInformation new_smtp_service() {
Geary.ServiceInformation service = new Geary.ServiceInformation(
return new Geary.ServiceInformation(
Geary.Protocol.SMTP, this.provider
);
service.credentials_requirement = this.smtp_auth.value.source;
if (service.credentials_requirement ==
Geary.Credentials.Requirement.CUSTOM) {
service.credentials = new Geary.Credentials(
Geary.Credentials.Method.PASSWORD,
this.smtp_login.value.get_text().strip(),
this.smtp_password.value.get_text().strip()
);
}
Components.NetworkAddressValidator host =
(Components.NetworkAddressValidator)
this.smtp_hostname.validator;
GLib.NetworkAddress address = host.validated_address;
service.host = address.hostname;
service.port = (uint16) address.port;
service.transport_security = this.smtp_tls.value.method;
if (service.port == 0) {
service.port = service.get_default_port();
}
return service;
}
private void check_validation() {
#if 0
bool server_settings_visible = this.stack.get_visible_child_name() == "server_settings";
bool controls_valid = true;
Gtk.ListBox[] list_boxes;
@ -348,22 +261,23 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
list_boxes = new Gtk.ListBox[] { this.details_list };
}
foreach (Gtk.ListBox list_box in list_boxes) {
list_box.foreach((child) => {
AddPaneRow? validatable = child as AddPaneRow;
if (validatable != null && !validatable.validator.is_valid) {
controls_valid = false;
}
});
for (int i = 0; true; i++) {
unowned var validatable = list_box.get_row_at_index(i) as AddPaneRow;
if (validatable == null)
break;
if (!validatable.validator.is_valid) {
controls_valid = false;
}
}
}
this.action_button.set_sensitive(controls_valid);
this.controls_valid = controls_valid;
#endif
}
private void update_operation_ui(bool is_running) {
this.action_spinner.visible = is_running;
this.action_spinner.active = is_running;
this.action_button.sensitive = !is_running;
this.back_button.sensitive = !is_running;
this.sensitive = !is_running;
}
@ -371,36 +285,37 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
this.stack.set_visible_child_name("user_settings");
this.action_button.set_label(_("_Next"));
this.action_button.set_sensitive(true);
this.action_button.get_style_context().remove_class("suggested-action");
this.action_button.remove_css_class("suggested-action");
}
private void switch_to_server_settings() {
this.stack.set_visible_child_name("server_settings");
this.action_button.set_label(_("_Create"));
this.action_button.set_sensitive(false);
this.action_button.get_style_context().add_class("suggested-action");
this.action_button.add_css_class("suggested-action");
}
private void set_server_settings_from_autoconfig(AutoConfig auto_config,
GLib.AsyncResult res)
throws Accounts.AutoConfigError {
AutoConfigValues auto_config_values = auto_config.get_config.end(res);
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
TlsComboBox imap_tls_combo_box = this.imap_tls.value;
TlsComboBox smtp_tls_combo_box = this.smtp_tls.value;
imap_hostname_entry.text = auto_config_values.imap_server +
":" + auto_config_values.imap_port;
smtp_hostname_entry.text = auto_config_values.smtp_server +
":" + auto_config_values.smtp_port;
imap_tls_combo_box.method = auto_config_values.imap_tls_method;
smtp_tls_combo_box.method = auto_config_values.smtp_tls_method;
Geary.ServiceInformation imap_service = this.receiving_service_widget.service;
Geary.ServiceInformation smtp_service = this.sending_service_widget.service;
this.imap_hostname.hide();
this.smtp_hostname.hide();
this.imap_tls.hide();
this.smtp_tls.hide();
imap_service.host = auto_config_values.imap_server;
imap_service.port = (uint16) uint.parse(auto_config_values.imap_port);
imap_service.transport_security = auto_config_values.imap_tls_method;
smtp_service.host = auto_config_values.smtp_server;
smtp_service.port = (uint16) uint.parse(auto_config_values.smtp_port);
smtp_service.transport_security = auto_config_values.smtp_tls_method;
//XXX GTK4 hide servr rows
// this.imap_hostname.hide();
// this.smtp_hostname.hide();
// this.imap_tls.hide();
// this.smtp_tls.hide();
switch (auto_config_values.id) {
case "googlemail.com":
@ -416,25 +331,26 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
}
private void set_server_settings_from_hostname(string hostname) {
Gtk.Entry imap_hostname_entry = this.imap_hostname.value;
Gtk.Entry smtp_hostname_entry = this.smtp_hostname.value;
Geary.ServiceInformation imap_service = this.receiving_service_widget.service;
Geary.ServiceInformation smtp_service = this.sending_service_widget.service;
string smtp_hostname = "smtp." + hostname;
string imap_hostname = "imap." + hostname;
string last_imap_hostname = "";
string last_smtp_hostname = "";
this.imap_hostname.show();
this.smtp_hostname.show();
// XXX GTK4 show these again if an autoconf happened
// this.imap_hostname.show();
// this.smtp_hostname.show();
if (this.last_valid_hostname != "") {
last_imap_hostname = "imap." + this.last_valid_hostname;
last_smtp_hostname = "smtp." + this.last_valid_hostname;
}
if (imap_hostname_entry.text == last_imap_hostname) {
imap_hostname_entry.text = imap_hostname;
if (imap_service.host == last_imap_hostname) {
imap_service.host = imap_hostname;
}
if (smtp_hostname_entry.text == last_smtp_hostname) {
smtp_hostname_entry.text = smtp_hostname;
if (smtp_service.host == last_smtp_hostname) {
smtp_service.host = smtp_hostname;
}
this.last_valid_hostname = hostname;
}
@ -458,51 +374,59 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
if (add_local) {
switch_to_server_settings();
} else {
this.editor.pop();
this.editor.pop_pane();
}
}
);
}
private void on_validated(Components.Validator.Trigger reason) {
[GtkCallback]
private void on_validated(Components.ValidatorGroup validators,
Components.Validator validator) {
check_validation();
if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
this.action_button.clicked();
}
//XXX GTK4 we somehow lost the Validator.Trigger here
// if (this.controls_valid && reason == Components.Validator.Trigger.ACTIVATED) {
// this.action_button.clicked();
// }
}
[GtkCallback]
private void on_activated() {
if (this.controls_valid) {
this.action_button.clicked();
}
}
private void on_email_changed() {
Gtk.Entry imap_login_entry = this.imap_login.value;
Gtk.Entry smtp_login_entry = this.smtp_login.value;
[GtkCallback]
private void on_email_row_changed(Gtk.Editable editable) {
var imap_service = this.receiving_service_widget.service;
var smtp_service = this.sending_service_widget.service;
this.auto_config_cancellable.cancel();
if (this.email.validator.state != Components.Validator.Validity.VALID) {
if (this.email_validator.state != Components.Validator.Validity.VALID) {
return;
}
string email = this.email.value.text;
string email = this.email_row.text;
string hostname = email.split("@")[1];
// Do not update entries if changed by user
if (imap_login_entry.text == this.last_valid_email) {
imap_login_entry.text = email;
if (imap_service.credentials.user == this.last_valid_email) {
imap_service.credentials = new Geary.Credentials(
Geary.Credentials.Method.PASSWORD, email
);
}
if (smtp_login_entry.text == this.last_valid_email) {
smtp_login_entry.text = email;
if (smtp_service.credentials.user == this.last_valid_email) {
smtp_service.credentials = new Geary.Credentials(
Geary.Credentials.Method.PASSWORD, email
);
}
this.last_valid_email = email;
// Try to get configuration from Thunderbird autoconfig service
this.action_spinner.visible = true;
this.action_spinner.active = true;
this.auto_config_cancellable = new GLib.Cancellable();
var auto_config = new AutoConfig(this.auto_config_cancellable);
auto_config.get_config.begin(hostname, (obj, res) => {
@ -513,19 +437,20 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
set_server_settings_from_hostname(hostname);
}
this.action_spinner.visible = false;
this.action_spinner.active = false;
});
}
private void on_smtp_auth_changed() {
#if 0
if (this.smtp_auth.value.source == Geary.Credentials.Requirement.CUSTOM) {
this.sending_list.add(this.smtp_login);
this.sending_list.add(this.smtp_password);
this.sending_list.append(this.smtp_login);
this.sending_list.append(this.smtp_password);
} else if (this.smtp_login.parent != null) {
this.sending_list.remove(this.smtp_login);
this.sending_list.remove(this.smtp_password);
}
check_validation();
#endif
}
private void on_untrusted_host(Geary.AccountInformation account,
@ -564,221 +489,4 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
this.validate_account.begin(this.op_cancellable);
}
}
[GtkCallback]
private void on_back_button_clicked() {
if (this.stack.get_visible_child_name() == "user_settings") {
this.editor.pop();
} else {
switch_to_user_settings();
}
}
[GtkCallback]
private bool on_list_keynav_failed(Gtk.Widget widget,
Gtk.DirectionType direction) {
bool ret = Gdk.EVENT_PROPAGATE;
Gtk.Container? next = null;
if (direction == Gtk.DirectionType.DOWN) {
if (widget == this.details_list) {
debug("Have details!");
next = this.receiving_list;
} else if (widget == this.receiving_list) {
next = this.sending_list;
}
} else if (direction == Gtk.DirectionType.UP) {
if (widget == this.sending_list) {
next = this.receiving_list;
} else if (widget == this.receiving_list) {
next = this.details_list;
}
}
if (next != null) {
next.child_focus(direction);
ret = Gdk.EVENT_STOP;
}
return ret;
}
}
private abstract class Accounts.AddPaneRow<Value> :
LabelledEditorRow<EditorAddPane,Value> {
internal Components.Validator? validator { get; protected set; }
protected AddPaneRow(string label, Value value) {
base(label, value);
this.activatable = false;
}
}
private abstract class Accounts.EntryRow : AddPaneRow<Gtk.Entry> {
private Components.EntryUndo undo;
protected EntryRow(string label,
string? initial_value = null,
string? placeholder = null) {
base(label, new Gtk.Entry());
this.value.text = initial_value ?? "";
this.value.placeholder_text = placeholder ?? "";
this.value.width_chars = 16;
this.undo = new Components.EntryUndo(this.value);
}
public override bool focus(Gtk.DirectionType direction) {
bool ret = Gdk.EVENT_PROPAGATE;
switch (direction) {
case Gtk.DirectionType.TAB_FORWARD:
case Gtk.DirectionType.TAB_BACKWARD:
ret = this.value.child_focus(direction);
break;
default:
ret = base.focus(direction);
break;
}
return ret;
}
}
private class Accounts.NameRow : EntryRow {
public NameRow(string default_name) {
// Translators: Label for the person's actual name when adding
// an account
base(_("Your name"), default_name.strip());
this.validator = new Components.Validator(this.value);
if (this.value.text != "") {
// Validate if the string is non-empty so it will be good
// to go from the start
this.validator.validate();
}
}
}
private class Accounts.EmailRow : EntryRow {
public EmailRow() {
base(
_("Email address"),
null,
// Translators: Placeholder for the default sender address
// when adding an account
_("person@example.com")
);
this.value.input_purpose = Gtk.InputPurpose.EMAIL;
this.validator = new Components.EmailValidator(this.value);
}
}
private class Accounts.LoginRow : EntryRow {
public LoginRow() {
// Translators: Label for an IMAP/SMTP service login/user name
// when adding an account
base(_("Login name"));
// Logins are not infrequently the same as the user's email
// address
this.value.input_purpose = Gtk.InputPurpose.EMAIL;
this.validator = new Components.Validator(this.value);
}
}
private class Accounts.PasswordRow : EntryRow {
public PasswordRow() {
base(_("Password"));
this.value.visibility = false;
this.value.input_purpose = Gtk.InputPurpose.PASSWORD;
this.validator = new Components.Validator(this.value);
}
}
private class Accounts.HostnameRow : EntryRow {
private Geary.Protocol type;
public HostnameRow(Geary.Protocol type) {
string label = "";
string placeholder = "";
switch (type) {
case Geary.Protocol.IMAP:
// Translators: Label for the IMAP server hostname when
// adding an account.
label = _("IMAP server");
// Translators: Placeholder for the IMAP server hostname
// when adding an account.
placeholder = _("imap.example.com");
break;
case Geary.Protocol.SMTP:
// Translators: Label for the SMTP server hostname when
// adding an account.
label = _("SMTP server");
// Translators: Placeholder for the SMTP server hostname
// when adding an account.
placeholder = _("smtp.example.com");
break;
}
base(label, null, placeholder);
this.type = type;
this.validator = new Components.NetworkAddressValidator(this.value, 0);
}
}
private class Accounts.TransportSecurityRow :
LabelledEditorRow<EditorAddPane,TlsComboBox> {
public TransportSecurityRow() {
TlsComboBox value = new TlsComboBox();
base(value.label, value);
// Set to Transport TLS by default per RFC 8314
this.value.method = Geary.TlsNegotiationMethod.TRANSPORT;
}
}
private class Accounts.OutgoingAuthRow :
LabelledEditorRow<EditorAddPane,OutgoingAuthComboBox> {
public OutgoingAuthRow() {
OutgoingAuthComboBox value = new OutgoingAuthComboBox();
base(value.label, value);
this.activatable = false;
this.value.source = Geary.Credentials.Requirement.USE_INCOMING;
}
}

View file

@ -9,15 +9,9 @@
* An account editor pane for editing a specific account's preferences.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_edit_pane.ui")]
internal class Accounts.EditorEditPane :
Gtk.Grid, EditorPane, AccountPane, CommandPane {
internal class Accounts.EditorEditPane : EditorPane, AccountPane, CommandPane {
/** {@inheritDoc} */
internal Gtk.Widget initial_widget {
get { return this.details_list.get_row_at_index(0); }
}
/** {@inheritDoc} */
internal Geary.AccountInformation account { get ; protected set; }
@ -27,32 +21,28 @@ internal class Accounts.EditorEditPane :
}
/** {@inheritDoc} */
internal bool is_operation_running { get; protected set; default = false; }
internal override bool is_operation_running { get; protected set; default = false; }
/** {@inheritDoc} */
internal GLib.Cancellable? op_cancellable {
internal override Cancellable? op_cancellable {
get; protected set; default = null;
}
/** {@inheritDoc} */
protected weak Accounts.Editor editor { get; set; }
protected override weak Accounts.Editor editor { get; set; }
[GtkChild] private unowned Gtk.HeaderBar header;
[GtkChild] private unowned Adw.HeaderBar header;
[GtkChild] private unowned Gtk.Grid pane_content;
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
[GtkChild] private unowned Gtk.ListBox details_list;
[GtkChild] private unowned Adw.EntryRow display_name_row;
[GtkChild] private unowned Gtk.ListBox senders_list;
[GtkChild] private unowned Gtk.Frame signature_frame;
[GtkChild] private unowned Adw.PreferencesGroup signature_bin;
private SignatureWebView signature_preview;
private bool signature_changed = false;
[GtkChild] private unowned Gtk.ListBox settings_list;
[GtkChild] private unowned Adw.ComboRow email_prefetch_row;
[GtkChild] private unowned Gtk.Button undo_button;
@ -63,24 +53,15 @@ internal class Accounts.EditorEditPane :
this.editor = editor;
this.account = account;
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
update_display_name();
this.details_list.set_header_func(Editor.seperator_headers);
this.details_list.add(
new DisplayNameRow(account, this.commands, this.op_cancellable)
);
this.senders_list.set_header_func(Editor.seperator_headers);
foreach (Geary.RFC822.MailboxAddress sender in
account.sender_mailboxes) {
this.senders_list.add(new_mailbox_row(sender));
this.senders_list.append(new_mailbox_row(sender));
}
this.senders_list.add(new AddMailboxRow());
this.signature_preview = new SignatureWebView(editor.application.config);
this.signature_preview.events = (
this.signature_preview.events | Gdk.EventType.FOCUS_CHANGE
);
this.signature_preview.add_css_class("card");
this.signature_preview.content_loaded.connect(() => {
// Only enable editability after the content has fully
// loaded to avoid the WebProcess crashing.
@ -91,33 +72,30 @@ internal class Accounts.EditorEditPane :
this.signature_preview.document_modified.connect(() => {
this.signature_changed = true;
});
this.signature_preview.focus_out_event.connect(() => {
// This event will also be fired if the top-level
// window loses focus, e.g. if the user alt-tabs away,
// so don't execute the command if the signature web
// view no longer the focus widget
if (!this.signature_preview.is_focus &&
this.signature_changed) {
this.commands.execute.begin(
new SignatureChangedCommand(
this.signature_preview, account
),
this.op_cancellable
);
}
return Gdk.EVENT_PROPAGATE;
});
var focus_controller = new Gtk.EventControllerFocus();
focus_controller.leave.connect(() => {
// This event will also be fired if the top-level
// window loses focus, e.g. if the user alt-tabs away,
// so don't execute the command if the signature web
// view no longer the focus widget
if (!this.signature_preview.is_focus() &&
this.signature_changed) {
this.commands.execute.begin(
new SignatureChangedCommand(
this.signature_preview, account
),
this.op_cancellable
);
}
});
this.signature_preview.add_controller(focus_controller);
this.signature_bin.add(this.signature_preview);
this.signature_preview.show();
this.signature_preview.load_html(
Geary.HTML.smart_escape(account.signature)
);
this.signature_frame.add(this.signature_preview);
this.settings_list.set_header_func(Editor.seperator_headers);
this.settings_list.add(new EmailPrefetchRow(this));
this.remove_button.set_visible(
!this.editor.accounts.is_goa_account(account)
);
@ -131,8 +109,12 @@ internal class Accounts.EditorEditPane :
disconnect_command_signals();
}
private void update_display_name() {
this.display_name_row.text = this.account.display_name;
}
internal string? get_default_name() {
string? name = account.primary_mailbox.name;
string? name = this.account.primary_mailbox.name;
if (Geary.String.is_empty_or_whitespace(name)) {
name = this.editor.accounts.get_account_name();
@ -141,15 +123,11 @@ internal class Accounts.EditorEditPane :
return name;
}
/** {@inheritDoc} */
internal Gtk.HeaderBar get_header() {
return this.header;
}
internal MailboxRow new_mailbox_row(Geary.RFC822.MailboxAddress sender) {
MailboxRow row = new MailboxRow(this.account, sender);
row.move_to.connect(on_sender_row_moved);
row.dropped.connect(on_sender_row_dropped);
MailboxRow row = new MailboxRow(this.account, sender, this);
//XXX GTK4
// row.move_to.connect(on_sender_row_moved);
// row.dropped.connect(on_sender_row_dropped);
return row;
}
@ -192,85 +170,96 @@ internal class Accounts.EditorEditPane :
);
}
[GtkCallback]
private void on_setting_activated(Gtk.ListBoxRow row) {
EditorRow<EditorEditPane>? setting = row as EditorRow<EditorEditPane>;
if (setting != null) {
setting.activated(this);
}
}
[GtkCallback]
private void on_server_settings_clicked() {
this.editor.push(new EditorServersPane(this.editor, this.account));
this.editor.push_pane(new EditorServersPane(this.editor, this.account));
}
[GtkCallback]
private void on_remove_account_clicked() {
if (!this.editor.accounts.is_goa_account(account)) {
var button = new Gtk.Button.with_mnemonic(_("Remove Account"));
button.get_style_context().add_class(Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION);
button.show();
var dialog = new Adw.AlertDialog(
_("Remove Account: %s").printf(account.primary_mailbox.address),
_("This will remove it from Geary and delete locally cached email data from your computer. Nothing will be deleted from your service provider.")
);
dialog.add_css_class("warning");
var dialog = new Gtk.MessageDialog(this.editor,
Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
Gtk.MessageType.WARNING,
Gtk.ButtonsType.NONE,
_("Remove Account: %s"),
account.primary_mailbox.address);
dialog.secondary_text = _("This will remove it from Geary and delete locally cached email data from your computer. Nothing will be deleted from your service provider.");
dialog.add_response("cancel", _("_Cancel"));
dialog.close_response = "cancel";
dialog.add_response("remove", _("_Remove Account"));
dialog.set_response_appearance("remove", Adw.ResponseAppearance.DESTRUCTIVE);
dialog.add_button (_("_Cancel"), Gtk.ResponseType.CANCEL);
dialog.add_action_widget(button, Gtk.ResponseType.ACCEPT);
dialog.response.connect((response_id) => {
if (response_id == Gtk.ResponseType.ACCEPT)
dialog.choose.begin(this, null, (obj, res) => {
string response = dialog.choose.end(res);
if (response == "remove")
this.editor.remove_account(this.account);
dialog.destroy();
});
dialog.show();
}
}
[GtkCallback]
private void on_back_button_clicked() {
this.editor.pop();
private void on_add_mailbox_clicked(Gtk.Button add_button) {
var dialog = new MailboxEditorDialog.for_new(get_default_name());
dialog.apply.connect((dialog, mailbox) => {
this.commands.execute.begin(
new AppendMailboxCommand(
this.senders_list,
new_mailbox_row(mailbox)
),
this.op_cancellable
);
dialog.close();
});
dialog.present(this);
}
[GtkCallback]
private bool on_list_keynav_failed(Gtk.Widget widget,
Gtk.DirectionType direction) {
bool ret = Gdk.EVENT_PROPAGATE;
Gtk.Container? next = null;
if (direction == Gtk.DirectionType.DOWN) {
if (widget == this.details_list) {
next = this.senders_list;
} else if (widget == this.senders_list) {
this.signature_preview.grab_focus();
} else if (widget == this.signature_preview) {
next = this.settings_list;
}
} else if (direction == Gtk.DirectionType.UP) {
if (widget == this.settings_list) {
this.signature_preview.grab_focus();
} else if (widget == this.signature_preview) {
next = this.senders_list;
} else if (widget == this.senders_list) {
next = this.details_list;
}
}
if (next != null) {
next.child_focus(direction);
ret = Gdk.EVENT_STOP;
}
return ret;
private static string period_to_string(Adw.EnumListItem item,
Accounts.PrefetchPeriod period) {
return period.to_string();
}
}
/**
* An enum to describe the possible values for the "Download Mail" option
*/
public enum Accounts.PrefetchPeriod {
2_WEEKS = 14,
1_MONTH = 30,
3_MONTHS = 90,
6_MONTHS = 180,
1_YEAR = 365,
2_YEARS = 720,
4_YEARS = 1461,
EVERYTHING = -1;
public unowned string to_string() {
switch (this) {
case 2_WEEKS:
return _("2 weeks back");
case 1_MONTH:
return _("1 month back");
case 3_MONTHS:
return _("3 months back");
case 6_MONTHS:
return _("6 months back");
case 1_YEAR:
return _("1 year back");
case 2_YEARS:
return _("2 years back");
case 4_YEARS:
return _("4 years back");
case EVERYTHING:
return _("Everything");
}
return_val_if_reached("");
}
}
private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
@ -299,7 +288,9 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
// undoable
this.value_undo = new Components.EntryUndo(this.value);
this.value.focus_out_event.connect(on_focus_out);
var focus_controller = new Gtk.EventControllerFocus();
focus_controller.leave.connect(on_focus_out);
this.value.add_controller(focus_controller);
}
public override void update() {
@ -337,231 +328,75 @@ private class Accounts.DisplayNameRow : AccountRow<EditorEditPane,Gtk.Entry> {
}
}
private bool on_focus_out() {
private void on_focus_out() {
commit();
return Gdk.EVENT_PROPAGATE;
}
}
private class Accounts.AddMailboxRow : AddRow<EditorEditPane> {
private class Accounts.MailboxRow : Adw.ActionRow {
public Geary.AccountInformation account { get; construct set; }
public AddMailboxRow() {
// Translators: Tooltip for adding a new email sender/from
// address's address to an account
this.set_tooltip_text(_("Add a new sender email address"));
}
public Geary.RFC822.MailboxAddress mailbox { get; construct set; }
public override void activated(EditorEditPane pane) {
MailboxEditorPopover popover = new MailboxEditorPopover(
pane.get_default_name() ?? "", "", false
);
popover.activated.connect(() => {
pane.commands.execute.begin(
new AppendMailboxCommand(
(Gtk.ListBox) get_parent(),
pane.new_mailbox_row(
new Geary.RFC822.MailboxAddress(
popover.display_name,
popover.address
)
)
),
pane.op_cancellable
);
popover.popdown();
});
popover.set_relative_to(this);
popover.popup();
}
}
private class Accounts.MailboxRow : AccountRow<EditorEditPane,Gtk.Label> {
internal Geary.RFC822.MailboxAddress mailbox;
public unowned Accounts.EditorEditPane pane { get; construct set; }
public MailboxRow(Geary.AccountInformation account,
Geary.RFC822.MailboxAddress mailbox) {
var label = new Gtk.Label("");
label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
label.set_line_wrap(true);
base(account, "", label);
this.mailbox = mailbox;
enable_drag();
Geary.RFC822.MailboxAddress mailbox,
Accounts.EditorEditPane pane) {
Object(
account: account,
mailbox: mailbox,
pane: pane,
activatable: true
);
//XXX GTK4 do this again
// enable_drag();
//XXX GTK4 also on notify
update();
}
public override void activated(EditorEditPane pane) {
MailboxEditorPopover popover = new MailboxEditorPopover(
this.mailbox.name ?? "",
this.mailbox.address,
public override void activate() {
var dialog = new MailboxEditorDialog.for_existing(
this.mailbox,
this.account.has_sender_aliases
);
popover.activated.connect(() => {
pane.commands.execute.begin(
new UpdateMailboxCommand(
this,
new Geary.RFC822.MailboxAddress(
popover.display_name,
popover.address
)
),
pane.op_cancellable
);
popover.popdown();
});
popover.remove_clicked.connect(() => {
pane.commands.execute.begin(
new RemoveMailboxCommand(this),
pane.op_cancellable
);
popover.popdown();
});
popover.set_relative_to(this);
popover.popup();
dialog.apply.connect((dialog, mailbox) => {
this.pane.commands.execute.begin(
new UpdateMailboxCommand(this, mailbox),
this.pane.op_cancellable
);
dialog.close();
});
dialog.remove.connect((dialog) => {
this.pane.commands.execute.begin(
new RemoveMailboxCommand(this),
this.pane.op_cancellable
);
dialog.close();
});
dialog.present(this);
}
public override void update() {
private void update() {
this.title = mailbox.address.strip();
string? name = this.mailbox.name;
if (Geary.String.is_empty_or_whitespace(name)) {
// Translators: Label used to indicate the user has
// provided no display name for one of their sender
// email addresses in their account settings.
name = _("Name not set");
set_dim_label(true);
} else {
set_dim_label(false);
}
this.label.set_text(name);
this.value.set_text(mailbox.address.strip());
}
}
internal class Accounts.MailboxEditorPopover : EditorPopover {
public string display_name { get; private set; }
public string address { get; private set; }
private Gtk.Entry name_entry = new Gtk.Entry();
private Components.EntryUndo name_undo;
private Gtk.Entry address_entry = new Gtk.Entry();
private Components.EntryUndo address_undo;
private Components.EmailValidator address_validator;
private Gtk.Button remove_button;
public signal void activated();
public signal void remove_clicked();
public MailboxEditorPopover(string? display_name,
string? address,
bool can_remove) {
this.display_name = display_name;
this.address = address;
this.name_entry.set_text(display_name ?? "");
this.name_entry.set_placeholder_text(
// Translators: This is used as a placeholder for the
// display name for an email address when editing a user's
// sender address preferences for an account.
_("Sender Name")
);
this.name_entry.set_width_chars(20);
this.name_entry.changed.connect(on_name_changed);
this.name_entry.activate.connect(on_activate);
this.name_entry.show();
this.name_undo = new Components.EntryUndo(this.name_entry);
this.address_entry.input_purpose = Gtk.InputPurpose.EMAIL;
this.address_entry.set_text(address ?? "");
this.address_entry.set_placeholder_text(
// Translators: This is used as a placeholder for the
// address part of an email address when editing a user's
// sender address preferences for an account.
_("person@example.com")
);
this.address_entry.set_width_chars(20);
this.address_entry.changed.connect(on_address_changed);
this.address_entry.activate.connect(on_activate);
this.address_entry.show();
this.address_undo = new Components.EntryUndo(this.address_entry);
this.address_validator =
new Components.EmailValidator(this.address_entry);
this.remove_button = new Gtk.Button.with_label(_("Remove"));
this.remove_button.halign = Gtk.Align.END;
this.remove_button.get_style_context().add_class(
"geary-setting-remove"
);
this.remove_button.get_style_context().add_class(
Gtk.STYLE_CLASS_DESTRUCTIVE_ACTION
);
this.remove_button.clicked.connect(on_remove_clicked);
this.remove_button.show();
add_labelled_row(
// Translators: Label used for the display name part of an
// email address when editing a user's sender address
// preferences for an account.
_("Sender name"),
this.name_entry
);
add_labelled_row(
// Translators: Label used for the address part of an
// email address when editing a user's sender address
// preferences for an account.
_("Email address"),
this.address_entry
);
if (can_remove) {
this.layout.attach(this.remove_button, 0, 2, 2, 1);
}
this.popup_focus = this.name_entry;
}
~MailboxEditorPopover() {
this.name_entry.changed.disconnect(on_name_changed);
this.name_entry.activate.disconnect(on_activate);
this.address_entry.changed.disconnect(on_address_changed);
this.address_entry.activate.disconnect(on_activate);
this.remove_button.clicked.disconnect(on_remove_clicked);
}
private void on_name_changed() {
this.display_name = this.name_entry.get_text().strip();
}
private void on_address_changed() {
this.address = this.address_entry.get_text().strip();
}
private void on_remove_clicked() {
remove_clicked();
}
private void on_activate() {
if (this.address_validator.state == Components.Validator.Validity.INDETERMINATE || this.address_validator.is_valid) {
activated();
}
this.subtitle = name;
}
}

View file

@ -9,7 +9,7 @@
* An account editor pane for listing all known accounts.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_list_pane.ui")]
internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
internal class Accounts.EditorListPane : Accounts.EditorPane, CommandPane {
private static int ordinal_sort(Gtk.ListBoxRow a, Gtk.ListBoxRow b) {
@ -29,29 +29,22 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
/** {@inheritDoc} */
internal Gtk.Widget initial_widget {
get {
return this.accounts_list;
}
}
/** {@inheritDoc} */
internal Application.CommandStack commands {
internal override Application.CommandStack commands {
get; protected set; default = new Application.CommandStack();
}
/** {@inheritDoc} */
internal bool is_operation_running { get; protected set; default = false; }
internal override bool is_operation_running { get; protected set; default = false; }
/** {@inheritDoc} */
internal GLib.Cancellable? op_cancellable {
internal override Cancellable? op_cancellable {
get; protected set; default = null;
}
internal Manager accounts { get; private set; }
/** {@inheritDoc} */
protected weak Accounts.Editor editor { get; set; }
protected override weak Accounts.Editor editor { get; set; }
private bool show_welcome {
get {
@ -59,11 +52,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
}
}
[GtkChild] private unowned Gtk.HeaderBar header;
[GtkChild] private unowned Gtk.Grid pane_content;
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
[GtkChild] private unowned Adw.HeaderBar header;
[GtkChild] private unowned Gtk.Grid welcome_panel;
@ -71,7 +60,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
[GtkChild] private unowned Gtk.ListBox accounts_list;
[GtkChild] private unowned Gtk.Frame accounts_list_frame;
[GtkChild] private unowned Gtk.ScrolledWindow accounts_list_scrolled;
private Gee.Map<Geary.AccountInformation,EditorEditPane> edit_pane_cache =
new Gee.HashMap<Geary.AccountInformation,EditorEditPane>();
@ -85,9 +74,6 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
// without worrying about the editor's lifecycle
this.accounts = editor.accounts;
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
this.accounts_list.set_header_func(Editor.seperator_headers);
this.accounts_list.set_sort_func(ordinal_sort);
foreach (Geary.AccountInformation account in this.accounts.iterable()) {
add_account(account, this.accounts.get_status(account));
@ -104,22 +90,22 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
update_welcome_panel();
}
public override void destroy() {
public override void dispose() {
this.commands.executed.disconnect(on_execute);
this.commands.undone.disconnect(on_undo);
this.commands.redone.disconnect(on_execute);
disconnect_command_signals();
this.accounts.account_added.disconnect(on_account_added);
this.accounts.account_status_changed.disconnect(on_account_status_changed);
this.accounts.account_removed.disconnect(on_account_removed);
this.accounts.account_status_changed.disconnect(on_account_status_changed);
this.accounts.account_removed.disconnect(on_account_removed);
this.edit_pane_cache.clear();
base.destroy();
this.edit_pane_cache.clear();
base.dispose();
}
internal void show_new_account() {
this.editor.push(new EditorAddPane(this.editor));
this.editor.push_pane(new EditorAddPane(this.editor));
}
internal void show_existing_account(Geary.AccountInformation account) {
@ -128,7 +114,7 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
edit_pane = new EditorEditPane(this.editor, account);
this.edit_pane_cache.set(account, edit_pane);
}
this.editor.push(edit_pane);
this.editor.push_pane(edit_pane);
}
/** Removes an account from the list. */
@ -142,17 +128,12 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
}
}
/** {@inheritDoc} */
internal Gtk.HeaderBar get_header() {
return this.header;
}
private void add_account(Geary.AccountInformation account,
Manager.Status status) {
AccountListRow row = new AccountListRow(account, status);
row.move_to.connect(on_editor_row_moved);
row.moved.connect(on_account_row_moved);
row.dropped.connect(on_editor_row_dropped);
this.accounts_list.add(row);
this.accounts_list.append(row);
}
private void update_welcome_panel() {
@ -160,24 +141,24 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
// No accounts are available, so show only the welcome
// pane and service list.
this.welcome_panel.show();
this.accounts_list_frame.hide();
this.accounts_list_scrolled.hide();
} else {
// There are some accounts available, so show them and
// the full add service UI.
this.welcome_panel.hide();
this.accounts_list_frame.show();
this.accounts_list_scrolled.show();
}
}
private AccountListRow? get_account_row(Geary.AccountInformation account) {
AccountListRow? row = null;
this.accounts_list.foreach((child) => {
AccountListRow? account_row = child as AccountListRow;
if (account_row != null && account_row.account == account) {
row = account_row;
}
});
return row;
for (int i = 0; true; i++) {
unowned var row = this.accounts_list.get_row_at_index(i) as AccountListRow;
if (row == null)
break;
if (row.account == account)
return row;
}
return null;
}
private void on_account_added(Geary.AccountInformation account,
@ -194,19 +175,17 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
}
}
private void on_editor_row_moved(EditorRow source, int new_position) {
private void on_account_row_moved(AccountListRow source, int new_position) {
this.commands.execute.begin(
new ReorderAccountCommand(
(AccountListRow) source, new_position, this.accounts
),
new ReorderAccountCommand(source, new_position, this.accounts),
this.op_cancellable
);
}
private void on_editor_row_dropped(EditorRow source, EditorRow target) {
private void on_editor_row_dropped(AccountListRow source, AccountListRow target) {
this.commands.execute.begin(
new ReorderAccountCommand(
(AccountListRow) source, target.get_index(), this.accounts
source, target.get_index(), this.accounts
),
this.op_cancellable
);
@ -222,34 +201,51 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
private void on_execute(Application.Command command) {
if (command.executed_label != null) {
uint notification_time =
Components.InAppNotification.DEFAULT_DURATION;
if (command.executed_notification_brief) {
notification_time =
this.editor.application.config.brief_notification_duration;
}
Components.InAppNotification ian = new Components.InAppNotification(
command.executed_label, notification_time
);
ian.set_button(_("Undo"), Action.Edit.prefix(Action.Edit.UNDO));
this.editor.add_notification(ian);
var toast = new Adw.Toast(command.executed_label);
toast.button_label = _("Undo");
toast.action_name = Action.Edit.prefix(Action.Edit.UNDO);
if (command.executed_notification_brief)
toast.timeout = this.editor.application.config.brief_notification_duration;
this.editor.add_toast(toast);
}
}
private void on_undo(Application.Command command) {
if (command.undone_label != null) {
Components.InAppNotification ian =
new Components.InAppNotification(command.undone_label);
ian.set_button(_("Redo"), Action.Edit.prefix(Action.Edit.REDO));
this.editor.add_notification(ian);
var toast = new Adw.Toast(command.undone_label);
toast.button_label = _("Redo");
toast.action_name = Action.Edit.prefix(Action.Edit.REDO);
this.editor.add_toast(toast);
}
}
[GtkCallback]
private void on_row_activated(Gtk.ListBoxRow row) {
EditorRow<EditorListPane>? setting = row as EditorRow<EditorListPane>;
if (setting != null) {
setting.activated(this);
unowned var account_row = row as AccountListRow;
if (account_row == null)
return;
Manager manager = this.accounts;
if (manager.is_goa_account(account_row.account) &&
manager.get_status(account_row.account) != Manager.Status.ENABLED) {
// GOA account but it's disabled, so just take people
// directly to the GOA panel
manager.show_goa_account.begin(
account_row.account, this.op_cancellable,
(obj, res) => {
try {
manager.show_goa_account.end(res);
} catch (GLib.Error err) {
// XXX display an error to the user
debug(
"Failed to show GOA account \"%s\": %s",
account_row.account.id,
err.message
);
}
});
} else {
show_existing_account(account_row.account);
}
}
@ -260,28 +256,29 @@ internal class Accounts.EditorListPane : Gtk.Grid, EditorPane, CommandPane {
}
private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
[GtkTemplate (ui = "/org/gnome/Geary/accounts-editor-account-list-row.ui")]
private class Accounts.AccountListRow : Adw.ActionRow {
public Geary.AccountInformation account { get; construct set; }
private Gtk.Label service_label = new Gtk.Label("");
private Gtk.Image unavailable_icon = new Gtk.Image.from_icon_name(
"dialog-warning-symbolic", Gtk.IconSize.BUTTON
);
[GtkChild] private unowned Gtk.Image drag_icon;
[GtkChild] private unowned Gtk.Image unavailable_icon;
private bool drag_picked_up = false;
private double drag_x;
private double drag_y;
public signal void moved(int new_position);
public signal void dropped(AccountListRow target);
construct {
this.account.changed.connect(on_account_changed);
update();
}
public AccountListRow(Geary.AccountInformation account,
Manager.Status status) {
base(account, "", new Gtk.Grid());
enable_drag();
this.value.add(this.unavailable_icon);
this.value.add(this.service_label);
this.service_label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
this.service_label.set_line_wrap(true);
this.service_label.show();
this.account.changed.connect(on_account_changed);
update();
Object(account: account);
update_status(status);
}
@ -289,37 +286,12 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
this.account.changed.disconnect(on_account_changed);
}
public override void activated(EditorListPane pane) {
Manager manager = pane.accounts;
if (manager.is_goa_account(this.account) &&
manager.get_status(this.account) != Manager.Status.ENABLED) {
// GOA account but it's disabled, so just take people
// directly to the GOA panel
manager.show_goa_account.begin(
account, pane.op_cancellable,
(obj, res) => {
try {
manager.show_goa_account.end(res);
} catch (GLib.Error err) {
// XXX display an error to the user
debug(
"Failed to show GOA account \"%s\": %s",
account.id,
err.message
);
}
});
} else {
pane.show_existing_account(this.account);
}
}
public override void update() {
public void update() {
string name = this.account.display_name;
if (Geary.String.is_empty(name)) {
name = account.primary_mailbox.to_address_display("", "");
}
this.label.set_text(name);
this.title = name;
string? details = this.account.service_label;
switch (account.service_provider) {
@ -335,32 +307,31 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
// no-op: Use the generated label
break;
}
this.service_label.set_text(details);
this.subtitle = details;
}
public void update_status(Manager.Status status) {
bool enabled = false;
switch (status) {
case ENABLED:
enabled = true;
this.set_tooltip_text("");
remove_css_class("dim-label");
this.tooltip_text = "";
this.unavailable_icon.visible = false;
break;
case DISABLED:
this.set_tooltip_text(
// Translators: Tooltip for accounts that have been
// loaded but disabled by the user.
_("This account has been disabled")
);
// Translators: Tooltip for accounts that have been
// loaded but disabled by the user.
this.tooltip_text = _("This account has been disabled");
add_css_class("dim-label");
this.unavailable_icon.visible = true;
break;
case UNAVAILABLE:
this.set_tooltip_text(
// Translators: Tooltip for accounts that have been
// loaded but because of some error are not able to be
// used.
_("This account has encountered a problem and is unavailable")
);
// Translators: Tooltip for accounts that have been loaded but
// because of some error are not able to be used.
this.tooltip_text = _("This account has encountered a problem and is unavailable");
add_css_class("dim-label");
this.unavailable_icon.visible = true;
break;
case REMOVED:
@ -368,23 +339,6 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
break;
}
this.unavailable_icon.set_visible(!enabled);
if (enabled) {
this.label.get_style_context().remove_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
this.service_label.get_style_context().remove_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
} else {
this.label.get_style_context().add_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
this.service_label.get_style_context().add_class(
Gtk.STYLE_CLASS_DIM_LABEL
);
}
}
private void on_account_changed() {
@ -395,6 +349,129 @@ private class Accounts.AccountListRow : AccountRow<EditorListPane,Gtk.Grid> {
}
}
[GtkCallback]
private bool on_key_pressed(Gtk.EventControllerKey key_controller,
uint keyval,
uint keycode,
Gdk.ModifierType state) {
if (state != Gdk.ModifierType.CONTROL_MASK)
return Gdk.EVENT_PROPAGATE;
int index = get_index();
if (keyval == Gdk.Key.Up) {
index--;
if (index >= 0) {
moved(index);
return Gdk.EVENT_STOP;
}
} else if (keyval == Gdk.Key.Down) {
index++;
if (get_next_sibling() != null) {
moved(index);
return Gdk.EVENT_STOP;
}
}
return Gdk.EVENT_PROPAGATE;
}
// DND
[GtkCallback]
private void on_drag_source_begin(Gtk.DragSource drag_source,
Gdk.Drag drag) {
// Show our row while dragging
var drag_widget = new Gtk.ListBox();
drag_widget.opacity = 0.8;
Gtk.Allocation allocation;
get_allocation(out allocation);
drag_widget.set_size_request(allocation.width, allocation.height);
var drag_row = new AccountListRow(this.account, Manager.Status.ENABLED);
drag_widget.append(drag_row);
drag_widget.drag_highlight_row(drag_row);
var drag_icon = (Gtk.DragIcon) Gtk.DragIcon.get_for_drag(drag);
drag_icon.child = drag_widget;
drag.set_hotspot((int) this.drag_x, (int) this.drag_y);
// Set a visual hint that the row is being dragged
add_css_class("geary-drag-source");
this.drag_picked_up = true;
}
[GtkCallback]
private void on_drag_source_end(Gtk.DragSource drag_source,
Gdk.Drag drag,
bool delete_data) {
remove_css_class("geary-drag-source");
this.drag_picked_up = false;
}
[GtkCallback]
private Gdk.ContentProvider on_drag_source_prepare(Gtk.DragSource drag_source,
double x,
double y) {
Graphene.Point p = { (float) x, (float) y };
Graphene.Point p_row;
this.drag_icon.compute_point(this, p, out p_row);
this.drag_x = p_row.x;
this.drag_y = p_row.y;
GLib.Value val = GLib.Value(typeof(int));
val.set_int(get_index());
return new Gdk.ContentProvider.for_value(val);
}
[GtkCallback]
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target,
double x,
double y) {
// Don't highlight the same row that was picked up
if (!this.drag_picked_up) {
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.drag_highlight_row(this);
}
}
return Gdk.DragAction.MOVE;
}
[GtkCallback]
private void on_drop_target_leave(Gtk.DropTarget drop_target) {
if (!this.drag_picked_up) {
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.drag_unhighlight_row();
}
}
}
[GtkCallback]
private bool on_drop_target_drop(Gtk.DropTarget drop_target,
GLib.Value val,
double x,
double y) {
if (!val.holds(typeof(int))) {
warning("Can't deal with non-int row value");
return false;
}
int drag_index = val.get_int();
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
var drag_row = parent.get_row_at_index(drag_index) as AccountListRow;
if (drag_row != null && drag_row != this) {
drag_row.dropped(this);
return true;
}
}
return false;
}
}

View file

@ -8,20 +8,14 @@
internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
private const string DND_ATOM = "geary-editor-row";
private const Gtk.TargetEntry[] DRAG_ENTRIES = {
{ DND_ATOM, Gtk.TargetFlags.SAME_APP, 0 }
};
protected Gtk.Box layout {
get;
private set;
default = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5); }
private Gtk.Container drag_handle;
private Gtk.Image drag_handle;
private bool drag_picked_up = false;
private bool drag_entered = false;
public signal void move_to(int new_position);
@ -29,66 +23,54 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
public EditorRow() {
get_style_context().add_class("geary-settings");
get_style_context().add_class("geary-labelled-row");
// We'd like to add the drag handle only when needed, but
// GNOME/gtk#1495 prevents us from doing so.
Gtk.EventBox drag_box = new Gtk.EventBox();
drag_box.add(
new Gtk.Image.from_icon_name(
"list-drag-handle-symbolic", Gtk.IconSize.BUTTON
)
);
this.drag_handle = new Gtk.Grid();
this.drag_handle.valign = Gtk.Align.CENTER;
this.drag_handle.add(drag_box);
this.drag_handle.show_all();
this.drag_handle.hide();
// Translators: Tooltip for dragging list items
this.drag_handle.set_tooltip_text(_("Drag to move this item"));
add_css_class("geary-settings");
add_css_class("geary-labelled-row");
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 5);
box.add(drag_handle);
box.add(this.layout);
box.show();
add(box);
this.child = box;
this.layout.show();
this.show();
this.size_allocate.connect((allocation) => {
if (allocation.width < 500) {
if (this.layout.orientation == Gtk.Orientation.HORIZONTAL) {
this.layout.orientation = Gtk.Orientation.VERTICAL;
}
} else if (this.layout.orientation == Gtk.Orientation.VERTICAL) {
this.layout.orientation = Gtk.Orientation.HORIZONTAL;
}
});
var breakpoint_bin = new Adw.BreakpointBin();
box.append(breakpoint_bin);
breakpoint_bin.child = this.layout;
var breakpoint = new Adw.Breakpoint(Adw.BreakpointCondition.parse("max-width: 500px"));
breakpoint.add_setters(this.layout, "orientation", Gtk.Orientation.VERTICAL);
breakpoint_bin.add_breakpoint(breakpoint);
this.drag_handle = new Gtk.Image.from_icon_name("list-drag-handle-symbolic");
this.drag_handle.valign = Gtk.Align.CENTER;
this.drag_handle.visible = false;
// Translators: Tooltip for dragging list items
this.drag_handle.set_tooltip_text(_("Drag to move this item"));
box.append(this.drag_handle);
var key_controller = new Gtk.EventControllerKey();
key_controller.key_pressed.connect(on_key_pressed);
add_controller(key_controller);
}
public virtual void activated(PaneType pane) {
// No-op by default
}
public override bool key_press_event(Gdk.EventKey event) {
private bool on_key_pressed(Gtk.EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) {
bool ret = Gdk.EVENT_PROPAGATE;
if (event.state == Gdk.ModifierType.CONTROL_MASK) {
if (state == Gdk.ModifierType.CONTROL_MASK) {
int index = get_index();
if (event.keyval == Gdk.Key.Up) {
if (keyval == Gdk.Key.Up) {
index -= 1;
if (index >= 0) {
move_to(index);
ret = Gdk.EVENT_STOP;
}
} else if (event.keyval == Gdk.Key.Down) {
} else if (keyval == Gdk.Key.Down) {
index += 1;
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null &&
index < parent.get_children().length() &&
//XXX GTK4 - I *think* we don't need this anymore
// index < parent.get_children().length() &&
!(parent.get_row_at_index(index) is AddRow)) {
move_to(index);
ret = Gdk.EVENT_STOP;
@ -96,127 +78,113 @@ internal class Accounts.EditorRow<PaneType> : Gtk.ListBoxRow {
}
}
if (ret != Gdk.EVENT_STOP) {
ret = base.key_press_event(event);
}
return ret;
}
/** Adds a drag handle to the row and enables drag signals. */
protected void enable_drag() {
Gtk.drag_source_set(
this.drag_handle,
Gdk.ModifierType.BUTTON1_MASK,
DRAG_ENTRIES,
Gdk.DragAction.MOVE
);
//XXX GTK4 - is this activated on click?
Gtk.DragSource drag_source = new Gtk.DragSource();
drag_source.drag_begin.connect(on_drag_source_begin);
drag_source.drag_end.connect(on_drag_source_end);
drag_source.prepare.connect(on_drag_source_prepare);
this.drag_handle.add_controller(drag_source);
Gtk.drag_dest_set(
this,
// No highlight, we'll take care of that ourselves so we
// can avoid highlighting the row that was picked up
Gtk.DestDefaults.MOTION | Gtk.DestDefaults.DROP,
DRAG_ENTRIES,
Gdk.DragAction.MOVE
);
Gtk.DropTarget drop_target = new Gtk.DropTarget(typeof(int), Gdk.DragAction.MOVE);
drop_target.enter.connect(on_drop_target_enter);
drop_target.leave.connect(on_drop_target_leave);
drop_target.drop.connect(on_drop_target_drop);
this.drag_handle.add_controller(drop_target);
this.drag_handle.drag_begin.connect(on_drag_begin);
this.drag_handle.drag_end.connect(on_drag_end);
this.drag_handle.drag_data_get.connect(on_drag_data_get);
//XXX GTK4 - Disable highlight by default, so we can avoid highlighting the row that was picked up
this.drag_handle.add_css_class("geary-drag-handle");
this.drag_handle.visible = true;
this.drag_motion.connect(on_drag_motion);
this.drag_leave.connect(on_drag_leave);
this.drag_data_received.connect(on_drag_data_received);
this.drag_handle.get_style_context().add_class("geary-drag-handle");
this.drag_handle.show();
get_style_context().add_class("geary-draggable");
add_css_class("geary-draggable");
}
private void on_drag_begin(Gdk.DragContext context) {
private void on_drag_source_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
// Draw a nice drag icon
Gtk.Allocation alloc = Gtk.Allocation();
this.get_allocation(out alloc);
Cairo.ImageSurface surface = new Cairo.ImageSurface(
Cairo.Format.ARGB32, alloc.width, alloc.height
);
Cairo.Context paint = new Cairo.Context(surface);
//XXX GTK4 lol, let's just make this a proper drag icon at some point
// Cairo.ImageSurface surface = new Cairo.ImageSurface(
// Cairo.Format.ARGB32, alloc.width, alloc.height
// );
// Cairo.Context paint = new Cairo.Context(surface);
Gtk.StyleContext style = get_style_context();
style.add_class("geary-drag-icon");
draw(paint);
style.remove_class("geary-drag-icon");
// add_css_class("geary-drag-icon");
// draw(paint);
// remove_css_class("geary-drag-icon");
int x, y;
this.drag_handle.translate_coordinates(this, 0, 0, out x, out y);
surface.set_device_offset(-x, -y);
Gtk.drag_set_icon_surface(context, surface);
// drag_source.set_icon(surface, 0, 0);
// Set a visual hint that the row is being dragged
style.add_class("geary-drag-source");
add_css_class("geary-drag-source");
this.drag_picked_up = true;
}
private void on_drag_end(Gdk.DragContext context) {
get_style_context().remove_class("geary-drag-source");
private void on_drag_source_end(Gtk.DragSource drag_source,
Gdk.Drag drag,
bool delete_data) {
remove_css_class("geary-drag-source");
this.drag_picked_up = false;
}
private bool on_drag_motion(Gdk.DragContext context,
int x, int y,
uint time_) {
if (!this.drag_entered) {
this.drag_entered = true;
// Don't highlight the same row that was picked up
if (!this.drag_picked_up) {
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.drag_highlight_row(this);
}
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target,
double x,
double y) {
// Don't highlight the same row that was picked up
if (!this.drag_picked_up) {
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.drag_highlight_row(this);
}
}
return true;
return Gdk.DragAction.MOVE;
}
private void on_drag_leave(Gdk.DragContext context,
uint time_) {
private void on_drop_target_leave(Gtk.DropTarget drop_target) {
if (!this.drag_picked_up) {
Gtk.ListBox? parent = get_parent() as Gtk.ListBox;
if (parent != null) {
parent.drag_unhighlight_row();
}
}
this.drag_entered = false;
}
private void on_drag_data_get(Gdk.DragContext context,
Gtk.SelectionData selection_data,
uint info, uint time_) {
selection_data.set(
Gdk.Atom.intern_static_string(DND_ATOM), 8,
get_index().to_string().data
);
private Gdk.ContentProvider on_drag_source_prepare(Gtk.DragSource drag_source,
double x,
double y) {
GLib.Value val = GLib.Value(typeof(int));
val.set_int(get_index());
return new Gdk.ContentProvider.for_value(val);
}
private void on_drag_data_received(Gdk.DragContext context,
int x, int y,
Gtk.SelectionData selection_data,
uint info, uint time_) {
int drag_index = int.parse((string) selection_data.get_data());
private bool on_drop_target_drop(Gtk.DropTarget drop_target,
GLib.Value val,
double x,
double y) {
if (!val.holds(typeof(int))) {
warning("Can't deal with non-uint row value");
return false;
}
int drag_index = val.get_int();
Gtk.ListBox? parent = this.get_parent() as Gtk.ListBox;
if (parent != null) {
EditorRow? drag_row = parent.get_row_at_index(drag_index) as EditorRow;
if (drag_row != null && drag_row != this) {
drag_row.dropped(this);
return true;
}
}
return false;
}
}
@ -233,11 +201,10 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
this.label.halign = Gtk.Align.START;
this.label.valign = Gtk.Align.CENTER;
this.label.hexpand = true;
this.label.set_text(label);
this.label.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
this.label.set_line_wrap(true);
this.label.show();
this.layout.add(this.label);
this.label.label = label;
this.label.wrap_mode = Pango.WrapMode.WORD_CHAR;
this.label.wrap = true;
this.layout.append(this.label);
bool expand_label = true;
this.value = value;
@ -250,14 +217,13 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
}
Gtk.Label? vlabel = value as Gtk.Label;
if (vlabel != null) {
vlabel.set_line_wrap_mode(Pango.WrapMode.WORD_CHAR);
vlabel.set_line_wrap(true);
vlabel.wrap_mode = Pango.WrapMode.WORD_CHAR;
vlabel.wrap = true;
}
widget.halign = Gtk.Align.START;
widget.valign = Gtk.Align.CENTER;
widget.show();
this.layout.add(widget);
this.layout.append(widget);
}
this.label.hexpand = expand_label;
@ -265,9 +231,9 @@ internal class Accounts.LabelledEditorRow<PaneType,V> : EditorRow<PaneType> {
public void set_dim_label(bool is_dim) {
if (is_dim) {
this.label.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.label.add_css_class("dim-label");
} else {
this.label.get_style_context().remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.label.remove_css_class("dim-label");
}
}
@ -278,51 +244,11 @@ internal class Accounts.AddRow<PaneType> : EditorRow<PaneType> {
public AddRow() {
get_style_context().add_class("geary-add-row");
Gtk.Image add_icon = new Gtk.Image.from_icon_name(
"list-add-symbolic", Gtk.IconSize.BUTTON
);
add_css_class("geary-add-row");
var add_icon = new Gtk.Image.from_icon_name("list-add-symbolic");
add_icon.set_hexpand(true);
add_icon.show();
this.layout.add(add_icon);
}
}
internal class Accounts.ServiceProviderRow<PaneType> :
LabelledEditorRow<PaneType,Gtk.Label> {
public ServiceProviderRow(Geary.ServiceProvider provider,
string other_type_label) {
string? label = null;
switch (provider) {
case Geary.ServiceProvider.GMAIL:
label = _("Gmail");
break;
case Geary.ServiceProvider.OUTLOOK:
label = _("Outlook.com");
break;
case Geary.ServiceProvider.OTHER:
label = other_type_label;
break;
}
base(
// Translators: Label describes the service provider
// hosting the email account, e.g. Gmail, Yahoo, or some
// other generic IMAP service.
_("Service provider"),
new Gtk.Label(label)
);
// Can't change this, so deactivate and dim out
set_activatable(false);
this.value.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.layout.append(add_icon);
}
}
@ -390,7 +316,7 @@ private abstract class Accounts.ServiceRow<PaneType,V> : AccountRow<PaneType,V>
Gtk.Widget? widget = value as Gtk.Widget;
if (widget != null && !is_editable) {
if (widget is Gtk.Label) {
widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
widget.add_css_class("dim-label");
} else {
widget.set_sensitive(false);
}
@ -533,7 +459,6 @@ internal class Accounts.TlsComboBox : Gtk.ComboBox {
}
internal class Accounts.OutgoingAuthComboBox : Gtk.ComboBox {
@ -626,13 +551,12 @@ internal class Accounts.EditorPopover : Gtk.Popover {
public EditorPopover() {
get_style_context().add_class("geary-editor");
add_css_class("geary-editor");
this.layout.orientation = Gtk.Orientation.VERTICAL;
this.layout.set_row_spacing(6);
this.layout.set_column_spacing(12);
this.layout.show();
add(this.layout);
this.child = this.layout;
this.closed.connect_after(on_closed);
}
@ -641,39 +565,12 @@ internal class Accounts.EditorPopover : Gtk.Popover {
this.closed.disconnect(on_closed);
}
/** {@inheritDoc} */
public new void popup() {
// Work-around GTK+ issue #1138
Gtk.Widget target = get_relative_to();
Gtk.Allocation content_area;
target.get_allocation(out content_area);
Gtk.StyleContext style = target.get_style_context();
Gtk.StateFlags flags = style.get_state();
Gtk.Border margin = style.get_margin(flags);
content_area.x = margin.left;
content_area.y = margin.bottom;
content_area.width -= (content_area.x + margin.right);
content_area.height -= (content_area.y + margin.top);
set_pointing_to(content_area);
base.popup();
if (this.popup_focus != null) {
this.popup_focus.grab_focus();
}
}
public void add_labelled_row(string label, Gtk.Widget value) {
Gtk.Label label_widget = new Gtk.Label(label);
label_widget.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
label_widget.add_css_class("dim-label");
label_widget.halign = Gtk.Align.END;
label_widget.show();
this.layout.add(label_widget);
this.layout.attach_next_to(label_widget, null, Gtk.PositionType.BOTTOM);
this.layout.attach_next_to(value, label_widget, Gtk.PositionType.RIGHT);
}

View file

@ -10,12 +10,11 @@
* An account editor pane for editing server details for an account.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor_servers_pane.ui")]
internal class Accounts.EditorServersPane :
Gtk.Grid, EditorPane, AccountPane, CommandPane {
internal class Accounts.EditorServersPane : EditorPane, AccountPane, CommandPane {
/** {@inheritDoc} */
internal weak Accounts.Editor editor { get; set; }
internal override weak Accounts.Editor editor { get; set; }
/** {@inheritDoc} */
internal Geary.AccountInformation account { get ; protected set; }
@ -26,200 +25,67 @@ internal class Accounts.EditorServersPane :
}
/** {@inheritDoc} */
internal Gtk.Widget initial_widget {
get { return this.details_list; }
}
/** {@inheritDoc} */
internal bool is_operation_running {
internal override bool is_operation_running {
get { return !this.sensitive; }
protected set { update_operation_ui(value); }
}
/** {@inheritDoc} */
internal GLib.Cancellable? op_cancellable {
internal override Cancellable? op_cancellable {
get; protected set; default = new GLib.Cancellable();
}
private Geary.Engine engine;
// These are copies of the originals that can be updated before
// validating on apply, without breaking anything.
private Geary.ServiceInformation incoming_mutable;
private Geary.ServiceInformation outgoing_mutable;
private Gee.List<Components.Validator> validators =
new Gee.LinkedList<Components.Validator>();
public Components.ValidatorGroup validators { get; construct set; }
[GtkChild] private unowned Gtk.HeaderBar header;
[GtkChild] private unowned Adw.ActionRow account_provider_row;
[GtkChild] private unowned Adw.ActionRow service_provider_row;
[GtkChild] private unowned Adw.SwitchRow save_drafts_row;
[GtkChild] private unowned Adw.SwitchRow save_sent_row;
[GtkChild] private unowned Gtk.Grid pane_content;
[GtkChild] private unowned Gtk.Adjustment pane_adjustment;
[GtkChild] private unowned Gtk.ListBox details_list;
[GtkChild] private unowned Gtk.ListBox receiving_list;
[GtkChild] private unowned Gtk.ListBox sending_list;
[GtkChild] private unowned ServiceInformationWidget receiving_service_widget;
[GtkChild] private unowned ServiceInformationWidget sending_service_widget;
[GtkChild] private unowned Gtk.Button apply_button;
[GtkChild] private unowned Gtk.Spinner apply_spinner;
private SaveDraftsRow save_drafts;
private SaveSentRow save_sent;
private ServiceLoginRow incoming_login;
private ServicePasswordRow incoming_password;
static construct {
typeof(ServiceInformationWidget).ensure();
private ServiceOutgoingAuthRow outgoing_auth;
private ServiceLoginRow outgoing_login;
private ServicePasswordRow outgoing_password;
install_action("apply", null, (Gtk.WidgetActionActivateFunc) action_apply);
}
public EditorServersPane(Editor editor, Geary.AccountInformation account) {
this.editor = editor;
this.account = account;
this.engine = editor.application.engine;
this.incoming_mutable = new Geary.ServiceInformation.copy(account.incoming);
this.outgoing_mutable = new Geary.ServiceInformation.copy(account.outgoing);
this.pane_content.set_focus_vadjustment(this.pane_adjustment);
// Details
fill_in_account_provider(editor.accounts);
fill_in_service_provider();
this.details_list.set_header_func(Editor.seperator_headers);
// Only add an account provider if it is esoteric enough.
if (this.account.mediator is GoaMediator) {
this.details_list.add(
new AccountProviderRow(editor.accounts, this.account)
);
}
ServiceProviderRow<EditorServersPane> service_provider =
new ServiceProviderRow<EditorServersPane>(
this.account.service_provider,
this.account.service_label
);
service_provider.set_dim_label(true);
service_provider.activatable = false;
add_row(this.details_list, service_provider);
this.receiving_service_widget.service = account.incoming;
this.sending_service_widget.service = account.outgoing;
this.save_drafts = new SaveDraftsRow(
this.account, this.commands, this.op_cancellable
);
add_row(this.details_list, this.save_drafts);
bool services_editable = !(account.mediator is GoaMediator);
this.receiving_service_widget.set_editable(services_editable);
this.sending_service_widget.set_editable(services_editable);
this.save_sent = new SaveSentRow(
this.account, this.commands, this.op_cancellable
);
switch (account.service_provider) {
case OTHER:
add_row(this.details_list, this.save_sent);
break;
default:
// XXX GMail and Outlook auto-save sent mail so don't
// include save sent option, but we shouldn't be
// hard-coding visible rows like this
break;
}
//XXX GTK4 Make sure we update save_drafts and save_sent
// Receiving
this.receiving_list.set_header_func(Editor.seperator_headers);
add_row(
this.receiving_list,
new ServiceHostRow(
account,
this.incoming_mutable,
this.commands,
this.op_cancellable
)
);
add_row(
this.receiving_list,
new ServiceSecurityRow(
account,
this.incoming_mutable,
this.commands,
this.op_cancellable
)
);
this.incoming_password = new ServicePasswordRow(
account,
this.incoming_mutable,
this.commands,
this.op_cancellable
);
this.incoming_login = new ServiceLoginRow(
account,
this.incoming_mutable,
this.commands,
this.op_cancellable,
this.incoming_password
);
add_row(this.receiving_list, this.incoming_login);
add_row(this.receiving_list, this.incoming_password);
// Sending
this.sending_list.set_header_func(Editor.seperator_headers);
add_row(
this.sending_list,
new ServiceHostRow(
account,
this.outgoing_mutable,
this.commands,
this.op_cancellable
)
);
add_row(
this.sending_list,
new ServiceSecurityRow(
account,
this.outgoing_mutable,
this.commands,
this.op_cancellable
)
);
this.outgoing_auth = new ServiceOutgoingAuthRow(
account,
this.outgoing_mutable,
this.incoming_mutable,
this.commands,
this.op_cancellable
);
this.outgoing_auth.value.changed.connect(on_outgoing_auth_changed);
add_row(this.sending_list, this.outgoing_auth);
this.outgoing_password = new ServicePasswordRow(
account,
this.outgoing_mutable,
this.commands,
this.op_cancellable
);
this.outgoing_login = new ServiceLoginRow(
account,
this.outgoing_mutable,
this.commands,
this.op_cancellable,
this.outgoing_password
);
add_row(this.sending_list, this.outgoing_login);
add_row(this.sending_list, this.outgoing_password);
// XXX GMail and Outlook auto-save sent mail so don't include save sent
// option, but we shouldn't be hard-coding visible rows like this
this.save_sent_row.visible = (account.service_provider == OTHER);
// Misc plumbing
connect_account_signals();
connect_command_signals();
update_outgoing_auth();
}
~EditorServersPane() {
@ -227,9 +93,48 @@ internal class Accounts.EditorServersPane :
disconnect_command_signals();
}
/** {@inheritDoc} */
internal Gtk.HeaderBar get_header() {
return this.header;
private void fill_in_account_provider(Manager accounts) {
if (this.account.mediator is GoaMediator) {
this.account_provider_row.subtitle = _("GNOME Online Accounts");
var button = new Gtk.Button.from_icon_name("external-link-symbolic");
button.valign = Gtk.Align.CENTER;
button.clicked.connect((button) => {
if (accounts.is_goa_account(this.account)) {
accounts.show_goa_account.begin(
account, this.op_cancellable,
(obj, res) => {
try {
accounts.show_goa_account.end(res);
} catch (GLib.Error err) {
// XXX display an error to the user
debug(
"Failed to show GOA account \"%s\": %s",
account.id,
err.message
);
}
});
}
});
this.account_provider_row.add_suffix(button);
}
}
private void fill_in_service_provider() {
switch (this.account.service_provider) {
case Geary.ServiceProvider.GMAIL:
this.service_provider_row.subtitle = _("Gmail");
break;
case Geary.ServiceProvider.OUTLOOK:
this.service_provider_row.subtitle = _("Outlook.com");
break;
case Geary.ServiceProvider.OTHER:
this.service_provider_row.subtitle = this.account.service_label;
break;
}
}
/** {@inheritDoc} */
@ -238,11 +143,8 @@ internal class Accounts.EditorServersPane :
this.apply_button.set_sensitive(this.commands.can_undo);
}
private bool is_valid() {
return Geary.traverse(this.validators).all((v) => v.is_valid);
}
private async void save(GLib.Cancellable? cancellable) {
#if 0
this.is_operation_running = true;
// Only need to validate if a generic, local account since
@ -278,18 +180,19 @@ internal class Accounts.EditorServersPane :
this.account.changed();
}
this.editor.pop();
this.editor.pop_pane();
} else {
// Re-enable apply so that the same config can be re-tried
// in the face of transient errors, without having to
// change something to re-enable it
this.apply_button.set_sensitive(true);
this.apply_button.sensitive = true;
// Undo these manually since it would have been updated
// already by the command
this.account.save_drafts = this.save_drafts.initial_value;
this.account.save_sent = this.save_sent.initial_value;
}
#endif
}
private async bool validate(GLib.Cancellable? cancellable) {
@ -301,9 +204,12 @@ internal class Accounts.EditorServersPane :
string? message = null;
bool imap_valid = false;
try {
yield this.engine.validate_imap(
local_account, this.incoming_mutable, cancellable
local_account,
this.receiving_service_widget.service_mutable,
cancellable
);
imap_valid = true;
} catch (Geary.ImapError.UNAUTHENTICATED err) {
@ -331,8 +237,8 @@ internal class Accounts.EditorServersPane :
try {
yield this.engine.validate_smtp(
local_account,
this.outgoing_mutable,
this.incoming_mutable.credentials,
this.sending_service_widget.service_mutable,
this.receiving_service_widget.service_mutable.credentials,
cancellable
);
smtp_valid = true;
@ -341,7 +247,8 @@ internal class Accounts.EditorServersPane :
// There was an SMTP auth error, but IMAP already
// succeeded, so the user probably needs to
// specify custom creds here
this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM;
//XXX GTK4
// this.outgoing_auth.value.source = Geary.Credentials.Requirement.CUSTOM;
// Translators: In-app notification label
message = _("Check your sending login and password");
} catch (GLib.TlsError.BAD_CERTIFICATE err) {
@ -366,8 +273,8 @@ internal class Accounts.EditorServersPane :
debug("Validation complete, is valid: %s", is_valid.to_string());
if (!is_valid && message != null) {
this.editor.add_notification(
new Components.InAppNotification(
this.editor.add_toast(
new Adw.Toast(
// Translators: In-app notification label, the
// string substitution is a more detailed reason.
_("Account not updated: %s").printf(message)
@ -381,6 +288,8 @@ internal class Accounts.EditorServersPane :
private async bool update_service(Geary.ServiceInformation existing,
Geary.ServiceInformation copy,
GLib.Cancellable cancellable) {
return true;
#if 0
bool has_changed = !existing.equal_to(copy);
if (has_changed) {
try {
@ -410,40 +319,28 @@ internal class Accounts.EditorServersPane :
}
}
return has_changed;
}
private void add_row(Gtk.ListBox list, EditorRow<EditorServersPane> row) {
list.add(row);
ValidatingRow? validating = row as ValidatingRow;
if (validating != null) {
validating.changed.connect(on_validator_changed);
validating.validator.activated.connect_after(on_validator_activated);
this.validators.add(validating.validator);
}
}
private void update_outgoing_auth() {
this.outgoing_login.set_visible(
this.outgoing_auth.value.source == CUSTOM
);
#endif
}
private void update_operation_ui(bool is_running) {
this.apply_spinner.visible = is_running;
this.apply_spinner.active = is_running;
this.apply_button.sensitive = !is_running;
this.sensitive = !is_running;
}
private void on_validator_changed() {
this.apply_button.set_sensitive(is_valid());
}
// [GtkCallback]
// private void on_validators_changed(Components.ValidatorGroup validators,
// Components.Validator validator) {
// action_set_enabled("apply", validators.is_valid());
// }
private void on_validator_activated() {
if (is_valid()) {
this.apply_button.clicked();
}
}
// [GtkCallback]
// private void on_validators_activated(Components.ValidatorGroup validators,
// Components.Validator validator) {
// if (validators.is_valid()) {
// activate_action("apply", null);
// }
// }
private void on_untrusted_host(Geary.AccountInformation account,
Geary.ServiceInformation service,
@ -465,132 +362,39 @@ internal class Accounts.EditorServersPane :
});
}
//XXX GTK4 we don't have a cancel button anymore
#if 0
[GtkCallback]
private void on_cancel_button_clicked() {
if (this.is_operation_running) {
cancel_operation();
} else {
this.editor.pop();
this.editor.pop_pane();
}
}
#endif
[GtkCallback]
private void on_apply_button_clicked() {
private void action_apply(string action_name, Variant? param) {
this.save.begin(this.op_cancellable);
}
[GtkCallback]
private bool on_list_keynav_failed(Gtk.Widget widget,
Gtk.DirectionType direction) {
bool ret = Gdk.EVENT_PROPAGATE;
Gtk.Container? next = null;
if (direction == Gtk.DirectionType.DOWN) {
if (widget == this.details_list) {
next = this.receiving_list;
} else if (widget == this.receiving_list) {
next = this.sending_list;
}
} else if (direction == Gtk.DirectionType.UP) {
if (widget == this.sending_list) {
next = this.receiving_list;
} else if (widget == this.receiving_list) {
next = this.details_list;
}
}
if (next != null) {
next.child_focus(direction);
ret = Gdk.EVENT_STOP;
}
return ret;
}
private void on_outgoing_auth_changed() {
update_outgoing_auth();
}
[GtkCallback]
private void on_activate(Gtk.ListBoxRow row) {
Accounts.EditorRow<EditorServersPane> server_row =
row as Accounts.EditorRow<EditorServersPane>;
if (server_row != null) {
server_row.activated(this);
}
}
}
private struct Accounts.InitialConfiguration {
bool save_drafts;
bool save_sent;
}
private class Accounts.AccountProviderRow :
AccountRow<EditorServersPane,Gtk.Label> {
private Manager accounts;
public AccountProviderRow(Manager accounts,
Geary.AccountInformation account) {
base(
account,
// Translators: This label describes the program that
// created the account, e.g. an SSO service like GOA, or
// locally by Geary.
_("Account source"),
new Gtk.Label("")
);
this.accounts = accounts;
update();
}
public override void update() {
string? source = null;
bool enabled = false;
if (this.account.mediator is GoaMediator) {
source = _("GNOME Online Accounts");
enabled = true;
} else {
source = _("Geary");
}
this.value.set_text(source);
this.set_activatable(enabled);
Gtk.StyleContext style = this.value.get_style_context();
if (enabled) {
style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
} else {
style.add_class(Gtk.STYLE_CLASS_DIM_LABEL);
}
}
public override void activated(EditorServersPane pane) {
if (this.accounts.is_goa_account(this.account)) {
this.accounts.show_goa_account.begin(
account, pane.op_cancellable,
(obj, res) => {
try {
this.accounts.show_goa_account.end(res);
} catch (GLib.Error err) {
// XXX display an error to the user
debug(
"Failed to show GOA account \"%s\": %s",
account.id,
err.message
);
}
});
}
}
}
private class Accounts.SaveDraftsRow :
AccountRow<EditorServersPane,Gtk.Switch> {
#if 0
private class zccounts.SaveDraftsRow : Adw.SwitchRow {
public Geary.AccountInformation account { get; construct set; }
public bool value_changed {
get { return this.initial_value != this.value.state; }
}
public bool initial_value { get; private set; }
public bool initial_value { get; construct set; }
private Application.CommandStack commands;
private GLib.Cancellable? cancellable;
@ -599,25 +403,22 @@ private class Accounts.SaveDraftsRow :
public SaveDraftsRow(Geary.AccountInformation account,
Application.CommandStack commands,
GLib.Cancellable? cancellable) {
Gtk.Switch value = new Gtk.Switch();
base(
account,
// Translators: This label describes an account
// preference.
_("Save draft email on server"),
value
Object(
account: account,
initial_value: account.save_drafts
);
update();
this.commands = commands;
this.cancellable = cancellable;
this.activatable = false;
this.initial_value = this.account.save_drafts;
this.account.notify["save-drafts"].connect(on_account_changed);
this.value.notify["active"].connect(on_activate);
this.account.notify["save-drafts"].connect(update);
this.notify["active"].connect(on_activate);
update();
}
public override void update() {
this.value.state = this.account.save_drafts;
private void update() {
//XXX GTK4 I think we need to guard this with an if to not activate the
// switch again
this.active = this.account.save_drafts;
}
private void on_activate() {
@ -630,11 +431,6 @@ private class Accounts.SaveDraftsRow :
);
}
}
private void on_account_changed() {
update();
}
}
@ -697,10 +493,6 @@ private class Accounts.ServiceHostRow :
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
public Components.Validator validator {
get; protected set;
}
public bool has_changed {
get {
return this.value.text.strip() != get_entry_text();
@ -850,11 +642,6 @@ private class Accounts.ServiceSecurityRow :
private class Accounts.ServiceLoginRow :
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
public Components.Validator validator {
get; protected set;
}
public bool has_changed {
get {
return this.value.text.strip() != get_entry_text();
@ -936,10 +723,9 @@ private class Accounts.ServiceLoginRow :
string? label = null;
if (this.service.credentials != null) {
string method = "%s";
Gtk.StyleContext value_style = this.value.get_style_context();
switch (this.service.credentials.supported_method) {
case Geary.Credentials.Method.PASSWORD:
value_style.remove_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.value.remove_css_class("dim-label");
break;
case Geary.Credentials.Method.OAUTH2:
@ -951,7 +737,7 @@ private class Accounts.ServiceLoginRow :
// the service's login name.
method = _("%s using OAuth2");
value_style.add_class(Gtk.STYLE_CLASS_DIM_LABEL);
this.value.add_css_class("dim-label");
break;
}
@ -975,10 +761,6 @@ private class Accounts.ServicePasswordRow :
ServiceRow<EditorServersPane,Gtk.Entry>, ValidatingRow<EditorServersPane> {
public Components.Validator validator {
get; protected set;
}
public bool has_changed {
get {
return this.value.text.strip() != get_entry_text();
@ -1116,3 +898,4 @@ private class Accounts.ServiceOutgoingAuthRow :
}
}
#endif

View file

@ -15,7 +15,7 @@
* management, account management and other common code for the panes.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts_editor.ui")]
public class Accounts.Editor : Gtk.Dialog {
public class Accounts.Editor : Adw.Dialog {
private const ActionEntry[] EDIT_ACTIONS = {
@ -35,10 +35,7 @@ public class Accounts.Editor : Gtk.Dialog {
/** Returns the editor's associated client application instance. */
public new Application.Client application {
get { return (Application.Client) base.get_application(); }
set { base.set_application(value); }
}
public Application.Client application { get; private set; }
internal Manager accounts { get; private set; }
@ -48,49 +45,35 @@ public class Accounts.Editor : Gtk.Dialog {
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
[GtkChild] private unowned Gtk.Overlay notifications_pane;
[GtkChild] private unowned Adw.ToastOverlay toast_overlay;
[GtkChild] private unowned Gtk.Stack editor_panes;
[GtkChild] private unowned Adw.NavigationView view;
private EditorListPane editor_list_pane;
private Gee.LinkedList<EditorPane> editor_pane_stack =
new Gee.LinkedList<EditorPane>();
public Editor(Application.Client application, Gtk.Window parent) {
public Editor(Application.Client application) {
this.application = application;
this.transient_for = parent;
this.icon_name = Config.APP_ID;
this.accounts = application.controller.account_manager;
this.certificates = application.controller.certificate_manager;
// Can't set this in Glade 3.22.1 :(
this.get_content_area().border_width = 0;
this.accounts = application.controller.account_manager;
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
insert_action_group(Action.Edit.GROUP_NAME, this.edit_actions);
this.editor_list_pane = new EditorListPane(this);
push(this.editor_list_pane);
push_pane(this.editor_list_pane);
update_command_actions();
if (this.accounts.size > 1) {
this.default_height = 650;
this.default_width = 800;
} else {
// Welcome dialog
this.default_width = 600;
}
}
public override bool key_press_event(Gdk.EventKey event) {
[GtkCallback]
private bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType mod_state) {
bool ret = Gdk.EVENT_PROPAGATE;
// XXX GTK4 - we'll need to disable the esc behavio in adwnavigationview and then do it manually here
// Allow the user to use Esc, Back and Alt+arrow keys to
// navigate between panes. If a pane is executing a long
// running operation, only allow Esc and use it to cancel the
@ -98,51 +81,15 @@ public class Accounts.Editor : Gtk.Dialog {
EditorPane? current_pane = get_current_pane();
if (current_pane != null &&
current_pane != this.editor_list_pane) {
Gdk.ModifierType state = (
event.state & Gtk.accelerator_get_default_mod_mask()
);
bool is_ltr = (get_direction() == Gtk.TextDirection.LTR);
switch (event.keyval) {
case Gdk.Key.Escape:
if (keyval == Gdk.Key.Escape) {
if (current_pane.is_operation_running) {
current_pane.cancel_operation();
} else {
pop();
pop_pane();
}
ret = Gdk.EVENT_STOP;
break;
case Gdk.Key.Back:
if (!current_pane.is_operation_running) {
pop();
ret = Gdk.EVENT_STOP;
}
break;
case Gdk.Key.Left:
if (!current_pane.is_operation_running &&
state == Gdk.ModifierType.MOD1_MASK &&
is_ltr) {
pop();
ret = Gdk.EVENT_STOP;
}
break;
case Gdk.Key.Right:
if (!current_pane.is_operation_running &&
state == Gdk.ModifierType.MOD1_MASK &&
!is_ltr) {
pop();
ret = Gdk.EVENT_STOP;
}
break;
}
}
if (ret != Gdk.EVENT_STOP) {
ret = base.key_press_event(event);
}
return ret;
@ -151,41 +98,20 @@ public class Accounts.Editor : Gtk.Dialog {
/**
* Adds and shows a new pane in the editor.
*/
internal void push(EditorPane pane) {
// Since we keep old, already-popped panes around (see pop for
// details), when a new pane is pushed on they need to be
// truncated.
EditorPane current = get_current_pane();
int target_length = this.editor_pane_stack.index_of(current) + 1;
while (target_length < this.editor_pane_stack.size) {
EditorPane old = this.editor_pane_stack.remove_at(target_length);
this.editor_panes.remove(old);
}
// Now push the new pane on
this.editor_pane_stack.add(pane);
this.editor_panes.add(pane);
this.editor_panes.set_visible_child(pane);
internal void push_pane(EditorPane pane) {
this.view.push(pane);
}
/**
* Removes the current pane from the editor, showing the last one.
*/
internal void pop() {
// One can't simply remove old panes for the GTK stack since
// there won't be any transition between them - the old one
// will simply disappear. So we need to keep old, popped panes
// around until a new one is pushed on.
EditorPane current = get_current_pane();
int prev_index = this.editor_pane_stack.index_of(current) - 1;
EditorPane prev = this.editor_pane_stack.get(prev_index);
this.editor_panes.set_visible_child(prev);
internal bool pop_pane() {
return this.view.pop();
}
/** Displays an in-app notification in the dialog. */
internal void add_notification(Components.InAppNotification notification) {
this.notifications_pane.add_overlay(notification);
notification.show();
internal void add_toast(Adw.Toast toast) {
this.toast_overlay.add_toast(toast);
}
/**
@ -202,14 +128,14 @@ public class Accounts.Editor : Gtk.Dialog {
throws Application.CertificateManagerError {
try {
yield this.certificates.prompt_pin_certificate(
this, account, service, endpoint, true, cancellable
get_root() as Gtk.Window, account, service, endpoint, true, cancellable
);
} catch (Application.CertificateManagerError.UNTRUSTED err) {
throw err;
} catch (Application.CertificateManagerError.STORE_FAILED err) {
// XXX show error info bar rather than a notification?
add_notification(
new Components.InAppNotification(
add_toast(
new Adw.Toast(
// Translators: In-app notification label, when
// the app had a problem pinning an otherwise
// untrusted TLS certificate
@ -225,7 +151,7 @@ public class Accounts.Editor : Gtk.Dialog {
/** Removes an account from the editor. */
internal void remove_account(Geary.AccountInformation account) {
this.editor_panes.set_visible_child(this.editor_list_pane);
this.view.pop_to_page(this.editor_list_pane);
this.editor_list_pane.remove_account(account);
}
@ -244,7 +170,7 @@ public class Accounts.Editor : Gtk.Dialog {
}
private inline EditorPane? get_current_pane() {
return this.editor_panes.get_visible_child() as EditorPane;
return this.view.visible_page as EditorPane;
}
private inline GLib.SimpleAction get_action(string name) {
@ -264,51 +190,18 @@ public class Accounts.Editor : Gtk.Dialog {
pane.redo();
}
}
[GtkCallback]
private void on_pane_changed() {
EditorPane? visible = get_current_pane();
Gtk.Widget? header = null;
if (visible != null) {
// Do this in an idle callback since it's not 100%
// reliable to just call it here for some reason. :(
GLib.Idle.add(() => {
visible.initial_widget.grab_focus();
return GLib.Source.REMOVE;
});
header = visible.get_header();
}
set_titlebar(header);
update_command_actions();
}
}
// XXX I'd really like to make EditorPane an abstract class,
// AccountPane an abstract class extending that, and the four concrete
// panes extend those, but the GTK+ Builder XML template system
// requires a template class to designate its immediate parent
// class. I.e. if accounts-editor-list-pane.ui specifies GtkGrid as
// the parent of EditorListPane, then it much exactly be that and not
// an instance of EditorPane, even if that extends GtkGrid. As a
// result, both EditorPane and AccountPane must both be interfaces so
// that the concrete pane classes can derive from GtkGrid directly,
// and everything becomes horrible. See GTK+ Issue #1151:
// https://gitlab.gnome.org/GNOME/gtk/issues/1151
/**
* Base interface for panes that can be shown by the accounts editor.
*/
internal interface Accounts.EditorPane : Gtk.Grid {
internal abstract class Accounts.EditorPane : Adw.NavigationPage {
/** The editor displaying this pane. */
internal abstract weak Accounts.Editor editor { get; set; }
/** The editor displaying this pane. */
internal abstract Gtk.Widget initial_widget { get; }
/**
* Determines if a long running operation is being executed.
*
@ -327,9 +220,6 @@ internal interface Accounts.EditorPane : Gtk.Grid {
*/
internal abstract GLib.Cancellable? op_cancellable { get; protected set; }
/** The GTK header bar to display for this pane. */
internal abstract Gtk.HeaderBar get_header();
/**
* Cancels this pane's current operation, any.
*
@ -376,21 +266,13 @@ internal interface Accounts.AccountPane : EditorPane {
this.account.changed.disconnect(on_account_changed);
}
/**
* Called when an account has changed.
*
* By default, updates the editor's header subtitle.
*/
private void account_changed() {
private void on_account_changed() {
update_header();
}
private inline void update_header() {
get_header().subtitle = this.account.display_name;
}
private void on_account_changed() {
account_changed();
// XXX GTK4 - this was subtitle before, will need to make the title subtitle
this.title = this.account.display_name;
}
}

View file

@ -0,0 +1,128 @@
/*
* Copyright 2018-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 simple dialog that allows adding/editing Mailboxes (e.g. when configuring
* sender addresses).
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts-mailbox-editor-dialog.ui")]
internal class Accounts.MailboxEditorDialog : Adw.Dialog {
[GtkChild] private unowned Adw.EntryRow name_row;
[GtkChild] private unowned Adw.EntryRow address_row;
[GtkChild] private unowned Gtk.Button apply_button;
[GtkChild] private unowned Gtk.Button remove_button;
private Components.EmailValidator address_validator;
private bool changed = false;
/** The display name for the address */
public string display_name { get; construct set; default = ""; }
/** The raw email address */
public string address { get; construct set; default = ""; }
/** Fired if the user pressed "Add"/"Apply" with the new details */
public signal void apply(Geary.RFC822.MailboxAddress mailbox);
/** Fired if the user requested to remove the address */
public signal void remove();
static construct {
install_action("apply", null, (Gtk.WidgetActionActivateFunc) action_apply);
install_action("remove", null, (Gtk.WidgetActionActivateFunc) action_remove);
}
construct {
this.name_row.text = this.display_name;
this.address_row.text = this.address;
this.changed = false;
this.address_validator =
new Components.EmailValidator(this.address_row);
this.address_validator.changed.connect((validator) => {
action_set_enabled("add", this.changed && input_is_valid());
});
}
/**
* Creates a MailboxEditorDialog for creating a new mailbox.
* @param display_name A suggestion for the name
*/
public MailboxEditorDialog.for_new(string? display_name) {
Object(
display_name: display_name ?? "",
address: ""
);
// Cange "Apply" to "Add" in this case, since that matches better
this.apply_button.label = _("_Add");
// Can't remove an address that doesn't exist yet
action_set_enabled("remove", false);
}
public MailboxEditorDialog.for_existing(Geary.RFC822.MailboxAddress mailbox,
bool can_remove) {
Object(
display_name: mailbox.name ?? "",
address: mailbox.address
);
action_set_enabled("remove", can_remove);
}
[GtkCallback]
private void on_name_changed(Gtk.Editable editable) {
var new_name = this.name_row.text.strip();
if (new_name != this.display_name) {
this.display_name = new_name;
this.changed = true;
}
}
[GtkCallback]
private void on_address_changed(Gtk.Editable editable) {
this.address = this.address_row.text.strip();
var new_address = this.address_row.text.strip();
if (new_address != this.address) {
this.address = new_address;
this.changed = true;
}
}
[GtkCallback]
private void on_entry_activate() {
activate_action("add", null);
}
private bool input_is_valid() {
return this.address_validator.state == Components.Validator.Validity.INDETERMINATE
|| this.address_validator.is_valid;
}
private void action_apply(string action_name, Variant? param) {
if (!input_is_valid()) {
debug("Tried to add mailbox, but email was invalid");
return;
}
apply(
new Geary.RFC822.MailboxAddress(this.display_name, this.address)
);
}
private void action_remove(string action_name, Variant? param) {
remove();
}
}

View file

@ -0,0 +1,173 @@
/*
* Copyright 2016 Software Freedom Conservancy Inc.
* Copyright 2018-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 widget for editing a {@link Geary.ServiceInformation} object.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts-service-information-widget.ui")]
internal class Accounts.ServiceInformationWidget : Adw.PreferencesGroup {
public Geary.ServiceInformation service {
get { return this._service; }
set {
this._service = value;
this.service_mutable = new Geary.ServiceInformation.copy(value);
update_details();
value.notify.connect((obj, pspec) => { update_details(); });
}
}
private Geary.ServiceInformation _service;
// A copy of the original that can be without breaking the original
public Geary.ServiceInformation service_mutable { get ; private set; }
public Components.ValidatorGroup validators { get; construct set; }
[GtkChild] private unowned Adw.EntryRow host_row;
[GtkChild] private unowned TlsComboRow security_row;
[GtkChild] private unowned Adw.ComboRow credentials_requirement_row;
[GtkChild] private unowned Adw.EntryRow login_name_row;
[GtkChild] private unowned Adw.PasswordEntryRow password_row;
static construct {
typeof(TlsComboRow).ensure();
typeof(Components.ValidatorGroup).ensure();
typeof(Components.Validator).ensure();
}
/**
* Sets whether editing the information is possible
*/
public void set_editable(bool editable) {
this.sensitive = editable;
update_details();
}
private void update_details() {
update_host_row(this.host_row, this.service_mutable);
this.security_row.method = this.service_mutable.transport_security;
update_auth(this.service_mutable);
}
private void update_host_row(Adw.EntryRow row, Geary.ServiceInformation service) {
row.title = host_label_for_protocol(service.protocol);
row.text = service.host ?? "";
if (!Geary.String.is_empty(service.host)) {
// Only show the port if it not the appropriate default port
uint16 port = service.port;
if (port != service.get_default_port()) {
row.text = "%s:%d".printf(service.host, service.port);
}
}
}
private string host_label_for_protocol(Geary.Protocol protocol) {
switch (protocol) {
case Geary.Protocol.IMAP:
// Translators: This label describes the host name or IP
// address and port used by an account's IMAP service.
return _("IMAP Server");
case Geary.Protocol.SMTP:
// Translators: This label describes the host name or IP
// address and port used by an account's SMTP service.
return _("SMTP Server");
}
return _("Unknown Protocol");
}
private void update_login_name_row(Adw.EntryRow row,
Geary.ServiceInformation service) {
// Translators: Label used when no auth scheme is used
// by an account's IMAP or SMTP service.
row.text = _("None");
// If we have credentials, we can do better
if (service.credentials != null) {
switch (service.credentials.supported_method) {
case Geary.Credentials.Method.PASSWORD:
row.text = service.credentials.user;
break;
case Geary.Credentials.Method.OAUTH2:
// Add a suffix for OAuth2 auth so people know they
// shouldn't expect to be prompted for a password
// Translators: Label used when an account's IMAP or
// SMTP service uses OAuth2. The string replacement is
// the service's login name.
row.text = _("%s using OAuth2").printf(service.credentials.user ?? "");
break;
}
}
// If we rely on the credentials of the incoming server, notify the user of that
if (service.protocol == Geary.Protocol.SMTP &&
service.credentials_requirement ==
Geary.Credentials.Requirement.USE_INCOMING) {
row.text = _("Use receiving server login");
}
}
private void update_password_row(Adw.PasswordEntryRow row,
Geary.ServiceInformation service) {
if (service.credentials != null) {
row.text = service.credentials.token ?? "";
} else {
row.text = "";
}
// If we're not enabled, the "Show Password" button is insensitive too
// so just hide the row
if (!this.sensitive)
row.visible = false;
}
[GtkCallback]
private void on_host_row_changed(Gtk.Editable editable) {
}
private void update_auth(Geary.ServiceInformation service) {
bool is_smtp = (service.protocol == Geary.Protocol.SMTP);
this.credentials_requirement_row.visible = is_smtp;
if (is_smtp) {
this.credentials_requirement_row.selected = service.credentials_requirement;
bool needs_login =
(service.credentials_requirement == Geary.Credentials.Requirement.CUSTOM);
this.login_name_row.visible = needs_login;
this.password_row.visible = needs_login;
}
update_login_name_row(this.login_name_row, this.service_mutable);
update_password_row(this.password_row, this.service_mutable);
}
[GtkCallback]
private static string outgoing_auth_to_string(Adw.EnumListItem item,
Geary.Credentials.Requirement requirement) {
return requirement.to_string();
}
[GtkCallback]
private void on_validators_changed(Components.ValidatorGroup validators,
Components.Validator validator) {
//XXX what do we do here?
}
[GtkCallback]
private void on_validators_activated(Components.ValidatorGroup validators,
Components.Validator validator) {
//XXX what do we do here?
}
}

View file

@ -22,8 +22,9 @@ public class Accounts.SignatureWebView : Components.WebView {
public SignatureWebView(Application.Configuration config) {
base(config);
base(config, null);
this.user_content_manager.add_script(SignatureWebView.app_script);
add_css_class("geary-signature");
}
}

View file

@ -0,0 +1,55 @@
/*
* Copyright 2025 Niels De Graef <nielsdegraef@gmail.com>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
[GtkTemplate (ui = "/org/gnome/Geary/accounts-tls-combo-row.ui")]
internal class Accounts.TlsComboRow : Adw.ComboRow {
private const string INSECURE_ICON = "channel-insecure-symbolic";
private const string SECURE_ICON = "channel-secure-symbolic";
public Geary.TlsNegotiationMethod method {
get { return ((Adw.EnumListItem) this.selected_item).value; }
set { this.selected = value; }
}
[GtkCallback]
private void on_factory_setup(Gtk.SignalListItemFactory factory,
GLib.Object object) {
unowned var item = (Gtk.ListItem) object;
var image = new Gtk.Image();
var label = new Gtk.Label(null);
label.xalign = 1.0f;
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
box.append(image);
box.append(label);
item.child = box;
}
[GtkCallback]
private void on_factory_bind(Gtk.SignalListItemFactory factory,
GLib.Object object) {
unowned var item = (Gtk.ListItem) object;
unowned var enum_item = (Adw.EnumListItem) item.item;
var method = (Geary.TlsNegotiationMethod) enum_item.get_value();
unowned var box = (Gtk.Box) item.child;
unowned var image = (Gtk.Image) box.get_first_child();
if (method == Geary.TlsNegotiationMethod.NONE)
image.icon_name = "channel-insecure-symbolic";
else
image.icon_name = "channel-secure-symbolic";
unowned var label = (Gtk.Label) image.get_next_sibling();
label.label = method.to_string();
}
}

View file

@ -84,67 +84,70 @@ public class Application.AttachmentManager : GLib.Object {
* else false.
*/
public async bool save_buffer(string display_name,
Geary.Memory.Buffer buffer,
GLib.Cancellable? cancellable) {
Gtk.FileChooserNative dialog = new_save_chooser(SAVE);
dialog.set_current_name(display_name);
Geary.Memory.Buffer buffer,
GLib.Cancellable? cancellable) {
var dialog = new Gtk.FileDialog();
dialog.initial_name = display_name;
dialog.initial_folder = download_dir();
string? destination_uri = null;
if (dialog.run() == Gtk.ResponseType.ACCEPT) {
destination_uri = dialog.get_uri();
File? destination = null;
try {
destination = yield dialog.save(this.parent, cancellable);
} catch (Error err) {
//XXX GTK4 check if cancelled is accidentally caught here as well
warning("Couldn't select file to save attachment: %s", err.message);
return false;
}
dialog.destroy();
bool succeeded = false;
if (!Geary.String.is_empty_or_whitespace(destination_uri)) {
succeeded = yield check_and_write(
buffer, GLib.File.new_for_uri(destination_uri), cancellable
);
}
return succeeded;
return yield check_and_write(buffer, destination, cancellable);
}
private async bool save_all(Gee.Collection<Geary.Attachment> attachments,
GLib.Cancellable? cancellable) {
var dialog = new_save_chooser(SELECT_FOLDER);
string? destination_uri = null;
if (dialog.run() == Gtk.ResponseType.ACCEPT) {
destination_uri = dialog.get_uri();
var dialog = new Gtk.FileDialog();
dialog.initial_file = download_dir();
File? destination_dir = null;
try {
destination_dir = yield dialog.select_folder(this.parent, cancellable);
} catch (Error err) {
//XXX GTK4 check if cancelled is accidentally caught here as well
warning("Couldn't select folder for saving attachments: %s", err.message);
return false;
}
dialog.destroy();
if (destination_dir == null)
return false;
bool succeeded = false;
if (!Geary.String.is_empty_or_whitespace(destination_uri)) {
var destination_dir = GLib.File.new_for_uri(destination_uri);
foreach (Geary.Attachment attachment in attachments) {
GLib.File? destination = null;
try {
destination = destination_dir.get_child_for_display_name(
yield attachment.get_safe_file_name(
AttachmentManager.untitled_file_name
)
);
} catch (GLib.IOError.CANCELLED err) {
// Everything is going to fail from now on, so get
// out of here
succeeded = false;
break;
} catch (GLib.Error err) {
warning(
"Error determining file system name for \"%s\": %s",
attachment.file.get_uri(), err.message
);
handle_error(err);
}
var content = yield open_buffer(attachment, cancellable);
if (content != null &&
destination != null) {
succeeded &= yield check_and_write(
content, destination, cancellable
);
} else {
succeeded = false;
}
foreach (Geary.Attachment attachment in attachments) {
GLib.File? destination = null;
try {
destination = destination_dir.get_child_for_display_name(
yield attachment.get_safe_file_name(
AttachmentManager.untitled_file_name
)
);
} catch (GLib.IOError.CANCELLED err) {
// Everything is going to fail from now on, so get
// out of here
succeeded = false;
break;
} catch (GLib.Error err) {
warning(
"Error determining file system name for \"%s\": %s",
attachment.file.get_uri(), err.message
);
handle_error(err);
}
var content = yield open_buffer(attachment, cancellable);
if (content != null &&
destination != null) {
succeeded &= yield check_and_write(
content, destination, cancellable
);
} else {
succeeded = false;
}
}
return succeeded;
@ -229,14 +232,18 @@ public class Application.AttachmentManager : GLib.Object {
"The file already exists in “%s”. Replacing it will overwrite its contents."
).printf(parent_name);
ConfirmationDialog dialog = new ConfirmationDialog(
this.parent,
primary,
secondary,
_("_Replace"),
"destructive-action"
var dialog = new Adw.AlertDialog(primary, secondary);
dialog.add_responses(
"replace", _("_Replace"),
"cancel", _("_Cancel"),
null
);
return (dialog.run() == Gtk.ResponseType.OK);
dialog.default_response = "cancel";
dialog.close_response = "cancel";
dialog.set_response_appearance("replace", Adw.ResponseAppearance.DESTRUCTIVE);
string response = yield dialog.choose(this.parent, cancellable);
return (response == "replace");
}
private async void write_buffer_to_file(Geary.Memory.Buffer buffer,
@ -263,20 +270,11 @@ public class Application.AttachmentManager : GLib.Object {
}
}
private inline Gtk.FileChooserNative new_save_chooser(Gtk.FileChooserAction action) {
Gtk.FileChooserNative dialog = new Gtk.FileChooserNative(
null,
this.parent,
action,
Stock._SAVE,
Stock._CANCEL
);
private File? download_dir() {
var download_dir = GLib.Environment.get_user_special_dir(DOWNLOAD);
if (!Geary.String.is_empty_or_whitespace(download_dir)) {
dialog.set_current_folder(download_dir);
}
dialog.set_local_only(false);
return dialog;
if (Geary.String.is_empty_or_whitespace(download_dir))
return null;
return File.new_for_path(download_dir);
}
private inline void handle_error(GLib.Error error) {

View file

@ -118,11 +118,11 @@ public class Application.CertificateManager : GLib.Object {
GLib.Cancellable? cancellable)
throws CertificateManagerError {
CertificateWarningDialog dialog = new CertificateWarningDialog(
parent, account, service, endpoint, is_validation
account, service, endpoint, is_validation
);
bool save = false;
switch (dialog.run()) {
switch (yield dialog.run(parent)) {
case CertificateWarningDialog.Result.TRUST:
// noop
break;

View file

@ -10,7 +10,7 @@
/**
* The client application's main point of entry and desktop integration.
*/
public class Application.Client : Gtk.Application {
public class Application.Client : Adw.Application {
public const string NAME = "Geary" + Config.NAME_SUFFIX;
public const string RESOURCE_BASE_PATH = "/org/gnome/Geary";
@ -222,7 +222,6 @@ public class Application.Client : Gtk.Application {
private File exec_dir;
private string binary;
private Gtk.CssProvider single_key_shortcuts = new Gtk.CssProvider();
private GLib.Cancellable controller_cancellable = new GLib.Cancellable();
private Components.Inspector? inspector = null;
private Geary.Nonblocking.Mutex controller_mutex = new Geary.Nonblocking.Mutex();
@ -348,9 +347,6 @@ public class Application.Client : Gtk.Application {
// Calls Gtk.init(), amongst other things
base.startup();
Hdy.init();
Hdy.StyleManager.get_default().set_color_scheme(
Hdy.ColorScheme.PREFER_LIGHT);
this.engine = new Geary.Engine(get_resource_directory());
this.config = new Configuration(SCHEMA_ID);
@ -378,27 +374,21 @@ public class Application.Client : Gtk.Application {
add_edit_accelerators(Action.Edit.REDO, { "<Ctrl><Shift>Z" });
add_edit_accelerators(Action.Edit.UNDO, { "<Ctrl>Z" });
// Load Geary GTK CSS
var provider = new Gtk.CssProvider();
Gtk.StyleContext.add_provider_for_screen(
Gdk.Display.get_default().get_default_screen(),
provider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
load_css(provider,
"resource:///org/gnome/Geary/geary.css");
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
#if 0
// Load Geary CSS for single key shortcuts
load_css(this.single_key_shortcuts,
"resource:///org/gnome/Geary/single-key-shortcuts.css");
update_single_key_shortcuts();
this.config.notify[Configuration.SINGLE_KEY_SHORTCUTS].connect(
on_single_key_shortcuts_toggled
);
#endif
MainWindow.add_accelerators(this);
Composer.Editor.add_accelerators(this);
Composer.Widget.add_accelerators(this);
Components.Inspector.add_accelerators(this);
Components.PreferencesWindow.add_accelerators(this);
Dialogs.ProblemDetailsDialog.add_accelerators(this);
// Manually place a hold on the application otherwise the
@ -432,7 +422,7 @@ public class Application.Client : Gtk.Application {
// thing down if it takes too long to complete
int64 start_usec = get_monotonic_time();
while (!controller_closed) {
Gtk.main_iteration();
MainContext.default().iteration(false);
int64 delta_usec = get_monotonic_time() - start_usec;
if (delta_usec >= FORCE_SHUTDOWN_USEC) {
@ -553,12 +543,11 @@ public class Application.Client : Gtk.Application {
public async void show_accounts() {
yield this.present();
Accounts.Editor editor = new Accounts.Editor(
this, get_active_main_window()
);
editor.run();
editor.destroy();
this.controller.expunge_accounts.begin();
Accounts.Editor editor = new Accounts.Editor(this);
editor.present(get_active_main_window());
editor.closed.connect((editor) => {
this.controller.expunge_accounts.begin();
});
}
/**
@ -667,9 +656,10 @@ public class Application.Client : Gtk.Application {
if (this.inspector == null) {
this.inspector = new Components.Inspector(this);
this.inspector.destroy.connect(() => {
this.inspector = null;
});
this.inspector.close_request.connect(() => {
this.inspector = null;
return true;
});
// Create a new window group for the inspector so it is
// not affected by the app's modal dialogs
@ -685,11 +675,11 @@ public class Application.Client : Gtk.Application {
public async void show_preferences() {
yield this.present();
Components.PreferencesWindow prefs = new Components.PreferencesWindow(
get_active_main_window(),
var prefs = new Components.PreferencesDialog(
this,
this.controller.plugins
);
prefs.show();
prefs.present(get_active_main_window());
}
public async void new_composer(Geary.RFC822.MailboxAddress? to = null) {
@ -820,10 +810,9 @@ public class Application.Client : Gtk.Application {
uri_ = "http://" + uri;
}
var launcher = new Gtk.UriLauncher(uri_);
try {
Gtk.show_uri_on_window(
get_active_window(), uri_, Gdk.CURRENT_TIME
);
yield launcher.launch(get_active_window(), null);
} catch (GLib.Error err) {
this.controller.report_problem(new Geary.ProblemReport(err));
}
@ -837,8 +826,10 @@ public class Application.Client : Gtk.Application {
* prompted about and if cancelled, will cancel shut-down here.
*/
public new void quit() {
if (this.controller == null ||
this.controller.check_open_composers()) {
//XXX GTK4 this is now async, need to figure out how to do this
// if (this.controller == null ||
// this.controller.check_open_composers()) {
if (this.controller == null) {
this.last_active_main_window = null;
base.quit();
}
@ -908,7 +899,9 @@ public class Application.Client : Gtk.Application {
private MainWindow new_main_window(bool select_first_inbox) {
MainWindow window = new MainWindow(this);
this.controller.register_window(window);
window.focus_in_event.connect(on_main_window_focus_in);
Gtk.EventControllerFocus focus_controller = new Gtk.EventControllerFocus();
focus_controller.enter.connect(on_main_window_focus_enter);
((Gtk.Widget) window).add_controller(focus_controller);
if (select_first_inbox) {
if (!window.select_first_inbox(true)) {
// The first inbox wasn't selected, so the account is
@ -958,11 +951,10 @@ public class Application.Client : Gtk.Application {
open_failed = true;
warning("Error creating controller: %s", err.message);
var dialog = new Dialogs.ProblemDetailsDialog(
null,
this,
new Geary.ProblemReport(err)
);
dialog.show();
dialog.present(null);
}
if (mutex_token != Geary.Nonblocking.Mutex.INVALID_TOKEN) {
@ -1100,20 +1092,23 @@ public class Application.Client : Gtk.Application {
set_accels_for_action("app." + action, accelerators);
}
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
#if 0
private void update_single_key_shortcuts() {
if (this.config.single_key_shortcuts) {
Gtk.StyleContext.add_provider_for_screen(
Gdk.Display.get_default().get_default_screen(),
Gtk.StyleContext.add_provider_for_display(
Gdk.Display.get_default(),
this.single_key_shortcuts,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
);
} else {
Gtk.StyleContext.remove_provider_for_screen(
Gdk.Display.get_default().get_default_screen(),
Gtk.StyleContext.remove_provider_for_display(
Gdk.Display.get_default(),
this.single_key_shortcuts
);
}
}
#endif
private void load_css(Gtk.CssProvider provider, string resource_uri) {
provider.parsing_error.connect(on_css_parse_error);
@ -1197,7 +1192,8 @@ public class Application.Client : Gtk.Application {
private void on_activate_help() {
try {
if (this.is_installed) {
this.show_uri.begin("help:geary");
var launcher = new Gtk.UriLauncher("help:geary");
launcher.launch.begin(get_active_window(), null);
} else {
Pid pid;
File exec_dir = this.exec_dir;
@ -1217,17 +1213,10 @@ public class Application.Client : Gtk.Application {
}
} catch (Error error) {
debug("Error showing help: %s", error.message);
Gtk.Dialog dialog = new Gtk.Dialog.with_buttons(
"Error",
get_active_window(),
Gtk.DialogFlags.DESTROY_WITH_PARENT,
Stock._CLOSE, Gtk.ResponseType.CLOSE, null);
dialog.response.connect(() => { dialog.destroy(); });
dialog.get_content_area().add(
new Gtk.Label("Error showing help: %s".printf(error.message))
Adw.AlertDialog dialog = new Adw.AlertDialog("Error",
"Error showing help: %s".printf(error.message)
);
dialog.show_all();
dialog.run();
dialog.present(get_active_window());
}
}
@ -1243,13 +1232,11 @@ public class Application.Client : Gtk.Application {
}
}
private bool on_main_window_focus_in(Gtk.Widget widget,
Gdk.EventFocus event) {
MainWindow? main = widget as MainWindow;
private void on_main_window_focus_enter(Gtk.EventControllerFocus focus_controller) {
MainWindow? main = focus_controller.get_widget() as MainWindow;
if (main != null) {
this.last_active_main_window = main;
}
return Gdk.EVENT_PROPAGATE;
}
private void on_window_removed(Gtk.Window window) {
@ -1271,22 +1258,25 @@ public class Application.Client : Gtk.Application {
}
}
//XXX GTK4 key shortcut themes aren't supported yet: https://gitlab.gnome.org/GNOME/gtk/-/issues/1669#note_1735942
#if 0
private void on_single_key_shortcuts_toggled() {
update_single_key_shortcuts();
}
#endif
private void on_css_parse_error(Gtk.CssSection section, GLib.Error error) {
uint start = section.get_start_line();
uint end = section.get_end_line();
if (start == end) {
private void on_css_parse_error(Gtk.CssProvider provider, Gtk.CssSection section, GLib.Error error) {
var start = section.get_start_location();
var end = section.get_end_location();
if (start.lines == end.lines) {
warning(
"Error parsing %s:%u: %s",
section.get_file().get_uri(), start, error.message
"Error parsing %s:%"+size_t.FORMAT+": %s",
section.get_file().get_uri(), start.lines, error.message
);
} else {
warning(
"Error parsing %s:%u-%u: %s",
section.get_file().get_uri(), start, end, error.message
"Error parsing %s:%"+size_t.FORMAT+"-%"+size_t.FORMAT+": %s",
section.get_file().get_uri(), start.lines, end.lines, error.message
);
}
}

View file

@ -126,8 +126,8 @@ public class Application.ContactStore : Geary.BaseObject {
Contact result = yield get_contact(
individual, null, cancellable
);
foreach (Geary.RFC822.MailboxAddress mailbox
in result.email_addresses) {
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
seen.add(to_cache_key(mailbox.address));
}
results.add(result);
@ -140,8 +140,8 @@ public class Application.ContactStore : Geary.BaseObject {
Contact result = yield get_contact(
individual, null, cancellable
);
foreach (Geary.RFC822.MailboxAddress mailbox
in result.email_addresses) {
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
seen.add(to_cache_key(mailbox.address));
}
results.add(result);
@ -166,8 +166,8 @@ public class Application.ContactStore : Geary.BaseObject {
Contact result = yield load(
contact.get_rfc822_address(), cancellable
);
foreach (Geary.RFC822.MailboxAddress mailbox
in result.email_addresses) {
for (uint i = 0; i < result.email_addresses.get_n_items(); i++) {
var mailbox = (Geary.RFC822.MailboxAddress) result.email_addresses.get_item(i);
seen.add(to_cache_key(mailbox.address));
}
results.add(result);

View file

@ -55,24 +55,23 @@ public class Application.Contact : Geary.BaseObject {
public bool load_remote_resources { get; private set; }
/** The set of email addresses associated with this contact. */
public Gee.Collection<Geary.RFC822.MailboxAddress> email_addresses {
public GLib.ListModel email_addresses {
get {
Gee.Collection<Geary.RFC822.MailboxAddress>? addrs =
this._email_addresses;
if (addrs == null) {
addrs = new Gee.LinkedList<Geary.RFC822.MailboxAddress>();
if (this._email_addresses == null) {
var addrs = new GLib.ListStore(typeof(Geary.RFC822.MailboxAddress));
foreach (Folks.EmailFieldDetails email in
this.individual.email_addresses) {
addrs.add(new Geary.RFC822.MailboxAddress(
this.display_name, email.value
));
var mailbox_addr = new Geary.RFC822.MailboxAddress(
this.display_name, email.value
);
addrs.append(mailbox_addr);
}
this._email_addresses = addrs;
}
return this._email_addresses;
}
}
private Gee.Collection<Geary.RFC822.MailboxAddress>? _email_addresses = null;
private GLib.ListModel? _email_addresses = null;
/** Fired when the contact has changed in some way. */
@ -142,14 +141,15 @@ public class Application.Contact : Geary.BaseObject {
}
if (this.display_name != other.display_name ||
this.email_addresses.size != other.email_addresses.size) {
this.email_addresses.get_n_items() != other.email_addresses.get_n_items()) {
return false;
}
foreach (Geary.RFC822.MailboxAddress this_addr in this.email_addresses) {
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
var this_addr = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
bool found = false;
foreach (Geary.RFC822.MailboxAddress other_addr
in other.email_addresses) {
for (uint j = 0; j < other.email_addresses.get_n_items(); j++) {
var other_addr = (Geary.RFC822.MailboxAddress) other.email_addresses.get_item(j);
if (this_addr.equal_to(other_addr)) {
found = true;
break;
@ -187,8 +187,8 @@ public class Application.Contact : Geary.BaseObject {
Gee.Set<Folks.EmailFieldDetails> email_addresses =
new Gee.HashSet<Folks.EmailFieldDetails>();
GLib.Value email_value = GLib.Value(typeof(Gee.Set));
foreach (Geary.RFC822.MailboxAddress addr
in this.email_addresses) {
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
var addr = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
email_addresses.add(
new Folks.EmailFieldDetails(addr.address)
);
@ -279,9 +279,9 @@ public class Application.Contact : Geary.BaseObject {
throws GLib.Error {
ContactStore? store = this.store;
if (store != null) {
Gee.Collection<Geary.Contact> contacts =
new Gee.LinkedList<Geary.Contact>();
foreach (Geary.RFC822.MailboxAddress mailbox in this.email_addresses) {
var contacts = new Gee.LinkedList<Geary.Contact>();
for (uint i = 0; i < this.email_addresses.get_n_items(); i++) {
var mailbox = (Geary.RFC822.MailboxAddress) this.email_addresses.get_item(i);
Geary.Contact? contact = yield store.lookup_engine_contact(
mailbox, cancellable
);
@ -347,7 +347,9 @@ public class Application.Contact : Geary.BaseObject {
private void update_from_engine() {
Geary.RFC822.MailboxAddress mailbox = this.engine.get_rfc822_address();
this._email_addresses = Geary.Collection.single(mailbox);
var addrs = new GLib.ListStore(typeof(Geary.RFC822.MailboxAddress));
addrs.append(mailbox);
this._email_addresses = addrs;
this.load_remote_resources = this.engine.flags.always_load_remote_images();
}

View file

@ -125,21 +125,13 @@ internal class Application.Controller :
GLib.File config_dir = application.get_home_config_directory();
GLib.File data_dir = application.get_home_data_directory();
// This initializes the IconFactory, important to do before
// the actions are created (as they refer to some of Geary's
// custom icons)
IconFactory.init(application.get_resource_directory());
// Create DB upgrade dialog.
this.database_manager = new DatabaseManager(application);
// Initialise WebKit and WebViews
Components.WebView.init_web_context(
this.application.config,
this.application.get_web_extensions_dir(),
this.application.get_home_cache_directory().get_child(
"web-resources"
)
this.application.get_web_extensions_dir()
);
Components.WebView.load_resources(config_dir);
Composer.WebView.load_resources();
@ -406,7 +398,7 @@ internal class Application.Controller :
// current window that is either a reply/forward for that
// message, or there is a quote to insert into it.
foreach (var existing in this.composer_widgets) {
if (existing.get_toplevel() == main &&
if (existing.get_root() == main &&
(existing.current_mode == INLINE ||
existing.current_mode == INLINE_COMPACT) &&
existing.sender_context == send_context &&
@ -957,6 +949,10 @@ internal class Application.Controller :
}
}
internal GLib.File get_web_cache_dir() {
return this.application.get_home_cache_directory().get_child("web-resources");
}
/** Expunges removed accounts while the controller remains open. */
internal async void expunge_accounts() {
try {
@ -1189,25 +1185,27 @@ internal class Application.Controller :
context.authentication_prompting = false;
} else {
context.authentication_prompting = true;
PasswordDialog password_dialog = new PasswordDialog(
var password_dialog = new PasswordDialog(
this.application.get_active_window(),
account,
service,
credentials
);
if (password_dialog.run()) {
bool remember;
var password = yield password_dialog.get_password(
this.application.get_active_window(),
out remember
);
if (password != null) {
// The update the credentials for the service that the
// credentials actually came from
Geary.ServiceInformation creds_service =
(credentials == account.incoming.credentials)
? account.incoming
: account.outgoing;
creds_service.credentials = credentials.copy_with_token(
password_dialog.password
);
creds_service.credentials = credentials.copy_with_token(password);
// Update the remember password pref if changed
bool remember = password_dialog.remember_password;
if (creds_service.remember_password != remember) {
creds_service.remember_password = remember;
account.changed();
@ -1301,37 +1299,35 @@ internal class Application.Controller :
// Returns true if the caller should try opening the account again
private async bool account_database_error_async(Geary.Account account) {
bool retry = true;
// give the user two options: reset the Account local store, or exit Geary. A third
// could be done to leave the Account in an unopened state, but we don't currently
// have provisions for that.
QuestionDialog dialog = new QuestionDialog(
this.application.get_active_main_window(),
var dialog = new Adw.AlertDialog(
_("Unable to open the database for %s").printf(account.information.id),
_("There was an error opening the local mail database for this account. This is possibly due to corruption of the database file in this directory:\n\n%s\n\nGeary can rebuild the database and re-synchronize with the server or exit.\n\nRebuilding the database will destroy all local email and its attachments. <b>The mail on the your server will not be affected.</b>")
.printf(account.information.data_dir.get_path()),
_("_Rebuild"), _("E_xit"));
dialog.use_secondary_markup(true);
switch (dialog.run()) {
case Gtk.ResponseType.OK:
// don't use Cancellable because we don't want to interrupt this process
try {
yield account.rebuild_async();
} catch (Error err) {
ErrorDialog errdialog = new ErrorDialog(
this.application.get_active_main_window(),
_("Unable to rebuild database for “%s”").printf(account.information.id),
_("Error during rebuild:\n\n%s").printf(err.message));
errdialog.run();
null);
dialog.format_body_markup(
_("There was an error opening the local mail database for this account. This is possibly due to corruption of the database file in this directory:\n\n%s\n\nGeary can rebuild the database and re-synchronize with the server or exit.\n\nRebuilding the database will destroy all local email and its attachments. <b>The mail on the your server will not be affected.</b>"),
account.information.data_dir.get_path());
dialog.add_response("exit", _("E_xit"));
dialog.add_response("rebuild", _("_Rebuild"));
retry = false;
}
break;
string response = yield dialog.choose(this.application.get_active_main_window(), null);
if (response != "rebuild") {
return false;
}
default:
retry = false;
break;
// don't use Cancellable because we don't want to interrupt this process
bool retry = true;
try {
yield account.rebuild_async();
} catch (Error err) {
var errdialog = new Adw.AlertDialog(
_("Unable to rebuild database for “%s”").printf(account.information.id),
_("Error during rebuild:\n\n%s").printf(err.message));
errdialog.add_css_class("error");
errdialog.present(this.application.get_active_main_window());
retry = false;
}
return retry;
@ -1454,10 +1450,11 @@ internal class Application.Controller :
composer.present();
}
internal bool check_open_composers() {
internal async bool check_open_composers() {
var do_quit = true;
foreach (var composer in this.composer_widgets) {
if (composer.conditional_close(true, true) == CANCELLED) {
var status = yield composer.conditional_close(true, true);
if (status == CANCELLED) {
do_quit = false;
break;
}
@ -1488,12 +1485,10 @@ internal class Application.Controller :
Geary.Email sent) {
/// Translators: The label for an in-app notification.
string message = _("Email sent");
Components.InAppNotification notification =
new Components.InAppNotification(
message, application.config.brief_notification_duration
);
var toast = new Adw.Toast(message);
toast.timeout = application.config.brief_notification_duration;
foreach (MainWindow window in this.application.get_main_windows()) {
window.add_notification(notification);
window.add_toast(toast);
}
AccountContext? context = this.accounts.get(service.account);

View file

@ -63,16 +63,10 @@ internal class Application.DatabaseManager : Geary.BaseObject {
window.sensitive = false;
}
var spinner = new Gtk.Spinner();
spinner.set_size_request(45, 45);
spinner.start();
var grid = new Gtk.Grid();
grid.orientation = VERTICAL;
grid.add(spinner);
var box = new Gtk.Box(Gtk.Orientation.VERTICAL, 6);
box.append(new Adw.Spinner());
/// Translators: Label for account database upgrade dialog
grid.add(new Gtk.Label(_("Account update in progress")));
grid.show_all();
box.append(new Gtk.Label(_("Account update in progress")));
this.dialog = new Gtk.Dialog.with_buttons(
/// Translators: Window title for account database upgrade
@ -81,15 +75,15 @@ internal class Application.DatabaseManager : Geary.BaseObject {
this.application.get_active_main_window(),
MODAL
);
this.dialog.get_style_context().add_class("geary-upgrade");
this.dialog.get_content_area().add(grid);
this.dialog.add_css_class("geary-upgrade");
this.dialog.get_content_area().append(box);
this.dialog.deletable = false;
this.dialog.delete_event.connect(this.on_delete_event);
this.dialog.close_request.connect(on_close_request);
this.dialog.close.connect(this.on_close);
this.dialog.show();
this.dialog.present();
}
private bool on_delete_event() {
private bool on_close_request() {
// Don't allow window to close until we're finished.
return !this.monitor.is_in_progress;
}

File diff suppressed because it is too large Load diff

View file

@ -115,9 +115,9 @@ internal class Application.NotificationPluginContext :
folder != null &&
this.folder_information.has_key(folder) && (
window == null ||
!window.has_toplevel_focus ||
!window.is_active ||
window.selected_folder != folder ||
window.conversation_list_view.vadjustment.value > 0.0
window.conversation_list_view.scrolled_window.vadjustment.value > 0.0
)
);
}

View file

@ -258,7 +258,7 @@ public class Application.PluginManager : GLib.Object {
Geary.Folder? target = this.globals.folders.to_engine_folder(folder);
if (target != null) {
if (!main.prompt_empty_folder(target.used_as)) {
if (!yield main.prompt_empty_folder(target.used_as)) {
throw new Plugin.Error.PERMISSION_DENIED(
"Permission not granted"
);
@ -419,7 +419,8 @@ public class Application.PluginManager : GLib.Object {
public void insert_text(string plain_text) {
var entry = this.backing.focused_input_widget as Gtk.Entry;
if (entry != null) {
entry.insert_at_cursor(plain_text);
int position = entry.get_position();
entry.insert_text(plain_text, plain_text.length, ref position);
} else {
this.backing.editor.body.insert_text(plain_text);
}
@ -477,7 +478,7 @@ public class Application.PluginManager : GLib.Object {
centre = new Gtk.Box(HORIZONTAL, 0);
this.action_bar.set_center_widget(centre);
}
centre.add(widget);
centre.append(widget);
break;
case END:
@ -487,7 +488,6 @@ public class Application.PluginManager : GLib.Object {
}
}
this.action_bar.show_all();
this.backing.editor.add_action_bar(this.action_bar);
}
@ -513,26 +513,24 @@ public class Application.PluginManager : GLib.Object {
if (item_type == typeof(Plugin.ActionBar.MenuItem)) {
var menu_item = item as Plugin.ActionBar.MenuItem;
var label = new Gtk.Box(HORIZONTAL, 6);
label.add(new Gtk.Label(menu_item.label));
label.add(new Gtk.Image.from_icon_name(
"pan-up-symbolic", Gtk.IconSize.BUTTON
));
var button = new Gtk.MenuButton();
button.direction = Gtk.ArrowType.UP;
button.use_popover = true;
button.menu_model = menu_item.menu;
button.add(label);
var content = new Adw.ButtonContent();
content.label = menu_item.label;
content.icon_name = "pan-up-symbolic";
button.child = content;
return button;
}
if (item_type == typeof(Plugin.ActionBar.GroupItem)) {
var group_items = item as Plugin.ActionBar.GroupItem;
var box = new Gtk.Box(HORIZONTAL, 0);
box.get_style_context().add_class(Gtk.STYLE_CLASS_LINKED);
box.add_css_class("linked");
foreach (var group_item in group_items.get_items()) {
box.add(widget_for_item(group_item));
box.append(widget_for_item(group_item));
}
return box;
}

View file

@ -12,7 +12,7 @@
* shown will differ slightly based on which is selected.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-pane.ui")]
public class Components.AttachmentPane : Gtk.Grid {
public class Components.AttachmentPane : Gtk.Box {
private const string GROUP_NAME = "cap";
@ -36,24 +36,6 @@ public class Components.AttachmentPane : Gtk.Grid {
{ ACTION_SELECT_ALL, on_select_all },
};
// This exists purely to be able to set key bindings on it.
private class FlowBox : Gtk.FlowBox {
/** Keyboard action to open the currently selected attachments. */
[Signal (action=true)]
public signal void open_attachments();
/** Keyboard action to save the currently selected attachments. */
[Signal (action=true)]
public signal void save_attachments();
/** Keyboard action to remove the currently selected attachments. */
[Signal (action=true)]
public signal void remove_attachments();
}
// Displays an attachment's icon and details
[GtkTemplate (ui = "/org/gnome/Geary/components-attachment-view.ui")]
private class View : Gtk.Grid {
@ -112,7 +94,7 @@ public class Components.AttachmentPane : Gtk.Grid {
return;
}
Gdk.Pixbuf? pixbuf = null;
Gdk.Paintable? paintable = null;
// XXX We need to hook up to GtkWidget::style-set and
// reload the icon when the theme changes.
@ -131,26 +113,20 @@ public class Components.AttachmentPane : Gtk.Grid {
Priority.DEFAULT,
load_cancelled
);
pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
stream, preview_size, preview_size, true, load_cancelled
);
pixbuf = pixbuf.apply_embedded_orientation();
paintable = Gdk.Texture.for_pixbuf(pixbuf);
} else {
// Load the icon for this mime type
GLib.Icon icon = GLib.ContentType.get_icon(
this.gio_content_type
);
Gtk.IconTheme theme = Gtk.IconTheme.get_default();
Gtk.IconLookupFlags flags = Gtk.IconLookupFlags.DIR_LTR;
if (get_direction() == Gtk.TextDirection.RTL) {
flags = Gtk.IconLookupFlags.DIR_RTL;
}
Gtk.IconInfo? icon_info = theme.lookup_by_gicon_for_scale(
icon, ATTACHMENT_ICON_SIZE, window_scale, flags
var theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
paintable = theme.lookup_by_gicon(
icon, ATTACHMENT_ICON_SIZE, window_scale, get_direction(), 0
);
if (icon_info != null) {
pixbuf = yield icon_info.load_icon_async(load_cancelled);
}
}
} catch (GLib.Error error) {
debug("Failed to load icon for attachment '%s': %s",
@ -158,43 +134,14 @@ public class Components.AttachmentPane : Gtk.Grid {
error.message);
}
if (pixbuf != null) {
Cairo.Surface surface = Gdk.cairo_surface_create_from_pixbuf(
pixbuf, window_scale, get_window()
);
this.icon.set_from_surface(surface);
if (paintable != null) {
this.icon.paintable = paintable;
}
}
}
static construct {
// Set up custom keybindings
unowned Gtk.BindingSet bindings = Gtk.BindingSet.by_class(
(ObjectClass) typeof(FlowBox).class_ref()
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.O, Gdk.ModifierType.CONTROL_MASK, "open-attachments", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.S, Gdk.ModifierType.CONTROL_MASK, "save-attachments", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.BackSpace, 0, "remove-attachments", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.Delete, 0, "remove-attachments", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.KP_Delete, 0, "remove-attachments", 0
);
}
/** Determines if this pane's contents can be modified. */
public bool edit_mode { get; private set; }
@ -205,13 +152,13 @@ public class Components.AttachmentPane : Gtk.Grid {
private GLib.SimpleActionGroup actions = new GLib.SimpleActionGroup();
[GtkChild] private unowned Gtk.Grid attachments_container;
[GtkChild] private unowned Gtk.Box attachments_container;
[GtkChild] private unowned Gtk.Button save_button;
[GtkChild] private unowned Gtk.Button remove_button;
private FlowBox attachments_view;
private Gtk.FlowBox attachments_view;
public AttachmentPane(bool edit_mode,
@ -225,22 +172,20 @@ public class Components.AttachmentPane : Gtk.Grid {
this.manager = manager;
this.attachments_view = new FlowBox();
this.attachments_view.open_attachments.connect(on_open_selected);
this.attachments_view.remove_attachments.connect(on_remove_selected);
this.attachments_view.save_attachments.connect(on_save_selected);
this.attachments_view = new Gtk.FlowBox();
//XXX GTK4 need to check if shortcuts still work
this.attachments_view.child_activated.connect(on_child_activated);
this.attachments_view.selected_children_changed.connect(on_selected_changed);
this.attachments_view.button_press_event.connect(on_attachment_button_press);
this.attachments_view.popup_menu.connect(on_attachment_popup_menu);
Gtk.GestureClick gesture = new Gtk.GestureClick();
gesture.pressed.connect(on_attachment_pressed);
this.attachments_view.add_controller(gesture);
this.attachments_view.activate_on_single_click = false;
this.attachments_view.max_children_per_line = 3;
this.attachments_view.column_spacing = 6;
this.attachments_view.row_spacing = 6;
this.attachments_view.selection_mode = Gtk.SelectionMode.MULTIPLE;
this.attachments_view.hexpand = true;
this.attachments_view.show();
this.attachments_container.add(this.attachments_view);
this.attachments_container.append(this.attachments_view);
this.actions.add_action_entries(action_entries, this);
insert_action_group(GROUP_NAME, this.actions);
@ -249,7 +194,7 @@ public class Components.AttachmentPane : Gtk.Grid {
public void add_attachment(Geary.Attachment attachment,
GLib.Cancellable? cancellable) {
View view = new View(attachment);
this.attachments_view.add(view);
this.attachments_view.append(view);
this.attachments.add(attachment);
view.load_icon.begin(cancellable);
@ -257,7 +202,7 @@ public class Components.AttachmentPane : Gtk.Grid {
}
public void open_attachment(Geary.Attachment attachment) {
open_attachments(Geary.Collection.single(attachment));
open_attachments.begin(Geary.Collection.single(attachment));
}
public void save_attachment(Geary.Attachment attachment) {
@ -270,12 +215,15 @@ public class Components.AttachmentPane : Gtk.Grid {
public void remove_attachment(Geary.Attachment attachment) {
this.attachments.remove(attachment);
this.attachments_view.foreach(child => {
Gtk.FlowBoxChild flow_child = (Gtk.FlowBoxChild) child;
if (((View) flow_child.get_child()).attachment == attachment) {
this.attachments_view.remove(child);
}
});
for (int i = 0; true; i++) {
unowned var flow_child = this.attachments_view.get_child_at_index(i);
if (flow_child == null)
break;
if (((View) flow_child.get_child()).attachment == attachment) {
this.attachments_view.remove(flow_child);
i--;
}
}
}
public bool save_all() {
@ -317,7 +265,7 @@ public class Components.AttachmentPane : Gtk.Grid {
bool ret = false;
var selected = get_selected_attachments();
if (!selected.is_empty) {
open_attachments(selected);
open_attachments.begin(selected);
ret = true;
}
return ret;
@ -362,29 +310,36 @@ public class Components.AttachmentPane : Gtk.Grid {
set_action_enabled(ACTION_SELECT_ALL, len < this.attachments.size);
}
private void open_attachments(Gee.Collection<Geary.Attachment> attachments) {
var main = this.get_toplevel() as Application.MainWindow;
if (main != null) {
Application.Client app = main.application;
bool confirmed = true;
if (app.config.ask_open_attachment) {
QuestionDialog ask_to_open = new QuestionDialog.with_checkbox(
main,
_("Are you sure you want to open these attachments?"),
_("Attachments may cause damage to your system if opened. Only open files from trusted sources."),
Stock._OPEN_BUTTON, Stock._CANCEL, _("Dont _ask me again"), false
);
if (ask_to_open.run() == Gtk.ResponseType.OK) {
app.config.ask_open_attachment = !ask_to_open.is_checked;
} else {
confirmed = false;
}
}
private async void open_attachments(Gee.Collection<Geary.Attachment> attachments) {
var main = get_root() as Application.MainWindow;
if (main == null)
return;
if (confirmed) {
foreach (var attachment in attachments) {
app.show_uri.begin(attachment.file.get_uri());
}
Application.Client app = main.application;
if (app.config.ask_open_attachment) {
var dialog = new Adw.AlertDialog(
_("Are you sure you want to open these attachments?"),
_("Attachments may cause damage to your system if opened. Only open files from trusted sources."));
dialog.add_response("cancel", _("_Cancel"));
dialog.add_response("open", _("_Open"));
dialog.default_response = "open";
dialog.close_response = "cancel";
var check = new Adw.SwitchRow();
check.title = _("Dont _ask me again");
string response = yield dialog.choose(main, null);
if (response != "open")
return;
app.config.ask_open_attachment = !check.active;
}
foreach (var attachment in attachments) {
var launcher = new Gtk.FileLauncher(attachment.file);
try {
yield launcher.launch(get_native() as Gtk.Window, null);
} catch (GLib.Error err) {
warning("Couldn't show attachment: %s", err.message);
}
}
}
@ -396,7 +351,7 @@ public class Components.AttachmentPane : Gtk.Grid {
}
}
private void show_popup(View view, Gdk.EventButton? event) {
private void show_popup(View view, Gdk.Rectangle? rect) {
Gtk.Builder builder = new Gtk.Builder.from_resource(
"/org/gnome/Geary/components-attachment-pane-menus.ui"
);
@ -410,21 +365,20 @@ public class Components.AttachmentPane : Gtk.Grid {
GROUP_NAME,
targets
);
Gtk.Menu menu = new Gtk.Menu.from_model(model);
menu.attach_to_widget(view, null);
if (event != null) {
menu.popup_at_pointer(event);
} else {
menu.popup_at_widget(view, CENTER, SOUTH, null);
Gtk.PopoverMenu menu = new Gtk.PopoverMenu.from_model(model);
menu.set_parent(view);
if (rect != null) {
menu.set_pointing_to(rect);
}
menu.popup();
}
private void beep() {
Gtk.Widget? toplevel = get_toplevel();
if (toplevel == null) {
Gdk.Window? window = toplevel.get_window();
if (window != null) {
window.beep();
Gtk.Native? native = get_native();
if (native == null) {
Gdk.Surface? surface = native.get_surface();
if (surface != null) {
surface.beep();
}
}
}
@ -486,32 +440,19 @@ public class Components.AttachmentPane : Gtk.Grid {
update_actions();
}
private bool on_attachment_popup_menu(Gtk.Widget widget) {
bool ret = Gdk.EVENT_PROPAGATE;
Gtk.Window parent = get_toplevel() as Gtk.Window;
if (parent != null) {
Gtk.FlowBoxChild? focus = parent.get_focus() as Gtk.FlowBoxChild;
if (focus != null && focus.parent == this.attachments_view) {
show_popup((View) focus.get_child(), null);
ret = Gdk.EVENT_STOP;
}
}
return ret;
}
private bool on_attachment_button_press(Gtk.Widget widget,
Gdk.EventButton event) {
bool ret = Gdk.EVENT_PROPAGATE;
if (event.triggers_context_menu()) {
private void on_attachment_pressed(Gtk.GestureClick gesture, int n_press, double x, double y) {
var event = gesture.get_current_event();
if (event.triggers_context_menu()) {
Gtk.FlowBoxChild? child = this.attachments_view.get_child_at_pos(
(int) event.x,
(int) event.y
(int) x,
(int) y
);
if (child != null) {
show_popup((View) child.get_child(), event);
ret = Gdk.EVENT_STOP;
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
show_popup((View) child.get_child(), rect);
//XXX GTK4?
// ret = Gdk.EVENT_STOP;
}
}
return ret;
}
}
}
}

View file

@ -12,9 +12,25 @@
[GtkTemplate (ui = "/org/gnome/Geary/components-conversation-actions.ui")]
public class Components.ConversationActions : Gtk.Box {
public bool show_conversation_actions { get; construct; }
public bool show_conversation_actions {
get { return this.action_buttons.visible; }
set {
if (this.action_buttons.visible == value)
return;
this.action_buttons.visible = value;
notify_property("show-conversation-actions");
}
}
public bool show_response_actions { get; construct; }
public bool show_response_actions {
get { return this.response_buttons.visible; }
set {
if (this.response_buttons.visible == value)
return;
this.response_buttons.visible = value;
notify_property("show-conversation-actions");
}
}
public bool pack_justified { get; construct; }
@ -43,16 +59,12 @@ public class Components.ConversationActions : Gtk.Box {
[GtkChild] private unowned Gtk.MenuButton mark_message_button { get; }
[GtkChild] private unowned Gtk.MenuButton copy_message_button { get; }
[GtkChild] private unowned Gtk.Box action_buttons { get; }
[GtkChild] private unowned Gtk.Box action_buttons;
[GtkChild] private unowned Gtk.Button archive_button;
[GtkChild] private unowned Gtk.Button trash_delete_button;
private bool show_trash_button = true;
// Load these at construction time
private Gtk.Image trash_image = new Gtk.Image.from_icon_name("user-trash-symbolic", Gtk.IconSize.MENU);
private Gtk.Image delete_image = new Gtk.Image.from_icon_name("edit-delete-symbolic", Gtk.IconSize.MENU);
static construct {
set_css_name("components-conversation-actions");
}
@ -69,16 +81,13 @@ public class Components.ConversationActions : Gtk.Box {
this.notify["selected-conversations"].connect(() => update_conversation_buttons());
this.notify["service-provider"].connect(() => update_conversation_buttons());
this.mark_message_button.popover = new Gtk.Popover.from_model(null, mark_menu);
this.mark_message_button.menu_model = mark_menu;
this.mark_message_button.toggled.connect((button) => {
this.mark_message_button.activate.connect((button) => {
if (button.active)
mark_message_button_toggled();
});
this.response_buttons.set_visible(this.show_response_actions);
this.action_buttons.set_visible(this.show_conversation_actions);
if (this.pack_justified) {
this.action_buttons.hexpand = true;
this.action_buttons.halign = END;
@ -102,14 +111,11 @@ public class Components.ConversationActions : Gtk.Box {
}
public void show_copy_menu() {
this.copy_message_button.clicked();
this.copy_message_button.active = true;
}
public void set_mark_inverted() {
var image = new Gtk.Image.from_icon_name(
"pan-up-symbolic", Gtk.IconSize.BUTTON
);
this.mark_message_button.set_image(image);
this.mark_message_button.icon_name = "pan-up-symbolic";
}
public void update_trash_button(bool show_trash) {
@ -142,10 +148,7 @@ public class Components.ConversationActions : Gtk.Box {
"Add label to conversations",
this.selected_conversations
);
this.copy_message_button.set_image(
new Gtk.Image.from_icon_name(
"tag-symbolic", Gtk.IconSize.BUTTON)
);
this.copy_message_button.icon_name = "tag-symbolic";
break;
default:
this.copy_message_button.tooltip_text = ngettext(
@ -153,10 +156,7 @@ public class Components.ConversationActions : Gtk.Box {
"Copy conversations",
this.selected_conversations
);
this.copy_message_button.set_image(
new Gtk.Image.from_icon_name(
"folder-symbolic", Gtk.IconSize.BUTTON)
);
this.copy_message_button.icon_name = "folder-symbolic";
break;
}
}
@ -165,7 +165,7 @@ public class Components.ConversationActions : Gtk.Box {
this.trash_delete_button.action_name = Action.Window.prefix(
Application.MainWindow.ACTION_TRASH_CONVERSATION
);
this.trash_delete_button.image = trash_image;
this.trash_delete_button.icon_name = "user-trash-symbolic";
this.trash_delete_button.tooltip_text = ngettext(
"Move conversation to Trash",
"Move conversations to Trash",
@ -175,7 +175,7 @@ public class Components.ConversationActions : Gtk.Box {
this.trash_delete_button.action_name = Action.Window.prefix(
Application.MainWindow.ACTION_DELETE_CONVERSATION
);
this.trash_delete_button.image = delete_image;
this.trash_delete_button.icon_name = "edit-delete-symbolic";
this.trash_delete_button.tooltip_text = ngettext(
"Delete conversation",
"Delete conversations",

View file

@ -6,7 +6,7 @@
*/
/**
* Provides per-GTK Entry undo and redo using a command stack.
* Provides per-GTK Editable undo and redo using a command stack.
*/
public class Components.EntryUndo : Geary.BaseObject {
@ -84,13 +84,13 @@ public class Components.EntryUndo : Geary.BaseObject {
}
}
private void do_insert(Gtk.Entry target) {
private void do_insert(Gtk.Editable target) {
int position = this.position;
target.insert_text(this.text, -1, ref position);
target.set_position(position);
}
private void do_delete(Gtk.Entry target) {
private void do_delete(Gtk.Editable target) {
target.delete_text(
this.position, this.position + this.text.char_count()
);
@ -100,7 +100,7 @@ public class Components.EntryUndo : Geary.BaseObject {
/** The entry being managed */
public Gtk.Entry target { get; private set; }
public Gtk.Editable target { get; private set; }
private Application.CommandStack commands;
private EditType last_edit = NONE;
@ -113,7 +113,8 @@ public class Components.EntryUndo : Geary.BaseObject {
private GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
public EntryUndo(Gtk.Entry target) {
// XXX GTK4 maybe rename this to EditableUndo?
public EntryUndo(Gtk.Editable target) {
this.edit_actions.add_action_entries(EDIT_ACTIONS, this);
this.target = target;
@ -157,7 +158,7 @@ public class Components.EntryUndo : Geary.BaseObject {
}
);
while (!complete) {
Gtk.main_iteration();
MainContext.default().iteration(true);
}
}
@ -179,7 +180,7 @@ public class Components.EntryUndo : Geary.BaseObject {
}
);
while (!complete) {
Gtk.main_iteration();
MainContext.default().iteration(true);
}
}
@ -201,7 +202,7 @@ public class Components.EntryUndo : Geary.BaseObject {
}
);
while (!complete) {
Gtk.main_iteration();
MainContext.default().iteration(true);
}
}
@ -298,7 +299,7 @@ public class Components.EntryUndo : Geary.BaseObject {
private void on_deleted(int start, int end) {
if (this.events_enabled) {
// Normalise value of end to be something useful if needed
string text = this.target.buffer.get_text();
string text = this.target.text;
if (end < 0) {
end = text.char_count();
}

View file

@ -1,34 +0,0 @@
/*
* Copyright © 2017 Software Freedom Conservancy Inc.
* Copyright © 2021 Michael Gratton <mike@vee.net>
* Copyright © 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* The Application HeaderBar
*
* @see Application.MainWindow
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-application.ui")]
public class Components.ApplicationHeaderBar : Hdy.HeaderBar {
[GtkChild] private unowned Gtk.MenuButton app_menu_button;
[GtkChild] public unowned MonitoredSpinner spinner;
construct {
Gtk.Builder builder = new Gtk.Builder.from_resource("/org/gnome/Geary/components-menu-application.ui");
MenuModel app_menu = (MenuModel) builder.get_object("app_menu");
this.app_menu_button.popover = new Gtk.Popover.from_model(null, app_menu);
}
public void show_app_menu() {
this.app_menu_button.clicked();
}
}

View file

@ -1,44 +0,0 @@
/*
* Copyright © 2017 Software Freedom Conservancy Inc.
* Copyright © 2021 Michael Gratton <mike@vee.net>
* Copyright © 2022 Cédric Bellegarde <cedric.bellegarde@adishatz.org>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* The conversation list headerbar.
*
* @see Application.MainWindow
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-conversation-list.ui")]
public class Components.ConversationListHeaderBar : Hdy.HeaderBar {
public string account { get; set; }
public string folder { get; set; }
public bool search_open { get; set; default = false; }
public bool selection_open { get; set; default = false; }
[GtkChild] private unowned Gtk.ToggleButton search_button;
[GtkChild] private unowned Gtk.ToggleButton selection_button;
[GtkChild] public unowned Gtk.Button back_button;
construct {
this.bind_property("account", this, "title", BindingFlags.SYNC_CREATE);
this.bind_property("folder", this, "subtitle", BindingFlags.SYNC_CREATE);
this.bind_property(
"search-open",
this.search_button, "active",
SYNC_CREATE | BIDIRECTIONAL
);
this.bind_property(
"selection-open",
this.selection_button, "active",
SYNC_CREATE | BIDIRECTIONAL
);
}
}

View file

@ -14,25 +14,23 @@
* @see Application.MainWindow
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-headerbar-conversation.ui")]
public class Components.ConversationHeaderBar : Gtk.Bin {
public class Components.ConversationHeaderBar : Adw.Bin {
public bool find_open { get; set; default = false; }
public ConversationActions shown_actions {
get {
return (ConversationActions) this.actions_squeezer.visible_child;
}
}
public bool compact { get; set; default = false; }
[GtkChild] private unowned Hdy.Squeezer actions_squeezer;
[GtkChild] public unowned ConversationActions full_actions;
[GtkChild] public unowned ConversationActions compact_actions;
[GtkChild] public unowned ConversationActions left_actions;
[GtkChild] public unowned ConversationActions right_actions;
[GtkChild] private unowned Gtk.ToggleButton find_button;
[GtkChild] public unowned Gtk.Button back_button;
[GtkChild] private unowned Hdy.HeaderBar conversation_header;
[GtkChild] private unowned Adw.HeaderBar conversation_header;
// Keep a strong ref when it's temporarily removed
private Adw.HeaderBar? _conversation_header = null;
//XXX GTK4 need to figure out close buttons
#if 0
public bool show_close_button {
get {
return this.conversation_header.show_close_button;
@ -41,12 +39,9 @@ public class Components.ConversationHeaderBar : Gtk.Bin {
this.conversation_header.show_close_button = value;
}
}
#endif
construct {
this.actions_squeezer.notify["visible-child"].connect_after(
() => { notify_property("shown-actions"); }
);
this.bind_property(
"find-open",
this.find_button, "active",
@ -54,17 +49,24 @@ public class Components.ConversationHeaderBar : Gtk.Bin {
);
}
public void set_conversation_header(Hdy.HeaderBar header) {
remove(this.conversation_header);
header.hexpand = true;
header.show_close_button = this.conversation_header.show_close_button;
add(header);
public override void dispose() {
this._conversation_header = null;
base.dispose();
}
public void remove_conversation_header(Hdy.HeaderBar header) {
remove(header);
this.conversation_header.show_close_button = header.show_close_button;
add(this.conversation_header);
public void set_conversation_header(Adw.HeaderBar header)
requires (header.parent == null) {
this._conversation_header = null;
header.hexpand = true;
//XXX GTK4 need to figure out close buttons
// header.show_close_button = this.conversation_header.show_close_button;
this.child = header;
}
public void remove_conversation_header(Adw.HeaderBar header) {
//XXX GTK4 need to figure out close buttons
// this.conversation_header.show_close_button = header.show_close_button;
this.child = this.conversation_header;
}
public void set_find_sensitive(bool is_sensitive) {

View file

@ -1,75 +0,0 @@
/* Copyright 2017 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Represents an in-app notification.
*
* Following the GNOME HIG, it should only contain a label and maybe a button.
* Looks like libadwaita toast, remove this when porting toward GTK4
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-in-app-notification.ui")]
public class Components.InAppNotification : Gtk.Revealer {
/** Default length of time to show the notification. */
public const uint DEFAULT_DURATION = 5;
[GtkChild] private unowned Gtk.Label message_label;
[GtkChild] private unowned Gtk.Button action_button;
private uint duration;
/**
* Creates an in-app notification.
*
* @param message The message that should be displayed.
* @param duration The length of time to show the notification,
* in seconds.
*/
public InAppNotification(string message,
uint duration = DEFAULT_DURATION) {
this.transition_type = Gtk.RevealerTransitionType.CROSSFADE;
this.message_label.label = message;
this.duration = duration;
}
/**
* Sets a button for the notification.
*/
public void set_button(string label, string action_name) {
this.action_button.visible = true;
this.action_button.label = label;
this.action_button.action_name = action_name;
}
public override void show() {
if (this.duration > 0) {
base.show();
this.reveal_child = true;
// Close after the given amount of time
GLib.Timeout.add_seconds(
this.duration, () => { close(); return false; }
);
}
}
/**
* Closes the in-app notification.
*/
[GtkCallback]
public void close() {
// Allows for the disappearing transition
this.reveal_child = false;
}
// Make sure the notification gets destroyed after closing.
[GtkCallback]
private void on_child_revealed(Object src, ParamSpec p) {
if (!this.child_revealed)
destroy();
}
}

View file

@ -158,7 +158,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
construct {
get_style_context().add_class("geary-info-bar-stack");
add_css_class("geary-info-bar-stack");
update_queue_type();
}
@ -174,7 +174,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
* stack constructed, the info bar may or may not be revealed
* immediately.
*/
public new void add(Components.InfoBar to_add) {
public void add(Components.InfoBar to_add) {
if (this.available.offer(to_add)) {
update();
}
@ -187,7 +187,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
* replaced with the next info bar added. If the only info bar
* present is removed, the stack also hides itself.
*/
public new void remove(Components.InfoBar to_remove) {
public void remove(Components.InfoBar to_remove) {
if (this.available.remove(to_remove)) {
update();
}
@ -210,7 +210,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
// Not currently showing an info bar but have one to show,
// so show it
this.visible = true;
base.add(next);
this.child = next;
next.revealed = true;
} else if (current != null && next != current) {
// Currently showing an info bar but should be showing
@ -241,7 +241,7 @@ public class Components.InfoBarStack : Gtk.Frame, Geary.BaseInterface {
private void on_revealed(GLib.Object target, GLib.ParamSpec param) {
var info_bar = target as Components.InfoBar;
target.notify["revealed"].disconnect(on_revealed);
base.remove(info_bar);
this.child = null;
remove(info_bar);
}

View file

@ -97,16 +97,13 @@ public class Components.InfoBar : Gtk.Box {
this.description.tooltip_text = description;
}
var container = new Gtk.Grid();
container.orientation = VERTICAL;
container.valign = CENTER;
container.add(this.status);
var container = new Gtk.Box(Gtk.Orientation.VERTICAL, 0);
container.valign = Gtk.Align.CENTER;
container.append(this.status);
if (this.description != null) {
container.add(this.description);
container.append(this.description);
}
get_content_area().add(container);
show_all();
get_content_area().append(container);
}
public InfoBar.for_plugin(Plugin.InfoBar plugin,
@ -143,14 +140,12 @@ public class Components.InfoBar : Gtk.Box {
var secondaries = plugin.secondary_buttons.bidir_list_iterator();
bool has_prev = secondaries.last();
while (has_prev) {
get_action_area().add(new_plugin_button(secondaries.get()));
get_action_area().append(new_plugin_button(secondaries.get()));
has_prev = secondaries.previous();
}
update_plugin_primary_button();
set_data<int>(InfoBarStack.PRIORITY_QUEUE_KEY, priority);
show_all();
}
[GtkCallback]
@ -161,10 +156,9 @@ public class Components.InfoBar : Gtk.Box {
response(Gtk.ResponseType.CLOSE);
}
/* {@inheritDoc} */
public override void destroy() {
public override void dispose() {
this.plugin = null;
base.destroy();
base.dispose();
}
public Gtk.Box get_action_area() {
@ -180,7 +174,7 @@ public class Components.InfoBar : Gtk.Box {
button.clicked.connect(() => {
response(response_id);
});
get_action_area().add(button);
get_action_area().append(button);
button.visible = true;
return button;
}
@ -194,7 +188,7 @@ public class Components.InfoBar : Gtk.Box {
get_action_area().remove(plugin_primary_button);
}
if (new_button != null) {
get_action_area().add(new_button);
get_action_area().append(new_button);
}
this.plugin_primary_button = new_button;
}
@ -204,11 +198,7 @@ public class Components.InfoBar : Gtk.Box {
if (ui.icon_name == null) {
button = new Gtk.Button.with_label(ui.label);
} else {
var icon = new Gtk.Image.from_icon_name(
ui.icon_name, Gtk.IconSize.BUTTON
);
button = new Gtk.Button();
button.add(icon);
button = new Gtk.Button.from_icon_name(ui.icon_name);
button.tooltip_text = ui.label;
}
button.set_action_name(
@ -217,26 +207,26 @@ public class Components.InfoBar : Gtk.Box {
if (ui.action_target != null) {
button.set_action_target_value(ui.action_target);
}
button.show_all();
return button;
}
private void _set_message_type(Gtk.MessageType message_type) {
if (this._message_type != message_type) {
Gtk.StyleContext context = this.get_style_context();
const string[] type_class = {
Gtk.STYLE_CLASS_INFO,
Gtk.STYLE_CLASS_WARNING,
Gtk.STYLE_CLASS_QUESTION,
Gtk.STYLE_CLASS_ERROR,
"info",
"warning",
"question",
"error",
null
};
if (type_class[this._message_type] != null)
context.remove_class(type_class[this._message_type]);
remove_css_class(type_class[this._message_type]);
this._message_type = message_type;
// XXX GTK4
#if 0
var atk_obj = this.get_accessible();
if (atk_obj is Atk.Object) {
string name = null;
@ -271,9 +261,10 @@ public class Components.InfoBar : Gtk.Box {
if (name != null)
atk_obj.set_name(name);
}
#endif
if (type_class[this._message_type] != null)
context.add_class(type_class[this._message_type]);
add_css_class(type_class[this._message_type]);
}
}
}

View file

@ -9,7 +9,7 @@
* A view that displays information about an application error.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-error-view.ui")]
public class Components.InspectorErrorView : Gtk.Grid {
public class Components.InspectorErrorView : Adw.Bin {
[GtkChild] private unowned Gtk.TextView problem_text;

View file

@ -9,12 +9,7 @@
* A view that displays the contents of the Engine's log.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-log-view.ui")]
public class Components.InspectorLogView : Gtk.Grid {
private const int COL_MESSAGE = 0;
private const int COL_ACCOUNT = 1;
private const int COL_DOMAIN = 2;
public class Components.InspectorLogView : Gtk.Box {
private class SidebarRow : Gtk.ListBoxRow {
@ -47,25 +42,56 @@ public class Components.InspectorLogView : Gtk.Grid {
() => { notify_property("enabled"); }
);
var grid = new Gtk.Grid();
grid.orientation = HORIZONTAL;
grid.add(label_widget);
grid.add(this.enabled_toggle);
add(grid);
show_all();
var box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
box.append(label_widget);
box.append(this.enabled_toggle);
this.child = box;
}
}
private class RecordRow : Gtk.Box {
public Geary.Logging.Record? record {
get { return this._record; }
set {
this._record = value;
update();
}
}
private Geary.Logging.Record? _record = null;
private unowned Gtk.Label message_label;
construct {
this.orientation = Gtk.Orientation.HORIZONTAL;
this.spacing = 6;
var label = new Gtk.Label("");
label.selectable = true;
label.add_css_class("monospace");
append(label);
this.message_label = label;
}
private void update() {
if (this.record == null) {
this.message_label.label = "";
} else {
this.message_label.label = this.record.format();
}
}
}
/** Determines if the log record search user interface is shown. */
public bool search_mode_enabled {
get { return this.search_bar.search_mode_enabled; }
set { this.search_bar.search_mode_enabled = value; }
}
[GtkChild] private unowned Hdy.SearchBar search_bar;
[GtkChild] private unowned Gtk.SearchBar search_bar;
[GtkChild] private unowned Gtk.SearchEntry search_entry;
@ -73,18 +99,13 @@ public class Components.InspectorLogView : Gtk.Grid {
[GtkChild] private unowned Gtk.ScrolledWindow logs_scroller;
[GtkChild] private unowned Gtk.TreeView logs_view;
[GtkChild] private unowned Gtk.ListView logs_view;
[GtkChild] private unowned Gtk.CellRendererText log_renderer;
[GtkChild] private unowned Gtk.MultiSelection selection;
private Gtk.ListStore logs_store = new Gtk.ListStore.newv({
typeof(string),
typeof(string),
typeof(string)
});
private Gtk.TreeModelFilter logs_filter;
[GtkChild] private unowned GLib.ListStore logs_store;
[GtkChild] private unowned Gtk.CustomFilter logs_filter;
private string[] logs_filter_terms = new string[0];
private bool update_logs = true;
@ -106,15 +127,7 @@ public class Components.InspectorLogView : Gtk.Grid {
public signal void record_selection_changed();
public InspectorLogView(Application.Configuration config,
Geary.AccountInformation? filter_by = null) {
GLib.Settings system = config.gnome_interface;
system.bind(
"monospace-font-name",
this.log_renderer, "font",
SettingsBindFlags.DEFAULT
);
public InspectorLogView(Geary.AccountInformation? filter_by = null) {
// Prefill well-known engine logging domains
add_domain(Geary.App.ConversationMonitor.LOGGING_DOMAIN);
add_domain(Geary.Imap.ClientService.LOGGING_DOMAIN);
@ -127,6 +140,8 @@ public class Components.InspectorLogView : Gtk.Grid {
this.search_bar.connect_entry(this.search_entry);
this.sidebar.set_header_func(this.sidebar_header_update);
this.account_filter = filter_by;
this.logs_filter.set_filter_func(log_filter_func);
}
/** Loads log records from the logging system into the view. */
@ -138,42 +153,29 @@ public class Components.InspectorLogView : Gtk.Grid {
this.listener_installed = true;
}
Gtk.ListStore logs_store = this.logs_store;
Geary.Logging.Record? logs = first;
int index = 0;
while (logs != last) {
update_record(logs, logs_store, index++);
update_record(logs, this.logs_store, index++);
logs = logs.next;
}
this.logs_filter = new Gtk.TreeModelFilter(this.logs_store, null);
this.logs_filter.set_visible_func(log_filter_func);
this.logs_view.set_model(this.logs_filter);
}
/** Clears all log records from the view. */
public void clear() {
this.logs_store.clear();
this.logs_store.remove_all();
this.first_pending = null;
}
/** {@inheritDoc} */
public override void destroy() {
~InspectorLogView() {
if (this.listener_installed) {
Geary.Logging.set_log_listener(null);
}
base.destroy();
}
/** Forwards a key press event to the search entry. */
public bool handle_key_press(Gdk.EventKey event) {
return this.search_entry.key_press_event(event);
}
/** Returns the number of currently selected log records. */
public int count_selected_records() {
return this.logs_view.get_selection().count_selected_rows();
public uint count_selected_records() {
return (uint) this.selection.get_selection().get_size();
}
/** Enables and disables updating log records as new ones arrive. */
@ -204,33 +206,30 @@ public class Components.InspectorLogView : Gtk.Grid {
out.put_string("```\n");
}
string line_sep = format.get_line_separator();
Gtk.TreeModel model = this.logs_view.model;
if (save_all) {
// Save all rows selected
Gtk.TreeIter? iter;
bool valid = model.get_iter_first(out iter);
while (valid && !cancellable.is_cancelled()) {
save_record(model, iter, @out, cancellable);
for (uint i = 0; i < this.logs_store.get_n_items(); i++) {
if (cancellable.is_cancelled())
break;
var record = (Geary.Logging.Record) this.logs_store.get_item(i);
out.put_string(record.format());
out.put_string(line_sep);
valid = model.iter_next(ref iter);
}
} else {
// Save only selected
GLib.Error? inner_err = null;
this.logs_view.get_selection().selected_foreach(
(model, path, iter) => {
if (inner_err == null) {
try {
save_record(model, iter, @out, cancellable);
out.put_string(line_sep);
} catch (GLib.Error err) {
inner_err = err;
}
}
}
);
if (inner_err != null) {
throw inner_err;
Gtk.Bitset selected = this.selection.get_selection();
for (uint i = 0; i < selected.get_size(); i++) {
if (cancellable.is_cancelled())
break;
uint position = selected.get_nth(i);
var record = (Geary.Logging.Record) this.logs_store.get_item(position);
assert(record != null);
out.put_string(record.format());
out.put_string(line_sep);
}
}
if (format == MARKDOWN) {
@ -238,19 +237,6 @@ public class Components.InspectorLogView : Gtk.Grid {
}
}
private inline void save_record(Gtk.TreeModel model,
Gtk.TreeIter iter,
GLib.DataOutputStream @out,
GLib.Cancellable? cancellable)
throws GLib.Error {
GLib.Value value;
model.get_value(iter, COL_MESSAGE, out value);
string? message = (string) value;
if (message != null) {
out.put_string(message);
}
}
private void add_account(Geary.AccountInformation account) {
if (this.seen_accounts.add(account.id)) {
var row = new SidebarRow(ACCOUNT, account.display_name, account.id);
@ -311,11 +297,11 @@ public class Components.InspectorLogView : Gtk.Grid {
string cleaned =
Geary.String.reduce_whitespace(this.search_entry.text).casefold();
this.logs_filter_terms = cleaned.split(" ");
this.logs_filter.refilter();
this.logs_filter.changed(Gtk.FilterChange.DIFFERENT);
}
private inline void update_record(Geary.Logging.Record record,
Gtk.ListStore store,
GLib.ListStore store,
int position) {
record.fill_well_known_sources();
if (record.account != null) {
@ -325,14 +311,7 @@ public class Components.InspectorLogView : Gtk.Grid {
assert(record.format() != null);
var account = record.account;
store.insert_with_values(
null,
position,
COL_MESSAGE, record.format(),
COL_ACCOUNT, account != null ? account.information.id : "",
COL_DOMAIN, record.domain ?? ""
);
store.insert(position, record);
}
private void sidebar_header_update(Gtk.ListBoxRow current_row,
@ -347,22 +326,19 @@ public class Components.InspectorLogView : Gtk.Grid {
current_row.set_header(header);
}
private bool log_filter_func(Gtk.TreeModel model, Gtk.TreeIter iter) {
GLib.Value value;
model.get_value(iter, COL_ACCOUNT, out value);
var account = (string) value;
var show_row = (
account == "" || !(account in this.suppressed_accounts)
private bool log_filter_func(GLib.Object object) {
unowned var record = (Geary.Logging.Record) object;
var account = record.account;
bool show_row = (
account == null || !(account.information.id in this.suppressed_accounts)
);
if (show_row) {
model.get_value(iter, COL_DOMAIN, out value);
var domain = (string) value;
show_row = !Geary.Logging.is_suppressed_domain(domain);
show_row = !Geary.Logging.is_suppressed_domain(record.domain ?? "");
}
model.get_value(iter, COL_MESSAGE, out value);
string message = (string) value;
string message = record.format();
if (show_row && this.logs_filter_terms.length > 0) {
var folded_message = message.casefold();
foreach (string term in this.logs_filter_terms) {
@ -384,23 +360,34 @@ public class Components.InspectorLogView : Gtk.Grid {
return show_row;
}
[GtkCallback]
private void on_logs_size_allocate() {
if (this.autoscroll) {
update_scrollbar();
}
}
[GtkCallback]
private void on_logs_search_changed() {
update_logs_filter();
}
[GtkCallback]
private void on_logs_selection_changed() {
private void on_logs_selection_changed(Gtk.SelectionModel selection,
uint position,
uint changed) {
record_selection_changed();
}
[GtkCallback]
private void on_item_factory_setup(Object object) {
unowned var item = (Gtk.ListItem) object;
item.child = new RecordRow();
}
[GtkCallback]
private void on_item_factory_bind(Object object) {
unowned var item = (Gtk.ListItem) object;
unowned var record = (Geary.Logging.Record) item.item;
unowned var row = (RecordRow) item.child;
row.record = record;
}
[GtkCallback]
private void on_sidebar_row_activated(Gtk.ListBox list,
Gtk.ListBoxRow activated) {

View file

@ -9,52 +9,7 @@
* A view that displays system and library information.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector-system-view.ui")]
public class Components.InspectorSystemView : Gtk.Grid {
private class 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();
}
}
public class Components.InspectorSystemView : Gtk.Box {
[GtkChild] private unowned Gtk.ListBox system_list;
@ -65,9 +20,11 @@ public class Components.InspectorSystemView : Gtk.Grid {
public InspectorSystemView(Application.Client application) {
this.details = application.get_runtime_information();
foreach (Application.Client.RuntimeDetail? detail in this.details) {
this.system_list.add(
new DetailRow("%s:".printf(detail.name), detail.value)
);
var row = new Adw.ActionRow();
row.add_css_class("property");
row.title = detail.name;
row.subtitle = detail.value;
this.system_list.append(row);
}
}

View file

@ -9,7 +9,7 @@
* A window that displays debugging and development information.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-inspector.ui")]
public class Components.Inspector : Gtk.ApplicationWindow {
public class Components.Inspector : Adw.ApplicationWindow {
/** Determines the format used when serialising inspector data. */
@ -68,7 +68,8 @@ public class Components.Inspector : Gtk.ApplicationWindow {
public Inspector(Application.Client application) {
Object(application: application);
this.title = this.header_bar.title = _("Inspector");
//XXX GTK4 need to figure out titles
// this.title = this.header_bar.title = _("Inspector");
// Edit actions
GLib.SimpleActionGroup edit_actions = new GLib.SimpleActionGroup();
@ -78,7 +79,7 @@ public class Components.Inspector : Gtk.ApplicationWindow {
// Window actions
add_action_entries(WINDOW_ACTIONS, this);
this.log_pane = new InspectorLogView(application.config, null);
this.log_pane = new InspectorLogView(null);
this.log_pane.record_selection_changed.connect(
on_logs_selection_changed
);
@ -95,11 +96,15 @@ public class Components.Inspector : Gtk.ApplicationWindow {
this.log_pane.load(Geary.Logging.get_earliest_record(), null);
}
public override bool key_press_event(Gdk.EventKey event) {
[GtkCallback]
private bool on_key_pressed(Gtk.EventControllerKey controller,
uint keyval,
uint keycode,
Gdk.ModifierType state) {
bool ret = Gdk.EVENT_PROPAGATE;
if (this.log_pane.search_mode_enabled &&
event.keyval == Gdk.Key.Escape) {
keyval == Gdk.Key.Escape) {
// Manually deactivate search so the button stays in sync
this.search_button.set_active(false);
ret = Gdk.EVENT_STOP;
@ -109,18 +114,17 @@ public class Components.Inspector : Gtk.ApplicationWindow {
this.log_pane.search_mode_enabled) {
// Ensure <Space> and others are passed to the search
// entry before getting used as an accelerator.
ret = this.log_pane.handle_key_press(event);
ret = controller.forward(this.log_pane);
}
if (ret == Gdk.EVENT_PROPAGATE) {
ret = base.key_press_event(event);
}
return ret;
//XXX GTK4 - not sure how to handle this
if (ret == Gdk.EVENT_PROPAGATE &&
!this.log_pane.search_mode_enabled) {
// Nothing has handled the event yet, and search is not
// active, so see if we want to activate it now.
ret = this.log_pane.handle_key_press(event);
ret = controller.forward(this.log_pane);
if (ret == Gdk.EVENT_STOP) {
this.search_button.set_active(true);
}
@ -140,10 +144,9 @@ public class Components.Inspector : Gtk.ApplicationWindow {
this.log_pane.enable_log_updates(enabled);
}
private async void save(string path,
private async void save(GLib.File dest,
GLib.Cancellable? cancellable)
throws GLib.Error {
GLib.File dest = GLib.File.new_for_path(path);
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
null,
false,
@ -209,35 +212,29 @@ public class Components.Inspector : Gtk.ApplicationWindow {
string clipboard_value = (string) bytes.get_data();
if (!Geary.String.is_empty(clipboard_value)) {
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
get_clipboard().set_text(clipboard_value);
}
}
[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")
);
save_as.begin((obj, res) => {
save_as.end(res);
});
}
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);
}
}
);
private async void save_as() {
var dialog = new Gtk.FileDialog();
dialog.title = _("Save As");
dialog.accept_label = _("Save As");
dialog.initial_name = new DateTime.now_local().format("Geary Inspector - %F %T.txt");
try {
File? file = yield dialog.save(this, null);
if (file != null)
yield this.save(file, null);
} catch (Error err) {
warning("Failed to save inspector data: %s", err.message);
}
}

View file

@ -9,7 +9,7 @@
* A placeholder image and message for empty views.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-placeholder-pane.ui")]
public class Components.PlaceholderPane : Gtk.Grid {
public class Components.PlaceholderPane : Gtk.Box {
public const string CLASS_HAS_TEXT = "geary-has-text";
@ -54,7 +54,7 @@ public class Components.PlaceholderPane : Gtk.Grid {
this.subtitle_label.hide();
}
if (this.title_label.visible || this.subtitle_label.visible) {
get_style_context().add_class(CLASS_HAS_TEXT);
add_css_class(CLASS_HAS_TEXT);
}
}

View file

@ -0,0 +1,159 @@
/*
* Copyright 2016 Software Freedom Conservancy Inc.
* 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.
*/
[GtkTemplate (ui = "/org/gnome/Geary/components-preferences-dialog.ui")]
public class Components.PreferencesDialog : Adw.PreferencesDialog {
[GtkChild] private unowned Adw.SwitchRow autoselect_row;
[GtkChild] private unowned Adw.SwitchRow display_preview_row;
[GtkChild] private unowned Adw.SwitchRow single_key_shortcuts_row;
[GtkChild] private unowned Adw.SwitchRow startup_notifications_row;
[GtkChild] private unowned Adw.SwitchRow trust_images_row;
[GtkChild] private unowned Adw.PreferencesGroup plugins_group;
private class PluginRow : Adw.ActionRow {
private Peas.PluginInfo plugin;
private Application.PluginManager plugins;
private Gtk.Switch sw = new Gtk.Switch();
public PluginRow(Peas.PluginInfo plugin,
Application.PluginManager plugins) {
this.plugin = plugin;
this.plugins = plugins;
this.sw.active = plugin.is_loaded();
this.sw.notify["active"].connect_after(() => update_plugin());
this.sw.valign = CENTER;
this.title = plugin.get_name();
this.subtitle = plugin.get_description();
this.activatable_widget = this.sw;
this.add_suffix(this.sw);
plugins.plugin_activated.connect((info) => {
if (this.plugin == info) {
this.sw.active = true;
}
});
plugins.plugin_deactivated.connect((info) => {
if (this.plugin == info) {
this.sw.active = false;
}
});
plugins.plugin_error.connect((info) => {
if (this.plugin == info) {
this.sw.active = false;
this.sw.sensitive = false;
}
});
}
private void update_plugin() {
if (this.sw.active && !this.plugin.is_loaded()) {
bool loaded = false;
try {
loaded = this.plugins.load_optional(this.plugin);
} catch (GLib.Error err) {
warning(
"Plugin %s not able to be loaded: %s",
plugin.get_name(), err.message
);
}
if (!loaded) {
this.sw.active = false;
}
} else if (!sw.active && this.plugin.is_loaded()) {
bool unloaded = false;
try {
unloaded = this.plugins.unload_optional(this.plugin);
} catch (GLib.Error err) {
warning(
"Plugin %s not able to be loaded: %s",
plugin.get_name(), err.message
);
}
if (!unloaded) {
this.sw.active = true;
}
}
}
}
/** Returns the window's associated client application instance. */
public Application.Client? application { get; construct set; }
private Application.PluginManager plugins;
public PreferencesDialog(Application.Client application,
Application.PluginManager plugins) {
Object(application: application);
this.plugins = plugins;
setup_general_pane();
setup_plugin_pane();
}
private void setup_general_pane() {
Application.Configuration config = this.application.config;
config.bind(
Application.Configuration.AUTOSELECT_KEY,
this.autoselect_row,
"active"
);
config.bind(
Application.Configuration.DISPLAY_PREVIEW_KEY,
this.display_preview_row,
"active"
);
config.bind(
Application.Configuration.SINGLE_KEY_SHORTCUTS,
this.single_key_shortcuts_row,
"active"
);
config.bind(
Application.Configuration.RUN_IN_BACKGROUND_KEY,
this.startup_notifications_row,
"active"
);
config.bind_with_mapping(
Application.Configuration.IMAGES_TRUSTED_DOMAINS,
this.trust_images_row,
"active",
(GLib.SettingsBindGetMappingShared) settings_trust_images_getter,
(GLib.SettingsBindSetMappingShared) settings_trust_images_setter
);
}
private void setup_plugin_pane() {
foreach (Peas.PluginInfo plugin in
this.plugins.get_optional_plugins()) {
this.plugins_group.add(new PluginRow(plugin, this.plugins));
}
}
private static bool settings_trust_images_getter(GLib.Value value, GLib.Variant variant, void* user_data) {
var domains = variant.get_strv();
value.set_boolean(domains.length > 0 && domains[0] == "*");
return true;
}
private static GLib.Variant settings_trust_images_setter(GLib.Value value, GLib.VariantType expected_type, void* user_data) {
var trusted = value.get_boolean();
string[] values = {};
if (trusted)
values += "*";
return new GLib.Variant.strv(values);
}
}

View file

@ -1,294 +0,0 @@
/*
* Copyright 2016 Software Freedom Conservancy Inc.
* 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.
*/
public class Components.PreferencesWindow : Hdy.PreferencesWindow {
private const string ACTION_CLOSE = "preferences-close";
private const ActionEntry[] WINDOW_ACTIONS = {
{ Action.Window.CLOSE, on_close },
{ ACTION_CLOSE, on_close },
};
private class PluginRow : Hdy.ActionRow {
private Peas.PluginInfo plugin;
private Application.PluginManager plugins;
private Gtk.Switch sw = new Gtk.Switch();
public PluginRow(Peas.PluginInfo plugin,
Application.PluginManager plugins) {
this.plugin = plugin;
this.plugins = plugins;
this.sw.active = plugin.is_loaded();
this.sw.notify["active"].connect_after(() => update_plugin());
this.sw.valign = CENTER;
this.title = plugin.get_name();
this.subtitle = plugin.get_description();
this.activatable_widget = this.sw;
this.add(this.sw);
plugins.plugin_activated.connect((info) => {
if (this.plugin == info) {
this.sw.active = true;
}
});
plugins.plugin_deactivated.connect((info) => {
if (this.plugin == info) {
this.sw.active = false;
}
});
plugins.plugin_error.connect((info) => {
if (this.plugin == info) {
this.sw.active = false;
this.sw.sensitive = false;
}
});
}
private void update_plugin() {
if (this.sw.active && !this.plugin.is_loaded()) {
bool loaded = false;
try {
loaded = this.plugins.load_optional(this.plugin);
} catch (GLib.Error err) {
warning(
"Plugin %s not able to be loaded: %s",
plugin.get_name(), err.message
);
}
if (!loaded) {
this.sw.active = false;
}
} else if (!sw.active && this.plugin.is_loaded()) {
bool unloaded = false;
try {
unloaded = this.plugins.unload_optional(this.plugin);
} catch (GLib.Error err) {
warning(
"Plugin %s not able to be loaded: %s",
plugin.get_name(), err.message
);
}
if (!unloaded) {
this.sw.active = true;
}
}
}
}
public static void add_accelerators(Application.Client app) {
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
}
/** Returns the window's associated client application instance. */
public new Application.Client? application {
get { return (Application.Client) base.get_application(); }
set { base.set_application(value); }
}
private Application.PluginManager plugins;
public PreferencesWindow(Application.MainWindow parent,
Application.PluginManager plugins) {
Object(
application: parent.application,
default_width: 800,
default_height: 600,
transient_for: parent
);
this.plugins = plugins;
add_general_pane();
add_plugin_pane();
}
private void add_general_pane() {
var autoselect = new Gtk.Switch();
autoselect.valign = CENTER;
var autoselect_row = new Hdy.ActionRow();
/// Translators: Preferences label
autoselect_row.title = _("_Automatically select next message");
autoselect_row.use_underline = true;
autoselect_row.activatable_widget = autoselect;
autoselect_row.add(autoselect);
var display_preview = new Gtk.Switch();
display_preview.valign = CENTER;
var display_preview_row = new Hdy.ActionRow();
/// Translators: Preferences label
display_preview_row.title = _("_Display conversation preview");
display_preview_row.use_underline = true;
display_preview_row.activatable_widget = display_preview;
display_preview_row.add(display_preview);
var single_key_shortucts = new Gtk.Switch();
single_key_shortucts.valign = CENTER;
var single_key_shortucts_row = new Hdy.ActionRow();
/// Translators: Preferences label
single_key_shortucts_row.title = _("Use _single key email shortcuts");
single_key_shortucts_row.tooltip_text = _(
"Enable keyboard shortcuts for email actions that do not require pressing <Ctrl>"
);
single_key_shortucts_row.use_underline = true;
single_key_shortucts_row.activatable_widget = single_key_shortucts;
single_key_shortucts_row.add(single_key_shortucts);
var startup_notifications = new Gtk.Switch();
startup_notifications.valign = CENTER;
var startup_notifications_row = new Hdy.ActionRow();
/// Translators: Preferences label
startup_notifications_row.title = _("_Watch for new mail when closed");
startup_notifications_row.use_underline = true;
/// Translators: Preferences tooltip
startup_notifications_row.tooltip_text = _(
"Geary will keep running after all windows are closed"
);
startup_notifications_row.activatable_widget = startup_notifications;
startup_notifications_row.add(startup_notifications);
var trust_images = new Gtk.Switch();
trust_images.valign = CENTER;
var trust_images_row = new Hdy.ActionRow();
/// Translators: Preferences label
trust_images_row.title = _("_Always load images");
trust_images_row.subtitle = _("Showing remote images allows the sender to track you");
trust_images_row.use_underline = true;
trust_images_row.activatable_widget = trust_images;
trust_images_row.add(trust_images);
var unset_html_colors = new Gtk.Switch();
unset_html_colors.valign = CENTER;
var unset_html_colors_row = new Hdy.ActionRow();
/// Translators: Preferences label
unset_html_colors_row.title = _("_Override the original colors in HTML emails");
unset_html_colors_row.subtitle = _("Overrides the original colors in HTML messages to integrate better with the app theme. Requires restart.");
unset_html_colors_row.use_underline = true;
unset_html_colors_row.activatable_widget = unset_html_colors;
unset_html_colors_row.add(unset_html_colors);
var group = new Hdy.PreferencesGroup();
/// Translators: Preferences group title
//group.title = _("General");
/// Translators: Preferences group description
//group.description = _("General application preferences");
group.add(autoselect_row);
group.add(display_preview_row);
group.add(single_key_shortucts_row);
group.add(startup_notifications_row);
group.add(trust_images_row);
group.add(unset_html_colors_row);
var page = new Hdy.PreferencesPage();
/// Translators: Preferences page title
page.title = _("Preferences");
page.icon_name = "preferences-other-symbolic";
page.add(group);
page.show_all();
add(page);
GLib.SimpleActionGroup window_actions = new GLib.SimpleActionGroup();
window_actions.add_action_entries(WINDOW_ACTIONS, this);
insert_action_group(Action.Window.GROUP_NAME, window_actions);
Application.Client? application = this.application;
if (application != null) {
Application.Configuration config = application.config;
config.bind(
Application.Configuration.AUTOSELECT_KEY,
autoselect,
"state"
);
config.bind(
Application.Configuration.DISPLAY_PREVIEW_KEY,
display_preview,
"state"
);
config.bind(
Application.Configuration.SINGLE_KEY_SHORTCUTS,
single_key_shortucts,
"state"
);
config.bind(
Application.Configuration.RUN_IN_BACKGROUND_KEY,
startup_notifications,
"state"
);
config.bind_with_mapping(
Application.Configuration.IMAGES_TRUSTED_DOMAINS,
trust_images,
"state",
(GLib.SettingsBindGetMappingShared) settings_trust_images_getter,
(GLib.SettingsBindSetMappingShared) settings_trust_images_setter
);
config.bind(
Application.Configuration.UNSET_HTML_COLORS,
unset_html_colors,
"state"
);
}
}
private void add_plugin_pane() {
var group = new Hdy.PreferencesGroup();
/// Translators: Preferences group title
//group.title = _("Plugins");
/// Translators: Preferences group description
//group.description = _("Optional features for Geary");
Application.Client? application = this.application;
if (application != null) {
foreach (Peas.PluginInfo plugin in
this.plugins.get_optional_plugins()) {
group.add(new PluginRow(plugin, this.plugins));
}
}
var page = new Hdy.PreferencesPage();
/// Translators: Preferences page title
page.title = _("Plugins");
page.icon_name = "application-x-addon-symbolic";
page.add(group);
page.show_all();
add(page);
}
private void on_close() {
close();
}
private static bool settings_trust_images_getter(GLib.Value value, GLib.Variant variant, void* user_data) {
var domains = variant.get_strv();
value.set_boolean(domains.length > 0 && domains[0] == "*");
return true;
}
private static GLib.Variant settings_trust_images_setter(GLib.Value value, GLib.VariantType expected_type, void* user_data) {
var trusted = value.get_boolean();
string[] values = {};
if (trusted)
values += "*";
return new GLib.Variant.strv(values);
}
}

View file

@ -106,14 +106,13 @@ public class Components.ProblemReportInfoBar : InfoBar {
}
private void show_details() {
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
var dialog = new Dialogs.ProblemDetailsDialog(
main,
main.application,
this.report
);
dialog.show();
dialog.present(main);
}
}

View file

@ -10,6 +10,8 @@
#include <gtk/gtk.h>
// XXX GTK4 - I really need to know how this works before reimplementing it
#if 0
#define COMPONENTS_TYPE_REFLOW_BOX (components_reflow_box_get_type())
G_DECLARE_FINAL_TYPE (ComponentsReflowBox, components_reflow_box, COMPONENTS, REFLOW_BOX, GtkContainer)
@ -491,6 +493,4 @@ components_reflow_box_new (void)
{
return g_object_new (COMPONENTS_TYPE_REFLOW_BOX, NULL);
}
#endif

View file

@ -6,11 +6,17 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class SearchBar : Hdy.SearchBar {
public class SearchBar : Adw.Bin {
/// Translators: Search entry placeholder text
private const string DEFAULT_SEARCH_TEXT = _("Search");
private unowned Gtk.SearchBar search_bar;
public bool search_mode_enabled {
get { return this.search_bar.search_mode_enabled; }
set { this.search_bar.search_mode_enabled = value; }
}
public Gtk.SearchEntry entry {
get; private set; default = new Gtk.SearchEntry();
}
@ -23,10 +29,14 @@ public class SearchBar : Hdy.SearchBar {
public SearchBar(Geary.Engine engine) {
var bar = new Gtk.SearchBar();
this.search_bar = bar;
this.child = bar;
this.engine = engine;
this.search_undo = new Components.EntryUndo(this.entry);
this.notify["search-mode-enabled"].connect(on_search_mode_changed);
search_bar.notify["search-mode-enabled"].connect(on_search_mode_changed);
/// Translators: Search entry tooltip
this.entry.tooltip_text = _("Search all mail in account for keywords");
@ -37,21 +47,18 @@ public class SearchBar : Hdy.SearchBar {
search_text_changed(this.entry.text);
});
this.entry.placeholder_text = DEFAULT_SEARCH_TEXT;
this.entry.has_focus = true;
var column = new Hdy.Clamp();
var column = new Adw.Clamp();
column.maximum_size = 400;
column.add(this.entry);
column.child = this.entry;
connect_entry(this.entry);
add(column);
show_all();
search_bar.connect_entry(this.entry);
search_bar.child = column;
}
public override void grab_focus() {
set_search_mode(true);
this.entry.grab_focus();
public override bool grab_focus() {
this.search_mode_enabled = true;
return this.entry.grab_focus();
}
public void set_account(Geary.Account? account) {

View file

@ -0,0 +1,91 @@
/*
* Copyright 2025 Niels De Graef <nielsdegraef@gmail.com>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* Groups several validators together, allowing to show an aggregate result.
*/
public class Components.ValidatorGroup : GLib.Object, GLib.ListModel, Gtk.Buildable {
private GenericArray<Validator> validators = new GenericArray<Validator>();
/** Fired when the relevant validator has changed */
public signal void changed(Validator validator);
/** Fired when the relevant validator has emitted the activated signal */
public signal void activated(Validator validator);
public void add_validator(Validator validator) {
validator.changed.connect(on_validator_changed);
validator.activated.connect(on_validator_activated);
this.validators.add(validator);
}
private void on_validator_changed(Validator validator) {
this.changed(validator);
}
private void on_validator_activated(Validator validator) {
this.activated(validator);
}
public bool is_valid() {
foreach (unowned var validator in this.validators) {
if (validator.is_valid)
return false;
}
return true;
}
// GListModel implementation
public GLib.Type get_item_type() {
return typeof(Components.Validator);
}
public uint get_n_items() {
return this.validators.length;
}
public GLib.Object? get_item(uint index) {
if (index >= this.validators.length)
return null;
return this.validators[index];
}
// GtkBuildable implementation
public void add_child(Gtk.Builder builder, Object child, string? type) {
unowned var validator = child as Validator;
if (validator == null) {
critical("Can't add child %p to ValidatorGroup, expected Validator instance", validator);
return;
}
add_validator(validator);
}
private string id;
public void set_id(string id) {
this.id = id;
}
public unowned string get_id() {
return this.id;
}
// We don't need any of these, but Vala requires us to implement them
public void custom_finished(Gtk.Builder builder, GLib.Object? child, string tagname, void* data) {}
public void custom_tag_end(Gtk.Builder builder, GLib.Object? child, string tagname, void* data) {}
public bool custom_tag_start(Gtk.Builder builder, GLib.Object? child, string tagname, out Gtk.BuildableParser parser, out void* data) {
return false;
}
public unowned GLib.Object get_internal_child(Gtk.Builder builder, string childname) {
return null;
}
public void parser_finished(Gtk.Builder builder) {}
public void set_buildable_property(Gtk.Builder builder, string name, GLib.Value value) {}
}

View file

@ -6,7 +6,7 @@
*/
/**
* Validates the contents of a Gtk Entry as they are entered.
* Validates the contents of a Gtk Editable as they are entered.
*
* This class may be used to validate required, but otherwise free
* form entries. Subclasses may perform more complex and task-specific
@ -15,34 +15,30 @@
public class Components.Validator : GLib.Object {
private const Gtk.EntryIconPosition ICON_POS =
Gtk.EntryIconPosition.SECONDARY;
/**
* The state of the entry monitored by this validator.
* The state of the editable monitored by this validator.
*
* Only {@link VALID} can be considered strictly valid, all other
* states should be treated as being invalid.
*/
public enum Validity {
/** The contents of the entry have not been validated. */
/** The contents of the editable have not been validated. */
INDETERMINATE,
/** The contents of the entry is valid. */
/** The contents of the editable is valid. */
VALID,
/**
* The contents of the entry is being checked.
* The contents of the editable is being checked.
*
* See {@link validate} for the use of this value.
*/
IN_PROGRESS,
/** The contents of the entry is required but not present. */
/** The contents of the editable is required but not present. */
EMPTY,
/** The contents of the entry is not valid. */
/** The contents of the editable is not valid. */
INVALID;
}
@ -50,11 +46,11 @@ public class Components.Validator : GLib.Object {
public enum Trigger {
/** A manual validation was requested via {@link validate}. */
MANUAL,
/** The entry's contents changed. */
/** The editable's contents changed. */
CHANGED,
/** The entry lost the keyboard focus. */
/** The editable lost the keyboard focus. */
LOST_FOCUS,
/** The user activated the entry. */
/** The user activated the editable. */
ACTIVATED;
}
@ -64,10 +60,26 @@ public class Components.Validator : GLib.Object {
public string? icon_tooltip_text;
}
/** The entry being monitored */
public Gtk.Entry target { get; private set; }
/** The editable being monitored */
public Gtk.Editable target {
get { return this._target; }
construct set {
this._target = value;
if (value is Gtk.Entry) {
this.target_helper = new EntryHelper((Gtk.Entry) value);
} else if (value is Adw.EntryRow) {
this.target_helper = new EntryRowHelper((Adw.EntryRow) value);
} else {
critical("Validator for type '%s' unsupported", value.get_type().name());
}
}
}
private Gtk.Editable _target;
protected EditableHelper target_helper;
/** Determines if the current state indicates the entry is valid. */
private Gtk.EventControllerFocus target_focus_controller = new Gtk.EventControllerFocus();
/** Determines if the current state indicates the editable is valid. */
public bool is_valid {
get { return (this.state == Validity.VALID); }
}
@ -75,40 +87,22 @@ public class Components.Validator : GLib.Object {
/**
* Determines how empty entries are treated.
*
* If true, an empty entry is considered {@link Validity.EMPTY}
* If true, an empty editable is considered {@link Validity.EMPTY}
* (i.e. invalid) else it is considered to be {@link
* Validity.INDETERMINATE}.
*/
public bool is_required { get; set; default = true; }
/** The current validation state of the entry. */
/** The current validation state of the editable. */
public Validity state {
get; private set; default = Validity.INDETERMINATE;
}
/** The UI state to use when indeterminate. */
public UiState indeterminate_state;
/** The UI state to use when valid. */
public UiState valid_state;
/** The UI state to use when in progress. */
public UiState in_progress_state;
/** The UI state to use when empty. */
public UiState empty_state;
/** The UI state to use when invalid. */
public UiState invalid_state;
// Determines if the value has changed since last validation
private bool target_changed = false;
private Geary.TimeoutManager ui_update_timer;
private Geary.TimeoutManager pulse_timer;
bool did_pulse = false;
/** Fired when the validation state changes. */
public signal void state_changed(Trigger reason, Validity prev_state);
@ -123,49 +117,28 @@ public class Components.Validator : GLib.Object {
public signal void focus_lost();
public Validator(Gtk.Entry target) {
this.target = target;
construct {
this.ui_update_timer = new Geary.TimeoutManager.seconds(
2, on_update_ui
1, on_update_ui
);
this.pulse_timer = new Geary.TimeoutManager.milliseconds(
200, on_pulse
);
this.pulse_timer.repetition = FOREVER;
this.indeterminate_state = {
target.get_icon_name(ICON_POS),
target.get_icon_tooltip_text(ICON_POS)
};
this.valid_state = {
target.get_icon_name(ICON_POS),
target.get_icon_tooltip_text(ICON_POS)
};
this.in_progress_state = {
target.get_icon_name(ICON_POS),
null
};
this.empty_state = { "dialog-warning-symbolic", null };
this.invalid_state = { "dialog-error-symbolic", null };
this.target.add_events(Gdk.EventMask.FOCUS_CHANGE_MASK);
this.target.activate.connect(on_activate);
this.target_helper.activated.connect(on_activate);
this.target.changed.connect(on_changed);
this.target.focus_out_event.connect(on_focus_out);
this.target_focus_controller.leave.connect(on_focus_out);
this.target.add_controller(this.target_focus_controller);
}
public Validator(Gtk.Editable target) {
GLib.Object(target: target);
}
~Validator() {
this.target.focus_out_event.disconnect(on_focus_out);
this.target.changed.disconnect(on_changed);
this.target.activate.disconnect(on_activate);
this.ui_update_timer.reset();
this.pulse_timer.reset();
}
/**
* Triggers a validation of the entry.
* Triggers a validation of the editable.
*
* In the case of an asynchronous validation implementations,
* result of the validation will be known sometime after this call
@ -176,12 +149,12 @@ public class Components.Validator : GLib.Object {
}
/**
* Called to validate the target entry's value.
* Called to validate the target editable's value.
*
* This method will be called repeatedly as the user edits the
* value of the target entry to set the new validation {@link
* value of the target editable to set the new validation {@link
* state} given the updated value. It will *not* be called if the
* entry is changed to be empty, instead the validity state will
* editable is changed to be empty, instead the validity state will
* be set based on {@link is_required}.
*
* Subclasses may override this method to implement custom
@ -195,7 +168,7 @@ public class Components.Validator : GLib.Object {
* with the actual result.
*
* The given reason specifies which user action was taken to cause
* the entry's value to be validated.
* the editable's value to be validated.
*
* By default, this always returns {@link Validity.VALID}, making
* it useful for required, but otherwise free-form fields only.
@ -205,7 +178,7 @@ public class Components.Validator : GLib.Object {
}
/**
* Updates the current validation state and the entry's UI.
* Updates the current validation state and the editable's UI.
*
* This should only be called by subclasses that implement a
* CPU-intensive or long-running validation routine and it has
@ -263,8 +236,6 @@ public class Components.Validator : GLib.Object {
// no-op
break;
}
} else if (!this.pulse_timer.is_running) {
this.pulse_timer.start();
}
}
@ -282,66 +253,12 @@ public class Components.Validator : GLib.Object {
private void update_ui(Validity state) {
this.ui_update_timer.reset();
Gtk.StyleContext style = this.target.get_style_context();
style.remove_class(Gtk.STYLE_CLASS_ERROR);
style.remove_class(Gtk.STYLE_CLASS_WARNING);
UiState ui = { null, null };
bool in_progress = false;
switch (state) {
case Validity.INDETERMINATE:
ui = this.indeterminate_state;
break;
case Validity.VALID:
ui = this.valid_state;
break;
case Validity.IN_PROGRESS:
in_progress = true;
ui = this.in_progress_state;
break;
case Validity.EMPTY:
style.add_class(Gtk.STYLE_CLASS_WARNING);
ui = this.empty_state;
break;
case Validity.INVALID:
style.add_class(Gtk.STYLE_CLASS_ERROR);
ui = this.invalid_state;
break;
}
if (in_progress) {
if (!this.pulse_timer.is_running) {
this.pulse_timer.start();
}
} else {
this.pulse_timer.reset();
// If a pulse hasn't been performed (and hence the
// progress bar is not visible), setting the fraction here
// to reset it will actually cause the progress bar to
// become visible. So only reset if needed.
if (this.did_pulse) {
this.target.progress_fraction = 0.0;
this.did_pulse = false;
}
}
this.target.set_icon_from_icon_name(ICON_POS, ui.icon_name);
this.target.set_icon_tooltip_text(
ICON_POS,
// Setting the tooltip to null or the empty string can
// cause GTK+ to setfult. See GTK+ issue #1160.
Geary.String.is_empty(ui.icon_tooltip_text)
? " " : ui.icon_tooltip_text
);
this.target_helper.update_ui(state);
}
private void on_activate() {
if (this.target_changed) {
validate_entry(Trigger.ACTIVATED);
validate_entry(Trigger.ACTIVATED);
} else {
activated();
}
@ -351,11 +268,6 @@ public class Components.Validator : GLib.Object {
update_ui(this.state);
}
private void on_pulse() {
this.target.progress_pulse();
this.did_pulse = true;
}
private void on_changed() {
this.target_changed = true;
validate_entry(Trigger.CHANGED);
@ -364,40 +276,161 @@ public class Components.Validator : GLib.Object {
this.ui_update_timer.start();
}
private bool on_focus_out() {
private void on_focus_out(Gtk.EventControllerFocus controller) {
if (this.target_changed) {
// Only update if the widget has lost focus due to not being
// the focused widget any more, rather than the whole window
// having lost focus.
if (!this.target.is_focus) {
if (!this.target.is_focus()) {
validate_entry(Trigger.LOST_FOCUS);
}
} else {
focus_lost();
}
return Gdk.EVENT_PROPAGATE;
}
}
/**
* A helper class to set an icon, abstracting away the underlying API of the
* Gtk.Editable implementation.
*/
protected abstract class Components.EditableHelper : Object {
/** Tooltip text in case the validator returns an invalid state */
public string? invalid_tooltip_text { get; set; default = null; }
/** Tooltip text in case the validator returns an empty state */
public string? empty_tooltip_text { get; set; default = null; }
/** Emitted if the editable has been activated */
public signal void activated();
/** Sets an error icon with the given name and tooltip */
public abstract void update_ui(Validator.Validity state);
}
private class Components.EntryHelper : EditableHelper {
private unowned Gtk.Entry entry;
public EntryHelper(Gtk.Entry entry) {
this.entry = entry;
this.entry.activate.connect((e) => this.activated());
}
public override void update_ui(Validator.Validity state) {
this.entry.remove_css_class("error");
this.entry.remove_css_class("warning");
switch (state) {
case Validator.Validity.INDETERMINATE:
case Validator.Validity.VALID:
// Reset
this.entry.secondary_icon_name = "";
this.entry.secondary_icon_tooltip_text = "";
break;
case Validator.Validity.IN_PROGRESS:
this.entry.secondary_icon_paintable = new Adw.SpinnerPaintable(this.entry);
this.entry.secondary_icon_tooltip_text = _("Validating");
break;
case Validator.Validity.EMPTY:
this.entry.add_css_class("warning");
this.entry.secondary_icon_name = "dialog-warning-symbolic";
this.entry.secondary_icon_tooltip_text = this.empty_tooltip_text ?? "";
break;
case Validator.Validity.INVALID:
this.entry.add_css_class("error");
this.entry.secondary_icon_name = "dialog-error-symbolic";
this.entry.secondary_icon_tooltip_text = this.invalid_tooltip_text ?? "";
break;
}
}
}
private class Components.EntryRowHelper : EditableHelper {
private unowned Adw.EntryRow row;
private unowned Adw.Spinner? spinner = null;
private unowned Gtk.Image? error_image = null;
public EntryRowHelper(Adw.EntryRow row) {
this.row = row;
this.row.entry_activated.connect((e) => this.activated());
}
public override void update_ui(Validator.Validity state) {
reset();
this.row.remove_css_class("error");
this.row.remove_css_class("warning");
switch (state) {
case Validator.Validity.INDETERMINATE:
case Validator.Validity.VALID:
break;
case Validator.Validity.IN_PROGRESS:
var spinner = new Adw.Spinner();
spinner.tooltip_text = _("Validating");
this.row.add_suffix(spinner);
this.spinner = spinner;
break;
case Validator.Validity.EMPTY:
this.row.add_css_class("warning");
var img = new Gtk.Image.from_icon_name("dialog-warning-symbolic");
img.tooltip_text = this.empty_tooltip_text ?? "";
this.row.add_suffix(img);
this.error_image = img;
break;
case Validator.Validity.INVALID:
this.row.add_css_class("error");
var img = new Gtk.Image.from_icon_name("dialog-error-symbolic");
img.tooltip_text = this.invalid_tooltip_text ?? "";
this.row.add_suffix(img);
this.error_image = img;
break;
}
}
private void reset() {
if (this.spinner != null) {
this.row.remove(this.spinner);
this.spinner = null;
}
if (this.error_image != null) {
this.row.remove(this.error_image);
this.error_image = null;
}
}
}
/**
* A validator for GTK Entry widgets that contain an email address.
* A validator for GTK Editable widgets that contain an email address.
*/
public class Components.EmailValidator : Validator {
public EmailValidator(Gtk.Entry target) {
base(target);
// Translators: Tooltip used when an entry requires a valid
construct {
// Translators: Tooltip used when an editable requires a valid
// email address to be entered, but one is not provided.
this.empty_state.icon_tooltip_text = _("An email address is required");
this.target_helper.empty_tooltip_text = _("An email address is required");
// Translators: Tooltip used when an entry requires a valid
// Translators: Tooltip used when an editablerequires a valid
// email address to be entered, but the address is invalid.
this.invalid_state.icon_tooltip_text = _("Not a valid email address");
this.target_helper.invalid_tooltip_text = _("Not a valid email address");
}
public EmailValidator(Gtk.Editable target) {
GLib.Object(target: target);
}
protected override Validator.Validity do_validate(string value,
Validator.Trigger reason) {
@ -409,9 +442,9 @@ public class Components.EmailValidator : Validator {
/**
* A validator for GTK Entry widgets that contain a network address.
* A validator for Gtk.Editable widgets that contain a network address.
*
* This attempts parse the entry value as a host name or IP address
* This attempts parse the editable value as a host name or IP address
* with an optional port, then resolve the host name if
* needed. Parsing is performed by {@link GLib.NetworkAddress.parse}
* to parse the user input, hence it may be specified in any form
@ -426,27 +459,30 @@ public class Components.NetworkAddressValidator : Validator {
}
/** The default port used when parsing the address. */
public uint16 default_port { get; private set; }
public uint16 default_port { get; construct set; }
private GLib.Resolver resolver;
private GLib.Cancellable? cancellable = null;
public NetworkAddressValidator(Gtk.Entry target, uint16 default_port = 0) {
base(target);
this.default_port = default_port;
construct {
this.resolver = GLib.Resolver.get_default();
// Translators: Tooltip used when an entry requires a valid,
// Translators: Tooltip used when an editable requires a valid,
// resolvable server name to be entered, but one is not
// provided.
this.empty_state.icon_tooltip_text = _("A server name is required");
this.target_helper.empty_tooltip_text = _("A server name is required");
// Translators: Tooltip used when an entry requires a valid
// Translators: Tooltip used when an editable requires a valid
// server name to be entered, but it was unable to be
// looked-up in the DNS.
this.invalid_state.icon_tooltip_text = _("Could not look up server name");
this.target_helper.invalid_tooltip_text = _("Could not look up server name");
}
public NetworkAddressValidator(Gtk.Editable target, uint16 default_port = 0) {
GLib.Object(
target: target,
default_port: default_port
);
}

View file

@ -48,23 +48,6 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
private const string USER_CSS_LEGACY = "user-message.css";
// Workaround WK binding ctor not accepting any args
private class WebsiteDataManager : WebKit.WebsiteDataManager {
public WebsiteDataManager(string base_cache_directory) {
// Use the cache dir for both cache and data since a)
// emails shouldn't be storing data anyway, and b) so WK
// doesn't use the default, shared data dir.
Object(
base_cache_directory: base_cache_directory,
base_data_directory: base_cache_directory
);
}
}
private static WebKit.WebContext? default_context = null;
private static GenericArray<WebKit.UserStyleSheet> styles = null;
@ -76,16 +59,12 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
* Initialises WebKit.WebContext for use by the client.
*/
public static void init_web_context(Application.Configuration config,
File web_extension_dir,
File cache_dir,
bool sandboxed=true) {
WebsiteDataManager data_manager = new WebsiteDataManager(cache_dir.get_path());
WebKit.WebContext context = new WebKit.WebContext.with_website_data_manager(data_manager);
// Enable WebProcess sandboxing
if (sandboxed) {
context.add_path_to_sandbox(web_extension_dir.get_path(), true);
context.set_sandbox_enabled(true);
}
File web_extension_dir) {
WebKit.WebContext context = new WebKit.WebContext();
// Configure WebProcess sandboxing
context.add_path_to_sandbox(web_extension_dir.get_path(), true);
// Use the doc browser model so that we get some caching of
// resources between email body loads.
context.set_cache_model(WebKit.CacheModel.DOCUMENT_BROWSER);
@ -102,11 +81,11 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
view.handle_internal_request(req);
}
});
context.initialize_web_extensions.connect((context) => {
context.set_web_extensions_directory(
context.initialize_web_process_extensions.connect((context) => {
context.set_web_process_extensions_directory(
web_extension_dir.get_path()
);
context.set_web_extensions_initialization_user_data(
context.set_web_process_extensions_initialization_user_data(
new Variant.boolean(config.enable_debug)
);
});
@ -196,6 +175,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
}
private static inline uint to_wk2_font_size(Pango.FontDescription font) {
// XXX GTK4 I have no idea what to do here
double size = font.get_size();
if (!font.get_size_is_absolute()) {
size = size / Pango.SCALE;
@ -331,6 +311,7 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
protected WebView(Application.Configuration config,
GLib.File? cache_dir = null,
WebKit.UserContentManager? custom_manager = null,
WebView? related = null) {
WebKit.Settings setts = new WebKit.Settings();
@ -345,9 +326,6 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
setts.enable_media_stream = false;
setts.enable_offline_web_application_cache = false;
setts.enable_page_cache = false;
#if WEBKIT_PLUGINS_SUPPORTED
setts.enable_plugins = false;
#endif
setts.hardware_acceleration_policy =
WebKit.HardwareAccelerationPolicy.NEVER;
setts.javascript_can_access_clipboard = true;
@ -376,10 +354,23 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
WebView.styles.foreach(style => content_manager.add_style_sheet(style));
}
// Use cache dir for both cache & data since a) emails shouldn't be
// storing data anyway, and b) so WK doesn't use the default, shared datadir
WebKit.NetworkSession nw_session;
if (cache_dir != null) {
nw_session = new WebKit.NetworkSession(
cache_dir.get_path(),
cache_dir.get_path()
);
} else {
nw_session = new WebKit.NetworkSession.ephemeral();
}
Object(
settings: setts,
user_content_manager: content_manager,
web_context: WebView.default_context
web_context: WebView.default_context,
network_session: nw_session
);
base_ref();
init(config);
@ -408,9 +399,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
base_unref();
}
public override void destroy() {
public override void dispose() {
this.message_handlers.clear();
base.destroy();
base.dispose();
}
/**
@ -668,7 +659,9 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
} else if (this.zoom_level > ZOOM_MAX) {
this.zoom_level = ZOOM_MAX;
}
this.scroll_event.connect(on_scroll_event);
var scroll_controller = new Gtk.EventControllerScroll(Gtk.EventControllerScrollFlags.VERTICAL);
scroll_controller.scroll.connect(on_scroll_event);
add_controller(scroll_controller);
// Watch desktop font settings
Settings system_settings = config.gnome_interface;
@ -797,15 +790,18 @@ public abstract class Components.WebView : WebKit.WebView, Geary.BaseInterface {
return Gdk.EVENT_STOP;
}
private bool on_scroll_event(Gdk.EventScroll event) {
if ((event.state & Gdk.ModifierType.CONTROL_MASK) != 0) {
private bool on_scroll_event(Gtk.EventControllerScroll scroll_controller, double dx, double dy) {
var event = (Gdk.ScrollEvent) scroll_controller.get_current_event();
if (Gdk.ModifierType.CONTROL_MASK in event.get_modifier_state()) {
double dir = 0;
if (event.direction == Gdk.ScrollDirection.UP)
if (event.get_direction() == Gdk.ScrollDirection.UP)
dir = -1;
else if (event.direction == Gdk.ScrollDirection.DOWN)
else if (event.get_direction() == Gdk.ScrollDirection.DOWN)
dir = 1;
else if (event.direction == Gdk.ScrollDirection.SMOOTH)
dir = event.delta_y;
else if (event.get_direction() == Gdk.ScrollDirection.SMOOTH) {
double delta_x;
event.get_deltas(out delta_x, out dir);
}
if (dir < 0) {
zoom_in();

View file

@ -62,8 +62,7 @@ public class FolderPopover : Gtk.Popover {
}
var row = new FolderPopoverRow(context, map);
row.show();
list_box.add(row);
list_box.append(row);
list_box.invalidate_sort();
}
@ -90,7 +89,9 @@ public class FolderPopover : Gtk.Popover {
[GtkCallback]
private void on_unmap(Gtk.Widget widget) {
list_box.foreach((row) => list_box.remove(row));
unowned var row = this.list_box.get_row_at_index(0);
while (row != null)
this.list_box.remove(row);
}
[GtkCallback]

View file

@ -1,142 +0,0 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
// Singleton class to hold icons.
public class IconFactory {
public const Gtk.IconSize ICON_TOOLBAR = Gtk.IconSize.LARGE_TOOLBAR;
public const Gtk.IconSize ICON_SIDEBAR = Gtk.IconSize.MENU;
public static IconFactory? instance { get; private set; }
public static void init(GLib.File resource_directory) {
IconFactory.instance = new IconFactory(resource_directory);
}
public const int UNREAD_ICON_SIZE = 16;
public const int STAR_ICON_SIZE = 16;
private Gtk.IconTheme icon_theme { get; private set; }
private File icons_dir;
// Creates the icon factory.
private IconFactory(GLib.File resource_directory) {
icons_dir = resource_directory.get_child("icons");
icon_theme = Gtk.IconTheme.get_default();
icon_theme.append_search_path(icons_dir.get_path());
}
private int icon_size_to_pixels(Gtk.IconSize icon_size) {
switch (icon_size) {
case ICON_SIDEBAR:
return 16;
case ICON_TOOLBAR:
default:
return 24;
}
}
public Icon get_theme_icon(string name) {
return new ThemedIcon(name);
}
public Icon get_custom_icon(string name, Gtk.IconSize size) {
int pixels = icon_size_to_pixels(size);
// Try sized icon first.
File icon_file = icons_dir.get_child("%dx%d".printf(pixels, pixels)).get_child(
"%s.svg".printf(name));
// If that wasn't found, try a non-sized icon.
if (!icon_file.query_exists())
icon_file = icons_dir.get_child("%s.svg".printf(name));
return new FileIcon(icon_file);
}
// Attempts to load and return the missing image icon.
private Gdk.Pixbuf? get_missing_icon(int size, Gtk.IconLookupFlags flags = 0) {
try {
return icon_theme.load_icon("image-missing", size, flags);
} catch (Error err) {
warning("Couldn't load image-missing icon: %s", err.message);
}
// If that fails... well they're out of luck.
return null;
}
public Gtk.IconInfo? lookup_icon(string icon_name, int size, Gtk.IconLookupFlags flags = 0) {
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
if (icon_info == null) {
icon_info = icon_theme.lookup_icon("text-x-generic-symbolic", size, flags);
}
return icon_info;
}
// GTK+ 3.14 no longer scales icons via the IconInfo, so perform manually until we
// properly install the icons as per 3.14's expectations.
private Gdk.Pixbuf aspect_scale_down_pixbuf(Gdk.Pixbuf pixbuf, int size) {
if (pixbuf.width <= size && pixbuf.height <= size)
return pixbuf;
int scaled_width, scaled_height;
if (pixbuf.width >= pixbuf.height) {
double aspect = (double) size / (double) pixbuf.width;
scaled_width = size;
scaled_height = (int) Math.round((double) pixbuf.height * aspect);
} else {
double aspect = (double) size / (double) pixbuf.height;
scaled_width = (int) Math.round((double) pixbuf.width * aspect);
scaled_height = size;
}
return pixbuf.scale_simple(scaled_width, scaled_height, Gdk.InterpType.BILINEAR);
}
public Gdk.Pixbuf? load_symbolic(string icon_name, int size, Gtk.StyleContext style,
Gtk.IconLookupFlags flags = 0) {
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
// Attempt to load as a symbolic icon.
if (icon_info != null) {
try {
return aspect_scale_down_pixbuf(icon_info.load_symbolic_for_context(style), size);
} catch (Error e) {
message("Couldn't load icon: %s", e.message);
}
}
// Default: missing image icon.
return get_missing_icon(size, flags);
}
/**
* Loads a symbolic icon into a pixbuf, where the color-key has been switched to the provided
* color.
*/
public Gdk.Pixbuf? load_symbolic_colored(string icon_name, int size, Gdk.RGBA color,
Gtk.IconLookupFlags flags = 0) {
Gtk.IconInfo? icon_info = icon_theme.lookup_icon(icon_name, size, flags);
// Attempt to load as a symbolic icon.
if (icon_info != null) {
try {
return aspect_scale_down_pixbuf(icon_info.load_symbolic(color), size);
} catch (Error e) {
warning("Couldn't load icon: %s", e.message);
}
}
// Default: missing image icon.
return get_missing_icon(size, flags);
}
}

View file

@ -7,28 +7,35 @@
/**
* Adapts a progress bar to automatically display progress of a Geary.ProgressMonitor.
*/
public class MonitoredProgressBar : Gtk.ProgressBar {
public class MonitoredProgressBar : Adw.Bin {
private Geary.ProgressMonitor? monitor = null;
private Gtk.ProgressBar progress_bar;
construct {
this.progress_bar = new Gtk.ProgressBar();
this.child = this.progress_bar;
}
public void set_progress_monitor(Geary.ProgressMonitor monitor) {
this.monitor = monitor;
monitor.start.connect(on_start);
monitor.finish.connect(on_finish);
monitor.update.connect(on_update);
fraction = monitor.progress;
this.progress_bar.fraction = monitor.progress;
}
private void on_start() {
fraction = 0.0;
this.progress_bar.fraction = 0.0;
}
private void on_update(double total_progress, double change, Geary.ProgressMonitor monitor) {
fraction = total_progress;
this.progress_bar.fraction = total_progress;
}
private void on_finish() {
fraction = 1.0;
this.progress_bar.fraction = 1.0;
}
}

View file

@ -7,9 +7,16 @@
/**
* Adapts a progress spinner to automatically display progress of a Geary.ProgressMonitor.
*/
public class MonitoredSpinner : Gtk.Spinner {
public class MonitoredSpinner : Adw.Bin {
private Geary.ProgressMonitor? monitor = null;
private Adw.Spinner spinner;
construct {
this.spinner = new Adw.Spinner();
this.child = spinner;
}
public void set_progress_monitor(Geary.ProgressMonitor? monitor) {
if (monitor != null) {
this.monitor = monitor;
@ -17,8 +24,7 @@ public class MonitoredSpinner : Gtk.Spinner {
monitor.finish.connect(on_stop);
} else {
this.monitor = null;
stop();
hide();
this.spinner.visible = false;
}
}
@ -28,13 +34,11 @@ public class MonitoredSpinner : Gtk.Spinner {
}
private void on_start() {
start();
show();
this.spinner.visible = true;
}
private void on_stop() {
stop();
hide();
this.spinner.visible = false;
}
}

View file

@ -0,0 +1,457 @@
/*
* Copyright © 2016 Software Freedom Conservancy Inc.
* Copyright © 2025 Niels De Graef <nielsdegraef@gmail.com>
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A widget that allows a user to create a list of email addresses
* (for example a "CC" row).
*
*XXX we need to put an EntryUndo back here
*/
public class Composer.AddressesRow : Adw.EntryRow, Geary.BaseInterface {
/**
* The list of email addresses.
*
* Check the "is-valid" property to see if they are actually valid.
*
* Manually setting this property will override any text that was there before.
*/
public Geary.RFC822.MailboxAddresses addresses {
get { return this._addresses; }
set {
this._addresses = value;
validate_addresses();
this.text = value.to_full_display();
}
}
private Geary.RFC822.MailboxAddresses _addresses = new Geary.RFC822.MailboxAddresses();
/** Determines if the entry contains only valid email addresses (and is not empty) */
public bool is_valid { get; private set; default = false; }
public bool is_empty {
//XXX could this be this.text.length != 0 insteaD?
get { return this.addresses.is_empty; }
}
// Text between the start of the entry or end of the previous email
// address and the current position of the cursor, if any.
// This will be used for searching a match in the contact list.
//XXX maybe replace this with a filter?
public string search_key {
get { return this._search_key; }
set {
if (this._search_key == value)
return;
string old_value = this._search_key;
this._search_key = value;
update_search_filter(old_value, value);
notify_property("search-key");
}
}
private string _search_key = "";
// The list of (possibly incomplete) email addresses
private GenericArray<string> addresses_raw = new GenericArray<string>();
// Index in addressew_raw of the email address the cursor is currently at
private int cursor_at_address = 0;
public Application.ContactStore? contacts { get; set; default = null; }
private unowned AddressSuggestionPopover popover;
static construct {
set_css_name("geary-composer-widget-header-row");
}
construct {
this.changed.connect(on_changed);
var popover = new AddressSuggestionPopover();
bind_property("contacts", popover, "contacts", BindingFlags.SYNC_CREATE);
popover.selected_address.connect(on_address_suggestion_selected);
popover.set_parent(this);
this.popover = popover;
// We can't use the default autohide behavior since it grabs focus,
// which we don't want (as the user should be able to continue typing).
popover.autohide = false;
}
public AddressesRow(string title) {
Object(title: title);
base_ref();
}
~AddressesRow() {
base_unref();
}
private void validate_addresses() {
bool is_valid = !this._addresses.is_empty;
foreach (Geary.RFC822.MailboxAddress address in this.addresses) {
if (!address.is_valid()) {
is_valid = false;
return;
}
}
this.is_valid = is_valid;
}
private void on_changed() {
update_addresses();
update_validity();
}
private void update_addresses() {
string current_key = "";
this.cursor_at_address = 0;
this.addresses_raw.length = 0;
// NB: Do not strip any white space from the addresses,
// otherwise we won't be able to accurately insert
// addresses in the middle of the list in
// ::insert_address_at_cursor.
int current_char = 0;
unichar c = 0;
int start_idx = 0;
int next_idx = 0;
bool in_quote = false;
while (this.text.get_next_char(ref next_idx, out c)) {
if (current_char == (this.text.char_count() - 1) &&
current_char != 0) {
if (c != ',') {
// Strip whitespace here though so it does not
// interfere with search and highlighting.
current_key = this.text.slice(
start_idx, next_idx
).strip();
}
// We're in the middle of the address, so it
// hasn't yet been added to the list and hence we
// don't need to subtract 1 from its size here
this.cursor_at_address = this.addresses_raw.length;
}
switch (c) {
case ',':
if (!in_quote) {
// Don't include the comma in the address
string address = this.text.slice(start_idx, next_idx - 1);
this.addresses_raw.add(address);
// Don't include it in the next one, either
start_idx = next_idx;
}
break;
case '"':
in_quote = !in_quote;
break;
}
current_char++;
}
// Add any remaining text after the last comma
string address = this.text.substring(start_idx);
this.addresses_raw.add(address);
// XXX we probably want to do this with a timeout
// Update current key
this.search_key = current_key;
}
private void update_validity() {
if (Geary.String.is_empty_or_whitespace(text)) {
this._addresses = new Geary.RFC822.MailboxAddresses();
this.is_valid = false;
} else {
try {
this._addresses =
new Geary.RFC822.MailboxAddresses.from_rfc822_string(text);
this.is_valid = true;
} catch (Geary.RFC822.Error err) {
this._addresses = new Geary.RFC822.MailboxAddresses();
this.is_valid = false;
}
}
//XXX we should make this conditional
notify_property("addresses");
notify_property("is-valid");
notify_property("is-empty");
}
private void update_search_filter(string old_value, string new_value) {
if (this.contacts == null)
return;
if (new_value.length > 3) {
this.popover.search_contacts.begin(new_value, null, (obj, res) => {
this.popover.search_contacts.end(res);
});
} else {
this.popover.clear_suggestions();
}
}
private void on_address_suggestion_selected(AddressSuggestionPopover popover,
Geary.RFC822.MailboxAddress address) {
insert_address_at_cursor(address);
}
private void insert_address_at_cursor(Geary.RFC822.MailboxAddress mailbox) {
// Take care to do a delete then an insert here so that
// Component.EntryUndo can combine the two into a single
// undoable command.
int start_char = 0;
if (this.cursor_at_address > 0) {
// Address parts don't contain commas, so need to add
// an char width for it. Don't need to worry about
// spaces because they are preserved by
// ::update_addresses.
start_char++;
for (uint i = 0; i < this.cursor_at_address; i++) {
start_char += this.addresses_raw[i].char_count();
}
}
int end_char = get_position();
// Format and use the selected address
string formatted = mailbox.to_full_display();
if (this.cursor_at_address != 0) {
// Isn't the first address, so add some whitespace to
// pad it out
formatted = " " + formatted;
}
if (get_position() < this.text.char_count() &&
this.addresses_raw[this.cursor_at_address].strip() !=
this.search_key.strip()) {
// Isn't at the end of the entry, and the address
// under the cursor does not simply consist of the
// lookup key (i.e. is effectively already empty
// otherwise), so add a comma to separate this address
// from the next one
formatted = formatted + ", ";
}
this.addresses_raw.insert(this.cursor_at_address, formatted);
// Update the entry text
if (start_char < end_char) {
delete_text(start_char, end_char);
}
insert_text(formatted, -1, ref start_char);
// Update the entry cursor position. The previous call
// updates the start so just use that, but add extra space
// for the comma and any white space at the start of the
// next address.
if (start_char < this.text.char_count()) {
start_char += 2;
}
set_position(start_char);
}
}
/**
* A helper object to list not just the addresses but also their related contact
*/
public class ContactAddressItem : GLib.Object {
public Application.Contact contact { get; construct set; }
public Geary.RFC822.MailboxAddress address { get; construct set; }
public ContactAddressItem(Application.Contact contact,
Geary.RFC822.MailboxAddress address) {
Object(contact: contact, address: address);
}
}
public class AddressSuggestionPopover : Gtk.Popover {
// Minimum visibility for the contact to appear in autocompletion.
private const Geary.Contact.Importance VISIBILITY_THRESHOLD =
Geary.Contact.Importance.RECEIVED_FROM;
public Application.ContactStore contacts { get; construct set; }
private GLib.ListStore model;
private Gtk.SingleSelection selection;
/**
* Fired when the user has selected an address suggestion
*/
public signal void selected_address(Geary.RFC822.MailboxAddress address);
construct {
var factory = new Gtk.SignalListItemFactory();
factory.setup.connect(on_setup_item);
factory.bind.connect(on_bind_item);
this.model = new GLib.ListStore(typeof(ContactAddressItem));
this.model.items_changed.connect((model, pos, removed, added) => {
bool is_empty = (model.get_n_items() == 0);
bool was_empty = ((model.get_n_items() - added + removed) == 0);
if (is_empty != was_empty) {
if (was_empty)
popup();
else
popdown();
}
});
this.selection = new Gtk.SingleSelection(this.model);
var listview = new Gtk.ListView(selection, factory);
listview.single_click_activate = true;
listview.tab_behavior = Gtk.ListTabBehavior.ITEM;
listview.activate.connect(on_activate);
var sw = new Gtk.ScrolledWindow();
sw.hscrollbar_policy = Gtk.PolicyType.NEVER;
sw.propagate_natural_height = true;
sw.max_content_height = 300;
sw.child = listview;
this.child = sw;
}
private void on_setup_item(Object object) {
unowned var item = (Gtk.ListItem) object;
// Create the row widget
var row = new AddressSuggestionRow();
item.child = row;
}
private void on_bind_item(Object object) {
unowned var item = (Gtk.ListItem) object;
unowned var row = (AddressSuggestionRow) item.child;
unowned var contact_address = (ContactAddressItem) item.item;
row.contact_address = contact_address;
}
private void on_activate(Gtk.ListView listvieww,
uint position) {
var contact_addr = (ContactAddressItem?) this.selection.selected_item;
if (contact_addr == null)
return;
popdown();
selected_address(contact_addr.address);
}
public void clear_suggestions() {
model.remove_all();
}
public async void search_contacts(string query,
GLib.Cancellable? cancellable) {
Gee.Collection<Application.Contact>? results = null;
try {
results = yield this.contacts.search(
query,
VISIBILITY_THRESHOLD,
20,
cancellable
);
} catch (GLib.IOError.CANCELLED err) {
// All good
} catch (GLib.Error err) {
debug("Error searching contacts for completion: %s", err.message);
}
if (!cancellable.is_cancelled()) {
model.remove_all();
foreach (Application.Contact contact in results) {
for (uint i = 0; i < contact.email_addresses.get_n_items(); i++) {
var addr = (Geary.RFC822.MailboxAddress) contact.email_addresses.get_item(i);
model.append(new ContactAddressItem(contact, addr));
}
}
}
}
}
private class AddressSuggestionRow : Gtk.Box {
private unowned Adw.Avatar avatar;
private unowned Gtk.Label name_label;
private unowned Gtk.Label address_label;
public ContactAddressItem? contact_address {
get { return this._contact_address; }
set {
if (this._contact_address == value)
return;
update(value);
notify_property("contact-address");
}
}
private ContactAddressItem? _contact_address = null;
construct {
this.orientation = Gtk.Orientation.HORIZONTAL;
this.spacing = 6;
this.margin_top = 3;
this.margin_bottom = 3;
this.margin_start = 3;
this.margin_end = 3;
add_css_class("contact-address-list-row");
var avatar = new Adw.Avatar(32, null, true);
append(avatar);
this.avatar = avatar;
var names_vbox = new Gtk.Box(Gtk.Orientation.VERTICAL, 3);
append(names_vbox);
var label = new Gtk.Label("");
label.ellipsize = Pango.EllipsizeMode.END;
label.valign = Gtk.Align.CENTER;
label.halign = Gtk.Align.START;
label.xalign = 0;
label.width_chars = 24;
names_vbox.append(label);
this.name_label = label;
label = new Gtk.Label("");
label.ellipsize = Pango.EllipsizeMode.END;
label.valign = Gtk.Align.CENTER;
label.halign = Gtk.Align.START;
label.xalign = 0;
label.width_chars = 24;
names_vbox.append(label);
this.address_label = label;
}
private void update(ContactAddressItem contact_addr) {
this._contact_address = contact_addr;
if (contact_addr == null) {
this.avatar.text = null;
this.name_label.label = "";
this.address_label.label = "";
return;
}
//XXX GTK4
if (Geary.String.is_empty(contact_addr.contact.display_name)) {
this.avatar.text = null;
this.name_label.label = "";
this.name_label.visible = false;
} else {
this.avatar.text = contact_addr.contact.display_name;
this.name_label.label = contact_addr.contact.display_name;
this.name_label.visible = true;
}
this.address_label.label = contact_addr.address.address;
}
}

View file

@ -25,4 +25,5 @@ internal interface Composer.ApplicationInterface :
internal abstract async void discard_composed_email(Composer.Widget composer);
internal abstract GLib.File get_web_cache_dir();
}

View file

@ -12,7 +12,7 @@
* Adding a composer to this container places it in {@link
* Widget.PresentationMode.PANED} mode.
*/
public class Composer.Box : Gtk.Frame, Container {
public class Composer.Box : Gtk.Frame, Composer.Container {
static construct {
set_css_name("geary-composer-box");
@ -21,7 +21,7 @@ public class Composer.Box : Gtk.Frame, Container {
/** {@inheritDoc} */
public Gtk.ApplicationWindow? top_window {
get { return get_toplevel() as Gtk.ApplicationWindow; }
get { return get_root() as Gtk.ApplicationWindow; }
}
/** {@inheritDoc} */
@ -39,14 +39,14 @@ public class Composer.Box : Gtk.Frame, Container {
this.composer.set_mode(PANED);
this.headerbar = headerbar;
this.headerbar.set_conversation_header(composer.header);
this.headerbar.set_conversation_header(composer.header.headerbar);
get_style_context().add_class("geary-composer-box");
add_css_class("geary-composer-box");
this.halign = Gtk.Align.FILL;
this.vexpand = true;
this.vexpand_set = true;
add(this.composer);
this.child = this.composer;
show();
}
@ -54,8 +54,8 @@ public class Composer.Box : Gtk.Frame, Container {
public void close() {
vanished();
this.headerbar.remove_conversation_header(composer.header);
remove(this.composer);
this.headerbar.remove_conversation_header(composer.header.headerbar);
this.child = null;
destroy();
}

View file

@ -6,8 +6,9 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
[CCode (cname = "components_reflow_box_get_type")]
private extern Type components_reflow_box_get_type();
//XXX GTK4
// [CCode (cname = "components_reflow_box_get_type")]
// private extern Type components_reflow_box_get_type();
/**
* A widget for editing the body of an email message.
@ -33,7 +34,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
private const string ACTION_PASTE_WITHOUT_FORMATTING = "paste-without-formatting";
private const string ACTION_REMOVE_FORMAT = "remove-format";
private const string ACTION_SELECT_ALL = "select-all";
private const string ACTION_SELECT_DICTIONARY = "select-dictionary";
private const string ACTION_SHOW_FORMATTING = "show-formatting";
private const string ACTION_STRIKETHROUGH = "strikethrough";
internal const string ACTION_TEXT_FORMAT = "text-format";
@ -71,7 +71,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
{ ACTION_PASTE_WITHOUT_FORMATTING, on_paste_without_formatting },
{ ACTION_REMOVE_FORMAT, on_remove_format, null, "false" },
{ ACTION_SELECT_ALL, on_select_all },
{ ACTION_SELECT_DICTIONARY, on_select_dictionary },
{ ACTION_SHOW_FORMATTING, on_toggle_action, null, "false",
on_show_formatting },
{ ACTION_STRIKETHROUGH, on_action, null, "false" },
@ -127,7 +126,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
private Menu context_menu_webkit_text_entry;
private Menu context_menu_inspector;
[GtkChild] private unowned Gtk.Grid body_container;
[GtkChild] private unowned Adw.Bin body_bin;
[GtkChild] private unowned Gtk.Label message_overlay_label;
@ -147,15 +146,16 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
[GtkChild] private unowned Gtk.Image font_color_icon;
[GtkChild] private unowned Gtk.MenuButton more_options_button;
private Gtk.GestureMultiPress click_gesture;
private Gtk.GestureClick click_gesture;
internal signal void insert_image(bool from_clipboard);
internal Editor(Application.Configuration config) {
internal Editor(Application.Configuration config, GLib.File cache_dir) {
base_ref();
components_reflow_box_get_type();
//XXX GTK4
// components_reflow_box_get_type();
this.config = config;
Gtk.Builder builder = new Gtk.Builder.from_resource(
@ -168,7 +168,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
this.context_menu_webkit_spelling = (Menu) builder.get_object("context_menu_webkit_spelling");
this.context_menu_webkit_text_entry = (Menu) builder.get_object("context_menu_webkit_text_entry");
this.body = new WebView(config);
this.body = new WebView(config, cache_dir);
this.body.command_stack_changed.connect(on_command_state_changed);
this.body.context_menu.connect(on_context_menu);
this.body.cursor_context_changed.connect(on_cursor_context_changed);
@ -178,12 +178,13 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
this.body.set_hexpand(true);
this.body.set_vexpand(true);
this.body.show();
this.body_container.add(this.body);
this.body_bin.child = this.body;
this.click_gesture = new Gtk.GestureMultiPress(this.body);
this.click_gesture = new Gtk.GestureClick();
this.click_gesture.propagation_phase = CAPTURE;
this.click_gesture.pressed.connect(this.on_button_press);
this.click_gesture.released.connect(this.on_button_release);
this.body.add_controller(this.click_gesture);
this.actions.add_action_entries(ACTIONS, this);
this.actions.change_action_state(
@ -219,16 +220,15 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
base_unref();
}
public override void destroy() {
public override void dispose() {
this.show_background_work_timeout.reset();
this.background_work_pulse.reset();
base.destroy();
base.dispose();
}
/** Adds an action bar to the composer. */
public void add_action_bar(Gtk.ActionBar to_add) {
this.action_bar_box.pack_start(to_add);
this.action_bar_box.reorder_child(to_add, 0);
this.action_bar_box.prepend(to_add);
}
/**
@ -304,10 +304,12 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
}
private async void update_color_icon(Gdk.RGBA color) {
var theme = Gtk.IconTheme.get_default();
var icon = theme.lookup_icon("font-color-symbolic", 16, 0);
var fg_color = Util.Gtk.rgba(0, 0, 0, 1);
this.get_style_context().lookup_color("theme_fg_color", out fg_color);
// XXX GTK4 - need to look into this
#if 0
var theme = Gtk.IconTheme.get_for_display(Gdk.Display.get_default());
var icon = theme.lookup_icon("font-color-symbolic", null, 16, 1, Gtk.TextDirection.NONE, 0);
Gdk.RGBA fg_color = { 0, 0, 0, 1 };
get_style_context().lookup_color("theme_fg_color", out fg_color);
try {
var pixbuf = yield icon.load_symbolic_async(
@ -318,6 +320,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
warning("Could not load icon `font-color-symbolic`!");
this.font_color_icon.icon_name = "font-color-symbolic";
}
#endif
}
private GLib.SimpleAction? get_action(string action_name) {
@ -343,7 +346,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
LinkPopover.Type.EXISTING_LINK, this.pointer_url,
(obj, res) => {
LinkPopover popover = this.new_link_popover.end(res);
popover.set_relative_to(this.body);
popover.set_parent(this.body);
popover.set_pointing_to(location);
popover.popup();
});
@ -352,7 +355,6 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
private bool on_context_menu(WebKit.WebView view,
WebKit.ContextMenu context_menu,
Gdk.Event event,
WebKit.HitTestResult hit_test_result) {
// This is a three step process:
// 1. Work out what existing menu items exist that we want to keep
@ -535,11 +537,8 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
action.set_state(new_state);
update_formatting_toolbar();
this.update_color_icon.begin(Util.Gtk.rgba(0, 0, 0, 0));
}
private void on_select_dictionary(SimpleAction action, Variant? param) {
this.select_dictionary_button.toggled();
Gdk.RGBA color = { 0, 0, 0, 0 };
this.update_color_icon.begin(color);
}
private void on_command_state_changed(bool can_undo, bool can_redo) {
@ -568,18 +567,24 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
}
private void on_copy_link(SimpleAction action, Variant? param) {
Gtk.Clipboard c = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
Gdk.Clipboard c = get_clipboard();
// XXX could this also be the cursor URL? We should be getting
// the target URLn as from the action param
c.set_text(this.pointer_url, -1);
c.store();
c.set_text(this.pointer_url);
c.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
try {
c.store_async.end(res);
} catch (Error err) {
debug("Couldn't store clipboard: %s", err.message);
}
});
}
private void on_paste() {
if (this.body.is_rich_text) {
// Check for pasted image in clipboard
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
bool has_image = clipboard.wait_is_image_available();
Gdk.Clipboard clipboard = get_clipboard();
bool has_image = clipboard.formats.contain_gtype(typeof(Gdk.Texture));
if (has_image) {
insert_image(true);
} else {
@ -643,7 +648,7 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
style.set_state(NORMAL);
});
popover.set_relative_to(this.insert_link_button);
popover.set_parent(this.insert_link_button);
popover.popup();
style.set_state(ACTIVE);
});
@ -684,19 +689,24 @@ public class Composer.Editor : Gtk.Grid, Geary.BaseInterface {
}
private void on_select_color() {
var dialog = new Gtk.ColorChooserDialog(
_("Select Color"),
get_toplevel() as Gtk.Window
);
if (dialog.run() == Gtk.ResponseType.OK) {
var rgba = dialog.get_rgba();
var dialog = new Gtk.ColorDialog();
dialog.title = _("Select Color");
dialog.choose_rgba.begin(get_root() as Gtk.Window, null, null, (obj, res) => {
Gdk.RGBA? rgba = null;
try {
rgba = dialog.choose_rgba.end(res);
} catch (Error err) {
debug("Couldn't select color: %s", err.message);
}
if (rgba == null)
return;
this.body.execute_editing_command_with_argument(
"forecolor", rgba.to_string()
);
this.update_color_icon.begin(rgba);
}
dialog.destroy();
});
}
private void on_action(GLib.SimpleAction action, GLib.Variant? param) {

View file

@ -47,9 +47,10 @@ public class Composer.EmailEntry : Gtk.Entry {
public EmailEntry(Composer.Widget composer) {
changed.connect(on_changed);
key_press_event.connect(on_key_press);
Gtk.EventControllerKey key_controller = new Gtk.EventControllerKey();
key_controller.key_pressed.connect(on_key_pressed);
add_controller(key_controller);
this.composer = composer;
show();
}
/** Marks the entry as being modified. */
@ -71,11 +72,14 @@ public class Composer.EmailEntry : Gtk.Entry {
private void on_changed() {
this.is_modified = true;
//XXX GTK4 see completion class
#if 0
ContactEntryCompletion? completion =
get_completion() as ContactEntryCompletion;
if (completion != null) {
completion.update_model();
}
#endif
if (Geary.String.is_empty_or_whitespace(text)) {
this._addresses = new Geary.RFC822.MailboxAddresses();
@ -92,9 +96,14 @@ public class Composer.EmailEntry : Gtk.Entry {
}
}
private bool on_key_press(Gtk.Widget widget, Gdk.EventKey event) {
private bool on_key_pressed(Gtk.EventControllerKey key_controller,
uint keyval,
uint keycode,
Gdk.ModifierType state) {
bool propagate = Gdk.EVENT_PROPAGATE;
if (event.keyval == Gdk.Key.Tab) {
if (keyval == Gdk.Key.Tab) {
//XXX GTK4 see completion class
#if 0
// If there is a completion entry selected, then use that
ContactEntryCompletion? completion = (
get_completion() as ContactEntryCompletion
@ -104,10 +113,11 @@ public class Composer.EmailEntry : Gtk.Entry {
composer.child_focus(Gtk.DirectionType.TAB_FORWARD);
propagate = Gdk.EVENT_STOP;
}
#endif
}
if (propagate == Gdk.EVENT_PROPAGATE &&
event.keyval != Gdk.Key.Escape) {
keyval != Gdk.Key.Escape) {
// Keyboard shortcuts for undo/redo won't work when the
// completion UI is visible unless we explicitly check for
// them there.
@ -115,9 +125,9 @@ public class Composer.EmailEntry : Gtk.Entry {
// However, don't forward it on if the button pressed is
// Escape, so that the completion is hidden if present
// before the composer is closed.
Gtk.Window? window = get_toplevel() as Gtk.Window;
Gtk.Window? window = get_root() as Gtk.Window;
if (window != null) {
propagate = window.activate_key(event);
propagate = key_controller.forward(window);
}
}
return propagate;

View file

@ -13,7 +13,7 @@
* Widget.PresentationMode.INLINE} or {@link
* Widget.PresentationMode.INLINE_COMPACT} mode.
*/
public class Composer.Embed : Gtk.EventBox, Container {
public class Composer.Embed : Adw.Bin, Container {
private const int MIN_EDITOR_HEIGHT = 200;
@ -25,7 +25,7 @@ public class Composer.Embed : Gtk.EventBox, Container {
/** {@inheritDoc} */
public Gtk.ApplicationWindow? top_window {
get { return get_toplevel() as Gtk.ApplicationWindow; }
get { return get_root() as Gtk.ApplicationWindow; }
}
/** The email this composer was originally a reply to. */
@ -57,26 +57,27 @@ public class Composer.Embed : Gtk.EventBox, Container {
this.outer_scroller = outer_scroller;
get_style_context().add_class("geary-composer-embed");
add_css_class("geary-composer-embed");
this.halign = Gtk.Align.FILL;
this.vexpand = true;
this.vexpand_set = true;
add(composer);
realize.connect(on_realize);
show();
this.child = composer;
//XXX GTK4 see below
// realize.connect(on_realize);
}
/** {@inheritDoc} */
public void close() {
disable_scroll_reroute(this);
//XXX GTK4 see below
// disable_scroll_reroute(this);
vanished();
this.composer.free_header();
remove(this.composer);
destroy();
this.child = null;
}
//XXX GTK4 I think scrolling has changed enough that we might need to rewrite this completely
#if 0
private void on_realize() {
reroute_scroll_handling(this);
}
@ -84,19 +85,19 @@ public class Composer.Embed : Gtk.EventBox, Container {
private void reroute_scroll_handling(Gtk.Widget widget) {
widget.add_events(Gdk.EventMask.SCROLL_MASK | Gdk.EventMask.SMOOTH_SCROLL_MASK);
widget.scroll_event.connect(on_inner_scroll_event);
Gtk.Container? container = widget as Gtk.Container;
if (container != null) {
foreach (Gtk.Widget child in container.get_children())
reroute_scroll_handling(child);
unowned Gtk.Widget? child = widget.get_first_child();
while (child != null) {
reroute_scroll_handling(child);
child = child.get_next_sibling();
}
}
private void disable_scroll_reroute(Gtk.Widget widget) {
widget.scroll_event.disconnect(on_inner_scroll_event);
Gtk.Container? container = widget as Gtk.Container;
if (container != null) {
foreach (Gtk.Widget child in container.get_children())
disable_scroll_reroute(child);
unowned Gtk.Widget? child = widget.get_first_child();
while (child != null) {
disable_scroll_reroute(child);
child = child.get_next_sibling();
}
}
@ -203,5 +204,6 @@ public class Composer.Embed : Gtk.EventBox, Container {
}
return ret;
}
#endif
}

View file

@ -4,8 +4,9 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
//XXX GTK4 rename headerbar to header in files too
[GtkTemplate (ui = "/org/gnome/Geary/composer-headerbar.ui")]
public class Composer.Headerbar : Hdy.HeaderBar {
public class Composer.Header : Adw.Bin {
public bool show_save_and_close {
@ -22,6 +23,8 @@ public class Composer.Headerbar : Hdy.HeaderBar {
private bool is_attached = true;
public Adw.HeaderBar headerbar;
[GtkChild] private unowned Gtk.Box detach_start;
[GtkChild] private unowned Gtk.Box detach_end;
[GtkChild] private unowned Gtk.Button recipients_button;
@ -34,23 +37,24 @@ public class Composer.Headerbar : Hdy.HeaderBar {
public signal void expand_composer();
public Headerbar(Application.Configuration config) {
public Header(Application.Configuration config) {
this.config = config;
this.headerbar = (Adw.HeaderBar) this.child;
Gtk.Settings.get_default().notify["gtk-decoration-layout"].connect(
on_gtk_decoration_layout_changed
);
}
public override void destroy() {
public override void dispose() {
Gtk.Settings.get_default().notify["gtk-decoration-layout"].disconnect(
on_gtk_decoration_layout_changed
);
base.destroy();
base.dispose();
}
public void set_recipients(string label, string tooltip) {
recipients_label.label = label;
recipients_button.tooltip_text = tooltip;
this.recipients_label.label = label;
this.recipients_button.tooltip_text = tooltip;
}
internal void set_mode(Widget.PresentationMode mode) {
@ -77,8 +81,9 @@ public class Composer.Headerbar : Hdy.HeaderBar {
break;
}
this.show_close_button = (mode == Widget.PresentationMode.PANED
&& this.config.desktop_environment != UNITY);
//XXX GTK4 still need to figure out close buttons
// this.show_close_button = (mode == Widget.PresentationMode.PANED
// && this.config.desktop_environment != UNITY);
}
private void set_attached(bool is_attached) {

View file

@ -83,9 +83,9 @@ public class Composer.LinkPopover : Gtk.Popover {
this.url.grab_focus();
}
public override void destroy() {
public override void dispose() {
this.validation_timeout.reset();
base.destroy();
base.dispose();
}
public void set_link_url(string url) {
@ -129,25 +129,24 @@ public class Composer.LinkPopover : Gtk.Popover {
}
}
Gtk.StyleContext style = this.url.get_style_context();
Gtk.EntryIconPosition pos = Gtk.EntryIconPosition.SECONDARY;
if (!is_valid) {
style.add_class(Gtk.STYLE_CLASS_ERROR);
style.remove_class(Gtk.STYLE_CLASS_WARNING);
this.url.add_css_class("error");
this.url.remove_css_class("warning");
this.url.set_icon_from_icon_name(pos, "dialog-error-symbolic");
this.url.set_tooltip_text(
_("Link URL is not correctly formatted, e.g. http://example.com")
);
} else if (!is_nominal) {
style.remove_class(Gtk.STYLE_CLASS_ERROR);
style.add_class(Gtk.STYLE_CLASS_WARNING);
this.url.remove_css_class("error");
this.url.add_css_class("warning");
this.url.set_icon_from_icon_name(pos, "dialog-warning-symbolic");
this.url.set_tooltip_text(
!is_mailto ? _("Invalid link URL") : _("Invalid email address")
);
} else {
style.remove_class(Gtk.STYLE_CLASS_ERROR);
style.remove_class(Gtk.STYLE_CLASS_WARNING);
this.url.remove_css_class("error");
this.url.remove_css_class("warning");
this.url.set_icon_from_icon_name(pos, null);
this.url.set_tooltip_text("");
}

View file

@ -78,7 +78,7 @@ public class Composer.WebView : Components.WebView {
public Gdk.RGBA font_color {
get;
private set;
default = Util.Gtk.rgba(0, 0, 0, 1);
default = { 0, 0, 0, 1 };
}
private uint context = 0;
@ -141,10 +141,8 @@ public class Composer.WebView : Components.WebView {
public signal void image_file_dropped(string filename, string type, uint8[] contents);
public WebView(Application.Configuration config) {
base(config);
add_events(Gdk.EventMask.KEY_PRESS_MASK | Gdk.EventMask.KEY_RELEASE_MASK);
public WebView(Application.Configuration config, GLib.File cache_dir) {
base(config, cache_dir);
this.user_content_manager.add_style_sheet(WebView.app_style);
this.user_content_manager.add_script(WebView.app_script);
@ -248,11 +246,16 @@ public class Composer.WebView : Components.WebView {
* Pastes plain text from the clipboard into the view.
*/
public void paste_plain_text() {
get_clipboard(Gdk.SELECTION_CLIPBOARD).request_text((clipboard, text) => {
if (text != null) {
var clipboard = get_clipboard();
clipboard.read_text_async.begin(null, (obj, res) => {
try {
string text = clipboard.read_text_async.end(res);
if (text != null)
insert_text(text);
}
});
} catch (Error err) {
debug("Couldn't read text from clipboard to paste: %s", err.message);
}
});
}
/**

File diff suppressed because it is too large Load diff

View file

@ -35,7 +35,7 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
public Window(Widget composer, Application.Client application) {
Object(application: application, type: Gtk.WindowType.TOPLEVEL);
Object(application: application);
this.composer = composer;
this.composer.set_mode(DETACHED);
@ -47,42 +47,34 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
// XXX Bug 764622
set_property("name", "GearyComposerWindow");
add(this.composer);
this.child = this.composer;
this.composer.update_window_title();
if (application.config.desktop_environment == UNITY) {
composer.embed_header();
} else {
set_titlebar(this.composer.header);
set_titlebar(this.composer.header.headerbar);
}
this.focus_in_event.connect((w, e) => {
Gtk.EventControllerFocus focus_controller = new Gtk.EventControllerFocus();
focus_controller.enter.connect((controller) => {
application.controller.window_focus_in();
return false;
});
this.focus_out_event.connect((w, e) => {
focus_controller.leave.connect((controller) => {
application.controller.window_focus_out();
return false;
});
show();
set_position(Gtk.WindowPosition.CENTER);
((Gtk.Widget) this).add_controller(focus_controller);
}
/** {@inheritDoc} */
public new void close() {
this.composer.free_header();
remove(this.composer);
destroy();
this.child = null;
}
public override void show() {
Gdk.Display? display = Gdk.Display.get_default();
if (display != null) {
Gdk.Monitor? monitor = display.get_primary_monitor();
if (monitor == null) {
monitor = display.get_monitor_at_point(1, 1);
}
Gdk.Monitor? monitor = display.get_monitor_at_surface(get_surface());
int[] size = this.application.config.get_composer_window_size();
//check if stored values are reasonable
if (monitor != null &&
@ -98,44 +90,36 @@ public class Composer.Window : Gtk.ApplicationWindow, Container {
}
private void save_window_geometry () {
if (!this.is_maximized) {
if (!this.maximized) {
Gdk.Display? display = get_display();
Gdk.Window? window = get_window();
if (display != null && window != null) {
Gdk.Monitor monitor = display.get_monitor_at_window(window);
int width = 0;
int height = 0;
get_size(out width, out height);
Gdk.Surface? surface = get_surface();
if (display != null && surface != null) {
Gdk.Monitor monitor = display.get_monitor_at_surface(surface);
// Only store if the values are reasonable-looking.
if (width > 0 && width <= monitor.geometry.width &&
height > 0 && height <= monitor.geometry.height) {
if (this.default_width > 0 && this.default_width <= monitor.geometry.width &&
this.default_height > 0 && this.default_height <= monitor.geometry.height) {
this.application.config.set_composer_window_size({
width, height
this.default_width, this.default_height
});
}
}
}
}
// Fired on window resize. Save window size for the next start.
public override void size_allocate(Gtk.Allocation allocation) {
base.size_allocate(allocation);
public override bool close_request() {
save_window_geometry();
this.save_window_geometry();
}
public override bool delete_event(Gdk.EventAny event) {
// Use the child instead of the `composer` property so we
// don't check with the composer if it has already been
// removed from the container.
Widget? child = get_child() as Widget;
bool ret = Gdk.EVENT_PROPAGATE;
if (child != null &&
child.conditional_close(true) == CANCELLED) {
ret = Gdk.EVENT_STOP;
}
// XXX GTK4 - This is now an async method, I'm not sure we can still stop htis?
// if (child != null &&
// child.conditional_close(true) == CANCELLED) {
// ret = Gdk.EVENT_STOP;
// }
return ret;
}

View file

@ -6,7 +6,9 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
public class ContactEntryCompletion : Adw.EntryRow, Geary.BaseInterface {
//XXX GTK4 probably want to create a ContactEntryRow or something like that, GtkEntryCompletion is deprecated
#if 0
// Minimum visibility for the contact to appear in autocompletion.
@ -365,4 +367,5 @@ public class ContactEntryCompletion : Gtk.EntryCompletion, Geary.BaseInterface {
return true;
}
#endif
}

View file

@ -60,8 +60,10 @@ public class SpellCheckPopover {
this.is_lang_visible = is_active || is_visible;
Gtk.Box box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 6);
box.margin = 6;
box.margin_start = 12;
box.margin_end = 6;
box.margin_top = 6;
box.margin_bottom = 6;
lang_name = Util.I18n.language_name_from_locale(lang_code);
country_name = Util.I18n.country_name_from_locale(lang_code);
@ -80,28 +82,27 @@ public class SpellCheckPopover {
country_label.halign = Gtk.Align.START;
country_label.ellipsize = END;
country_label.xalign = 0;
country_label.get_style_context().add_class("dim-label");
country_label.add_css_class("dim-label");
label_box.add(label);
label_box.add(country_label);
box.pack_start(label_box, false, false);
label_box.append(label);
label_box.append(country_label);
box.append(label_box);
} else {
box.pack_start(label, false, false);
box.append(label);
}
Gtk.IconSize sz = Gtk.IconSize.SMALL_TOOLBAR;
active_image = new Gtk.Image.from_icon_name("object-select-symbolic", sz);
active_image = new Gtk.Image.from_icon_name("object-select-symbolic");
this.visibility_button = new Gtk.Button();
this.visibility_button.set_relief(Gtk.ReliefStyle.NONE);
box.pack_start(active_image, false, false, 6);
box.pack_start(this.visibility_button, true, true);
this.visibility_button.add_css_class("flat");
box.append(active_image);
box.append(this.visibility_button);
this.visibility_button.halign = Gtk.Align.END; // Make the button stay at the right end of the screen
this.visibility_button.valign = CENTER;
this.visibility_button.clicked.connect(on_visibility_clicked);
update_images();
add(box);
this.child = box;
}
public bool is_lang_active() {
@ -109,23 +110,21 @@ public class SpellCheckPopover {
}
private void update_images() {
Gtk.IconSize sz = Gtk.IconSize.SMALL_TOOLBAR;
switch (lang_active) {
case SpellCheckStatus.ACTIVE:
active_image.set_from_icon_name("object-select-symbolic", sz);
this.active_image.set_from_icon_name("object-select-symbolic");
break;
case SpellCheckStatus.INACTIVE:
active_image.clear();
this.active_image.clear();
break;
}
if (is_lang_visible) {
this.visibility_button.set_image(new Gtk.Image.from_icon_name("list-remove-symbolic", sz));
this.visibility_button.icon_name = "list-remove-symbolic";
this.visibility_button.set_tooltip_text(_("Remove this language from the preferred list"));
}
else {
this.visibility_button.set_image(new Gtk.Image.from_icon_name("list-add-symbolic", sz));
this.visibility_button.icon_name = "list-add-symbolic";
this.visibility_button.set_tooltip_text(_("Add this language to the preferred list"));
}
}
@ -193,7 +192,7 @@ public class SpellCheckPopover {
}
public SpellCheckPopover(Gtk.MenuButton button, Application.Configuration config) {
this.popover = new Gtk.Popover(button);
this.popover = new Gtk.Popover();
button.popover = this.popover;
this.config = config;
this.selected_rows = new GLib.GenericSet<string>(GLib.str_hash, GLib.str_equal);
@ -224,12 +223,11 @@ public class SpellCheckPopover {
search_box = new Gtk.SearchEntry();
search_box.set_placeholder_text(_("Search for more languages"));
search_box.changed.connect(on_search_box_changed);
search_box.grab_focus.connect(on_search_box_grab_focus);
content.pack_start(search_box, false, true);
content.append(search_box);
view = new Gtk.ScrolledWindow(null, null);
view.set_shadow_type(Gtk.ShadowType.IN);
view = new Gtk.ScrolledWindow();
view.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC);
view.vexpand = true;
langs_list = new Gtk.ListBox();
langs_list.set_selection_mode(Gtk.SelectionMode.NONE);
@ -239,7 +237,7 @@ public class SpellCheckPopover {
lang in enabled_langs,
lang in visible_langs
);
langs_list.add(row);
langs_list.append(row);
if (row.is_lang_active())
selected_rows.add(lang);
@ -248,14 +246,14 @@ public class SpellCheckPopover {
row.visibility_changed.connect(this.on_row_visibility_changed);
}
langs_list.row_activated.connect(on_row_activated);
view.add(langs_list);
view.child = langs_list;
content.pack_start(view, true, true);
content.append(view);
langs_list.set_filter_func(this.filter_function);
langs_list.set_header_func(this.header_function);
popover.add(content);
this.popover.child = content;
// Make sure that the search box does not get the focus first. We want it to have it only
// if the user wants to perform an extended search.
@ -265,8 +263,8 @@ public class SpellCheckPopover {
content.set_margin_top(6);
content.set_margin_bottom(6);
popover.show.connect(this.on_shown);
popover.set_size_request(360, 350);
this.popover.show.connect(this.on_shown);
this.popover.set_size_request(360, 350);
}
private void on_row_activated(Gtk.ListBoxRow row) {
@ -281,10 +279,6 @@ public class SpellCheckPopover {
langs_list.invalidate_filter();
}
private void on_search_box_grab_focus() {
set_expanded(true);
}
private void set_expanded(bool expanded) {
is_expanded = expanded;
langs_list.invalidate_filter();
@ -295,8 +289,6 @@ public class SpellCheckPopover {
content.set_focus_child(view);
is_expanded = false;
langs_list.invalidate_filter();
popover.show_all();
}
private void on_row_enabled_changed(SpellCheckLangRow row,

View file

@ -110,10 +110,10 @@ internal class ConversationList.Row : Gtk.ListBoxRow {
private void set_button_active(bool active) {
this.selected_button.set_active(active);
if (active) {
this.get_style_context().add_class("selected");
this.add_css_class("selected");
this.set_state_flags(Gtk.StateFlags.SELECTED, false);
} else {
this.get_style_context().remove_class("selected");
this.remove_css_class("selected");
this.unset_state_flags(Gtk.StateFlags.SELECTED);
}
}
@ -134,9 +134,9 @@ internal class ConversationList.Row : Gtk.ListBoxRow {
private void update_flags(Geary.Email? email) {
if (conversation.is_unread()) {
get_style_context().add_class("unread");
add_css_class("unread");
} else {
get_style_context().remove_class("unread");
remove_css_class("unread");
}
if (conversation.is_flagged()) {

View file

@ -11,7 +11,8 @@
*
*/
[GtkTemplate (ui = "/org/gnome/Geary/conversation-list-view.ui")]
public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
public class ConversationList.View : Adw.Bin, Geary.BaseInterface {
/**
* The fields that must be available on any ConversationMonitor
* passed to ConversationList.View
@ -42,11 +43,11 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
private Application.Configuration config;
private Gtk.GestureMultiPress press_gesture;
private Gtk.GestureLongPress long_press_gesture;
private Gtk.EventControllerKey key_event_controller;
private Gdk.ModifierType last_modifier_type;
[GtkChild] public unowned Gtk.ScrolledWindow scrolled_window;
[GtkChild] private unowned Gtk.ListBox list;
/*
@ -64,14 +65,10 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
this.list.set_header_func(header_func);
this.vadjustment.value_changed.connect(maybe_load_more);
this.vadjustment.value_changed.connect(update_visible_conversations);
this.scrolled_window.vadjustment.value_changed.connect(maybe_load_more);
this.scrolled_window.vadjustment.value_changed.connect(update_visible_conversations);
this.press_gesture = new Gtk.GestureMultiPress(this.list);
this.press_gesture.set_button(0);
this.press_gesture.released.connect(on_press_gesture_released);
this.long_press_gesture = new Gtk.GestureLongPress(this.list);
this.long_press_gesture = new Gtk.GestureLongPress();
this.long_press_gesture.propagation_phase = CAPTURE;
this.long_press_gesture.pressed.connect((n_press, x, y) => {
Row? row = (Row) this.list.get_row_at_y((int) y);
@ -80,14 +77,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
this.selection_mode_enabled = true;
}
});
this.list.add_controller(this.long_press_gesture);
this.key_event_controller = new Gtk.EventControllerKey(this.list);
this.key_event_controller.key_pressed.connect(on_key_event_controller_key_pressed);
Gtk.drag_source_set(this.list, Gdk.ModifierType.BUTTON1_MASK, FolderList.Tree.TARGET_ENTRY_LIST,
Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
this.list.drag_begin.connect(on_drag_begin);
this.list.drag_end.connect(on_drag_end);
//XXX GTK4 - check if started on click
var drag_source = new Gtk.DragSource();
drag_source.drag_begin.connect(on_drag_begin);
drag_source.drag_end.connect(on_drag_end);
this.list.add_controller(drag_source);
}
static construct {
@ -113,10 +109,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
* automatically but instead it must be externally scheduled
*/
public void refresh_times() {
this.list.foreach((child) => {
var row = (Row) child;
int i = 0;
Row? row = this.list.get_row_at_index(0) as Row;
while (row != null) {
row.refresh_time();
});
i++;
row = this.list.get_row_at_index(i) as Row;
}
}
// -------------------
@ -204,7 +203,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
private Gtk.Popover construct_popover(Row row, uint selection_size) {
GLib.Menu context_menu_model = new GLib.Menu();
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
if (!main.is_shift_down) {
@ -303,13 +302,8 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
);
context_menu_model.append_section(null, actions_section);
// Use a popover rather than a regular context menu since
// the latter grabs the event queue, so the MainWindow
// will not receive events if the user releases Shift,
// making the trash/delete header bar state wrong.
Gtk.Popover context_menu = new Gtk.Popover.from_model(
row, context_menu_model
);
var context_menu = new Gtk.PopoverMenu.from_model(context_menu_model);
context_menu.set_parent(row);
return context_menu;
}
@ -347,13 +341,16 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
* If a conversation is not present in the ListBox, it is ignored.
*/
public void select_conversations(Gee.Collection<Geary.App.Conversation> selection) {
this.list.foreach((child) => {
var row = (Row) child;
int i = 0;
Row? row = this.list.get_row_at_index(0) as Row;
while (row != null) {
Geary.App.Conversation conversation = row.conversation;
if (selection.contains(conversation)) {
this.list.select_row(row);
}
});
i++;
row = this.list.get_row_at_index(i) as Row;
}
}
/**
@ -466,9 +463,9 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
*/
private int VISIBILITY_UPDATE_DELAY_MS = 1000;
/**
* The set of all conversations currently displayed in the viewport
*/
/**
* The set of all conversations currently displayed in the viewport
*/
public Gee.Set<Geary.App.Conversation> visible_conversations {get; private set; default = new Gee.HashSet<Geary.App.Conversation>(); }
private Geary.Scheduler.Scheduled? scheduled_visible_update;
@ -482,7 +479,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
scheduled_visible_update = Geary.Scheduler.after_msec(VISIBILITY_UPDATE_DELAY_MS, () => {
var visible = new Gee.HashSet<Geary.App.Conversation>();
Gtk.ListBoxRow? first = this.list.get_row_at_y((int) this.vadjustment.value);
Gtk.ListBoxRow? first = this.list.get_row_at_y((int) this.scrolled_window.vadjustment.value);
if (first == null) {
this.visible_conversations = visible;
@ -492,7 +489,7 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
uint start_index = ((uint) first.get_index());
uint end_index = uint.min(
// Assume that all messages are the same height
start_index + (uint) (this.vadjustment.page_size / first.get_allocated_height()),
start_index + (uint) (this.scrolled_window.vadjustment.page_size / first.get_allocated_height()),
this.model.get_n_items()
);
@ -581,29 +578,34 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
* Update conversation row
*/
private void on_conversation_updated(Geary.App.Conversation convo) {
this.list.foreach((child) => {
var row = (Row) child;
int i = 0;
Row? row = this.list.get_row_at_index(0) as Row;
while (row != null) {
if (convo == row.conversation) {
row.update();
}
});
i++;
row = this.list.get_row_at_index(i) as Row;
}
}
// ----------
// Gestures
// ----------
private void on_press_gesture_released(int n_press, double x, double y) {
[GtkCallback]
private void on_press_gesture_released(Gtk.GestureClick click_gesture, int n_press, double x, double y) {
var row = (Row) this.list.get_row_at_y((int) y);
if (row == null)
return;
var button = this.press_gesture.get_current_button();
if (button == 1) {
Gdk.EventSequence sequence = this.press_gesture.get_current_sequence();
Gdk.Event event = this.press_gesture.get_last_event(sequence);
event.get_state(out this.last_modifier_type);
var button = click_gesture.get_current_button();
if (button == Gdk.BUTTON_PRIMARY) {
Gdk.EventSequence sequence = click_gesture.get_current_sequence();
Gdk.Event event = click_gesture.get_last_event(sequence);
this.last_modifier_type = event.get_modifier_state();
if (!this.selection_mode_enabled) {
if ((this.last_modifier_type & Gdk.ModifierType.SHIFT_MASK) ==
Gdk.ModifierType.SHIFT_MASK ||
@ -614,19 +616,19 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
conversation_activated(((Row) row).conversation, 1);
}
}
} else if (button == 2) {
} else if (button == Gdk.BUTTON_MIDDLE) {
conversation_activated(row.conversation, 2);
} else if (button == 3) {
var rect = Gdk.Rectangle();
row.translate_coordinates(this.list, 0, 0, out rect.x, out rect.y);
rect.x = (int) x;
rect.y = (int) y - rect.y;
rect.width = rect.height = 0;
} else if (button == Gdk.BUTTON_SECONDARY) {
Graphene.Point p = { (float) x, (float) y };
Graphene.Point p_row;
this.list.compute_point(row, p, out p_row);
Gdk.Rectangle rect = { (int) p_row.x, (int) p_row.y, 0, 0 };
context_menu(row, rect);
}
}
private bool on_key_event_controller_key_pressed(uint keyval, uint keycode, Gdk.ModifierType modifier_type) {
[GtkCallback]
private bool on_key_pressed(uint keyval, uint keycode, Gdk.ModifierType modifier_type) {
switch (keyval) {
case Gdk.Key.Up:
case Gdk.Key.Down:
@ -646,19 +648,17 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
}
/**
* Widgets used as drag icons have to be explicitly destroyed after the drag
* so we track the widget as a private member
*/
/**
* Widgets used as drag icons have to be explicitly destroyed after the drag
* so we track the widget as a private member
*/
private Row? drag_widget = null;
private void on_drag_begin(Gdk.DragContext ctx) {
private void on_drag_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
int screen_x, screen_y;
Gdk.ModifierType _modifier;
this.get_window().get_device_position(ctx.get_device(), out screen_x, out screen_y, out _modifier);
Row? row = this.list.get_row_at_y(screen_y + (int) this.vadjustment.value) as Row?;
Row? row = this.list.get_row_at_y((int) this.scrolled_window.vadjustment.value) as Row?;
if (row != null) {
// If the user has a selection but drags starting from an unselected
// row, we need to set the selection to that row
@ -669,18 +669,18 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
this.drag_widget = new Row(this.config, row.conversation, false);
this.drag_widget.width_request = row.get_allocated_width();
this.drag_widget.get_style_context().add_class("drag-n-drop");
this.drag_widget.add_css_class("drag-n-drop");
this.drag_widget.visible = true;
int hot_x, hot_y;
this.translate_coordinates(row, screen_x, screen_y, out hot_x, out hot_y);
Gtk.drag_set_icon_widget(ctx, this.drag_widget, hot_x, hot_y);
double hot_x, hot_y;
// XXX GTK4 - this might be a bit more work to do properly wiht Paintable
translate_coordinates(row, 0, 0, out hot_x, out hot_y);
// drag_source.set_icon(this.drag_widget, hot_x, hot_y);
}
}
private void on_drag_end(Gdk.DragContext ctx) {
private void on_drag_end(Gtk.DragSource drag_source, Gdk.Drag drag, bool delete_data) {
if (this.drag_widget != null) {
this.drag_widget.destroy();
this.drag_widget = null;
}
}
@ -706,10 +706,13 @@ public class ConversationList.View : Gtk.ScrolledWindow, Geary.BaseInterface {
}
private void on_selection_mode_changed() {
this.list.foreach((child) => {
var row = (Row) child;
int i = 0;
Row? row = this.list.get_row_at_index(0) as Row;
while (row != null) {
row.set_selection_enabled(this.selection_mode_enabled);
});
i++;
row = this.list.get_row_at_index(i) as Row;
}
if (this.selection_mode_enabled) {
this.to_restore_row = this.list.get_selected_row();

View file

@ -43,9 +43,9 @@ public class Conversation.ContactPopover : Gtk.Popover {
private Application.Configuration config;
[GtkChild] private unowned Gtk.Grid contact_pane;
[GtkChild] private unowned Gtk.Box contact_pane;
[GtkChild] private unowned Hdy.Avatar avatar;
[GtkChild] private unowned Adw.Avatar avatar;
[GtkChild] private unowned Gtk.Label contact_name;
@ -55,13 +55,13 @@ public class Conversation.ContactPopover : Gtk.Popover {
[GtkChild] private unowned Gtk.Button unstarred_button;
[GtkChild] private unowned Gtk.ModelButton open_button;
[GtkChild] private unowned Gtk.Button open_button;
[GtkChild] private unowned Gtk.ModelButton save_button;
[GtkChild] private unowned Gtk.Button save_button;
[GtkChild] private unowned Gtk.ModelButton load_remote_button;
[GtkChild] private unowned Gtk.CheckButton load_remote_button;
[GtkChild] private unowned Gtk.Grid deceptive_pane;
[GtkChild] private unowned Gtk.Box deceptive_pane;
[GtkChild] private unowned Gtk.Label forged_email_label;
@ -79,22 +79,20 @@ public class Conversation.ContactPopover : Gtk.Popover {
Geary.RFC822.MailboxAddress mailbox,
Application.Configuration config) {
this.relative_to = relative_to;
set_parent(relative_to);
this.contact = contact;
this.mailbox = mailbox;
this.config = config;
this.load_remote_button.role = CHECK;
this.contact.bind_property("display-name",
this.avatar,
"text",
BindingFlags.SYNC_CREATE);
this.contact.bind_property("avatar",
this.avatar,
"loadable-icon",
BindingFlags.SYNC_CREATE);
load_avatar.begin((obj, res) => { load_avatar.end(res); });
this.contact.notify["avatar"].connect((obj, pspec) => {
load_avatar.begin((obj, res) => { load_avatar.end(res); });
});
this.actions.add_action_entries(ACTION_ENTRIES, this);
insert_action_group(ACTION_GROUP, this.actions);
@ -106,10 +104,10 @@ public class Conversation.ContactPopover : Gtk.Popover {
/**
* Starts loading the avatar for the message's sender.
*/
public override void destroy() {
public override void dispose() {
this.contact.changed.disconnect(this.on_contact_changed);
this.load_cancellable.cancel();
base.destroy();
base.dispose();
}
private void update() {
@ -182,13 +180,34 @@ public class Conversation.ContactPopover : Gtk.Popover {
}
}
private async void load_avatar() {
if (this.contact.avatar == null) {
this.avatar.custom_image = null;
return;
}
try {
GLib.InputStream stream = yield this.contact.avatar.load_async(
avatar.size, this.load_cancellable
);
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(
stream, avatar.size, avatar.size, true, load_cancellable
);
this.avatar.custom_image = Gdk.Texture.for_pixbuf(pixbuf);
} catch (GLib.Error err) {
debug("Couldn't load avatar for contact: %s", err.message);
this.avatar.custom_image = null;
}
}
private async void set_load_remote_resources(bool enabled) {
try {
// Remove all contact email domains from trusted list
// Otherwise, user may not understand why images are always shown
if (!enabled) {
var email_addresses = this.contact.email_addresses;
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
this.config.remove_images_trusted_domain(email.domain);
}
}
@ -214,9 +233,15 @@ public class Conversation.ContactPopover : Gtk.Popover {
}
private void on_copy_email() {
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
clipboard.set_text(this.mailbox.to_full_display(), -1);
clipboard.store();
Gdk.Clipboard clipboard = get_clipboard();
clipboard.set_text(this.mailbox.to_full_display());
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
try {
clipboard.store_async.end(res);
} catch (Error err) {
debug("Couldn't copy email to clipboard: %s", err.message);
}
});
}
private void on_load_remote(GLib.SimpleAction action) {
@ -225,7 +250,7 @@ public class Conversation.ContactPopover : Gtk.Popover {
}
private void on_new_conversation() {
var main = this.get_toplevel() as Application.MainWindow;
var main = this.get_root() as Application.MainWindow;
if (main != null) {
main.application.new_composer.begin(this.mailbox);
}
@ -240,7 +265,7 @@ public class Conversation.ContactPopover : Gtk.Popover {
}
private void on_show_conversations() {
var main = this.get_toplevel() as Application.MainWindow;
var main = this.get_root() as Application.MainWindow;
if (main != null) {
main.show_search_bar("from:%s".printf(this.mailbox.address));
}

View file

@ -177,12 +177,12 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
/** Determines if the email has been manually marked as being read. */
public bool is_manually_read {
get { return get_style_context().has_class(MANUAL_READ_CLASS); }
get { return has_css_class(MANUAL_READ_CLASS); }
set {
if (value) {
get_style_context().add_class(MANUAL_READ_CLASS);
add_css_class(MANUAL_READ_CLASS);
} else {
get_style_context().remove_class(MANUAL_READ_CLASS);
remove_css_class(MANUAL_READ_CLASS);
}
}
}
@ -237,7 +237,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
// window, for updating email menu trash/delete actions.
private bool shift_handler_installed = false;
[GtkChild] private unowned Gtk.Grid actions;
[GtkChild] private unowned Gtk.Box actions;
[GtkChild] private unowned Gtk.Button attachments_button;
@ -247,7 +247,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
[GtkChild] private unowned Gtk.MenuButton email_menubutton;
[GtkChild] private unowned Gtk.Grid sub_messages;
[GtkChild] private unowned Gtk.Box sub_messages;
/** Fired when a internal link is activated */
@ -284,7 +284,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
new Geary.Nonblocking.Spinlock(load_cancellable);
if (is_sent) {
get_style_context().add_class(SENT_CLASS);
add_css_class(SENT_CLASS);
}
// Construct the view for the primary message, hook into it
@ -295,7 +295,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
this.contacts,
this.config
);
this.primary_message.summary.add(this.actions);
this.primary_message.summary.append(this.actions);
connect_message_view_signals(this.primary_message);
// Wire up the rest of the UI
@ -310,7 +310,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
BODY_LOAD_TIMEOUT_MSEC, this.on_body_loading_timeout
);
pack_start(this.primary_message, true, true, 0);
append(this.primary_message);
update_email_state();
}
@ -482,7 +482,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
/** Displays the raw RFC 822 source for this email. */
public async void view_source() {
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
Geary.Email email = this.email;
try {
@ -567,7 +567,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
string js = "geary.addPrintHeaders(" + generator.to_data(null) + ");";
yield this.primary_message.evaluate_javascript(js, null);
Gtk.Window? window = get_toplevel() as Gtk.Window;
Gtk.Window? window = get_root() as Gtk.Window;
WebKit.PrintOperation op = this.primary_message.new_print_operation();
Gtk.PrintSettings settings = new Gtk.PrintSettings();
@ -694,7 +694,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
Gee.List<Geary.RFC822.Message> sub_messages = message.get_sub_messages();
if (sub_messages.size > 0) {
this.primary_message.body_container.add(this.sub_messages);
this.primary_message.body_container.append(this.sub_messages);
}
foreach (Geary.RFC822.Message sub_message in sub_messages) {
ConversationMessage attached_message =
@ -706,7 +706,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
);
connect_message_view_signals(attached_message);
attached_message.add_internal_resources(cid_resources);
this.sub_messages.add(attached_message);
this.sub_messages.append(attached_message);
this._attached_messages.add(attached_message);
attached_message.load_contacts.begin(this.load_cancellable);
yield attached_message.load_message_body(
@ -719,20 +719,18 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
}
private void update_email_state() {
Gtk.StyleContext style = get_style_context();
if (this.is_unread) {
style.add_class(UNREAD_CLASS);
add_css_class(UNREAD_CLASS);
} else {
style.remove_class(UNREAD_CLASS);
remove_css_class(UNREAD_CLASS);
}
if (this.is_starred) {
style.add_class(STARRED_CLASS);
add_css_class(STARRED_CLASS);
this.star_button.hide();
this.unstar_button.show();
} else {
style.remove_class(STARRED_CLASS);
remove_css_class(STARRED_CLASS);
this.star_button.show();
this.unstar_button.hide();
}
@ -756,7 +754,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
this.conversation.base_folder is Geary.FolderSupport.Remove
);
bool is_shift_down = false;
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
is_shift_down = main.is_shift_down;
@ -805,7 +803,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
}
);
this.email_menubutton.popover.bind_model(new_model, null);
this.email_menubutton.menu_model = new_model;
this.email_menubutton.popover.grab_focus();
}
}
@ -814,13 +812,13 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
private void update_displayed_attachments() {
bool has_attachments = !this.displayed_attachments.is_empty;
this.attachments_button.set_visible(has_attachments);
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (has_attachments && main != null) {
this.attachments_pane = new Components.AttachmentPane(
false, main.attachments
);
this.primary_message.body_container.add(this.attachments_pane);
this.primary_message.body_container.append(this.attachments_pane);
foreach (var attachment in this.displayed_attachments) {
this.attachments_pane.add_attachment(
@ -834,7 +832,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
this.message_body_state = FAILED;
this.primary_message.show_load_error_pane();
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
Geary.AccountInformation account = this.email_store.account.information;
main.application.controller.report_problem(
@ -853,16 +851,13 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
}
private void activate_email_action(string name) {
GLib.ActionGroup? email_actions = get_action_group(
ConversationListBox.EMAIL_ACTION_GROUP_NAME
);
if (email_actions != null) {
email_actions.activate_action(name, this.email.id.to_variant());
}
//XXX GTK4 check if this works still
string action_name = ConversationListBox.EMAIL_ACTION_GROUP_NAME + "." + name;
activate_action_variant(action_name, this.email.id.to_variant());
}
[GtkCallback]
private void on_email_menu() {
private void on_email_menu(Gtk.MenuButton email_menubutton) {
update_email_menu();
}
@ -885,7 +880,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
private void on_save_image(string uri,
string? alt_text,
Geary.Memory.Buffer? content) {
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
if (main != null) {
if (uri.has_prefix(Components.WebView.CID_URL_PREFIX)) {
string cid = uri.substring(Components.WebView.CID_URL_PREFIX.length);

View file

@ -18,7 +18,7 @@
* ConversationListBox sorts by the {@link Geary.Email.date} field
* (the Date: header), as that's the date displayed to the user.
*/
public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public class ConversationListBox : Adw.Bin, Geary.BaseInterface {
/** Fields that must be available for listing conversation email. */
public const Geary.Email.Field REQUIRED_FIELDS = (
@ -194,17 +194,18 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public void unmark_terms() {
cancel();
this.list.foreach((child) => {
EmailRow? row = child as EmailRow;
if (row != null) {
if (row.is_search_match) {
row.is_search_match = false;
foreach (ConversationMessage msg_view in row.view) {
msg_view.unmark_search_terms();
}
}
for (int i = 0; true; i++) {
unowned var row = this.list.listbox.get_row_at_index(i) as EmailRow;
if (row == null)
break;
if (row.is_search_match) {
row.is_search_match = false;
foreach (ConversationMessage msg_view in row.view) {
msg_view.unmark_search_terms();
}
});
}
}
}
public void cancel() {
@ -324,58 +325,46 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// Enables firing the should_scroll signal when this row is
// allocated a size
public void enable_should_scroll() {
this.size_allocate.connect(on_size_allocate);
//XXX GTK4 - once we work with models, we won't need this
// this.size_allocate.connect(on_size_allocate);
}
private void update_css_class() {
if (this.is_expanded)
get_style_context().add_class(EXPANDED_CLASS);
add_css_class(EXPANDED_CLASS);
else
get_style_context().remove_class(EXPANDED_CLASS);
remove_css_class(EXPANDED_CLASS);
update_previous_sibling_css_class();
}
// This is mostly taken form libhandy HdyExpanderRow
private Gtk.Widget? get_previous_sibling() {
if (this.parent is Gtk.Container) {
var siblings = this.parent.get_children();
unowned List<weak Gtk.Widget> l;
for (l = siblings; l != null && l.next != null && l.next.data != this; l = l.next);
if (l != null && l.next != null && l.next.data == this) {
return l.data;
}
}
return null;
}
private void update_previous_sibling_css_class() {
var previous_sibling = get_previous_sibling();
var previous_sibling = get_prev_sibling();
if (previous_sibling != null) {
if (this.is_expanded)
previous_sibling.get_style_context().add_class("geary-expanded-previous-sibling");
previous_sibling.add_css_class("geary-expanded-previous-sibling");
else
previous_sibling.get_style_context().remove_class("geary-expanded-previous-sibling");
previous_sibling.remove_css_class("geary-expanded-previous-sibling");
}
}
protected inline void set_style_context_class(string class_name, bool value) {
if (value) {
get_style_context().add_class(class_name);
add_css_class(class_name);
} else {
get_style_context().remove_class(class_name);
remove_css_class(class_name);
}
}
//XXX GTK4 - once we work with models, we won't need this
#if 0
protected void on_size_allocate() {
// Disable should_scroll so we don't keep on scrolling
// later, like when the window has been resized.
this.size_allocate.disconnect(on_size_allocate);
should_scroll();
}
#endif
}
@ -391,7 +380,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// Does the row contain an email matching the current search?
public bool is_search_match {
get { return get_style_context().has_class(MATCH_CLASS); }
get { return has_css_class(MATCH_CLASS); }
set {
set_style_context_class(MATCH_CLASS, value);
this.is_pinned = value;
@ -407,7 +396,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public EmailRow(ConversationEmail view) {
base(view.email);
this.view = view;
add(view);
this.child = view;
}
public override async void expand()
@ -446,14 +435,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public LoadingRow() {
base(null);
get_style_context().add_class(LOADING_CLASS);
add_css_class(LOADING_CLASS);
Gtk.Spinner spinner = new Gtk.Spinner();
spinner.height_request = 16;
spinner.width_request = 16;
spinner.show();
spinner.start();
add(spinner);
this.child = spinner;
}
}
@ -470,7 +457,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
base(view.referred);
this.view = view;
this.is_expanded = true;
add(this.view);
this.child = this.view;
this.focus_on_click = false;
}
@ -479,23 +466,13 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
static construct {
// Set up custom keybindings
unowned Gtk.BindingSet bindings = Gtk.BindingSet.by_class(
(ObjectClass) typeof(ConversationListBox).class_ref()
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.space, 0, "focus-next", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.KP_Space, 0, "focus-next", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.space, Gdk.ModifierType.SHIFT_MASK, "focus-prev", 0
);
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.KP_Space, Gdk.ModifierType.SHIFT_MASK, "focus-prev", 0
);
add_shortcut(new Gtk.Shortcut(Gtk.ShortcutTrigger.parse_string("Space"),
new Gtk.NamedAction("focus-next")));
add_shortcut(new Gtk.Shortcut(Gtk.ShortcutTrigger.parse_string("<Shift>Space"),
new Gtk.NamedAction("focus-prev")));
//XXX GTK4
#if 0
Gtk.BindingEntry.add_signal(
bindings, Gdk.Key.Up, 0, "scroll", 1,
typeof(Gtk.ScrollType), Gtk.ScrollType.STEP_UP
@ -520,6 +497,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
bindings, Gdk.Key.End, 0, "scroll", 1,
typeof(Gtk.ScrollType), Gtk.ScrollType.END
);
#endif
}
private static int on_sort(Gtk.ListBoxRow row1, Gtk.ListBoxRow row2) {
@ -535,6 +513,8 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
return Geary.Email.compare_sent_date_ascending(email1, email2);
}
internal Gtk.ListBox listbox { get; set; default = new Gtk.ListBox(); }
/** Conversation being displayed. */
public Geary.App.Conversation conversation { get; private set; }
@ -588,7 +568,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
var handled = false;
var composer = this.current_composer;
if (composer != null) {
var window = get_toplevel() as Gtk.Window;
var window = get_root() as Gtk.Window;
if (window != null) {
var focused = window.get_focus();
if (focused != null &&
@ -612,7 +592,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
}
if (!handled) {
Gtk.Adjustment adj = get_adjustment();
Gtk.Adjustment adj = this.listbox.get_adjustment();
double value = adj.get_value();
switch (type) {
case Gtk.ScrollType.STEP_UP:
@ -645,14 +625,14 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
/** Keyboard action to shift focus to the next message, if any. */
[Signal (action=true)]
public virtual signal void focus_next() {
this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1);
this.listbox.move_cursor(Gtk.MovementStep.DISPLAY_LINES, 1, false, false);
this.mark_read_timer.start();
}
/** Keyboard action to shift focus to the prev message, if any. */
[Signal (action=true)]
public virtual signal void focus_prev() {
this.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1);
this.listbox.move_cursor(Gtk.MovementStep.DISPLAY_LINES, -1, false, false);
this.mark_read_timer.start();
}
@ -702,23 +682,20 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
MARK_READ_TIMEOUT_MSEC, this.check_mark_read
);
this.selection_mode = NONE;
this.child = this.listbox;
this.listbox.selection_mode = NONE;
this.listbox.valign = Gtk.Align.START;
get_style_context().add_class("content");
get_style_context().add_class("background");
get_style_context().add_class("conversation-listbox");
this.listbox.add_css_class("content");
this.listbox.add_css_class("conversation-listbox");
/* we need to update the previous sibling style class when rows are added or removed */
add.connect(update_previous_sibling_css_class);
remove.connect(update_previous_sibling_css_class);
set_adjustment(adjustment);
set_sort_func(ConversationListBox.on_sort);
this.listbox.set_adjustment(adjustment);
this.listbox.set_sort_func(ConversationListBox.on_sort);
this.email_actions.add_action_entries(email_action_entries, this);
insert_action_group(EMAIL_ACTION_GROUP_NAME, this.email_actions);
this.row_activated.connect(on_row_activated);
this.listbox.row_activated.connect(on_row_activated);
this.conversation.appended.connect(on_conversation_appended);
this.conversation.trimmed.connect(on_conversation_trimmed);
@ -729,34 +706,45 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
base_unref();
}
public override void destroy() {
public override void dispose() {
this.search.cancel();
this.cancellable.cancel();
this.email_rows.clear();
this.mark_read_timer.reset();
base.destroy();
base.dispose();
}
public void append(Gtk.Widget child) {
this.listbox.append(child);
update_previous_sibling_css_class();
}
public new void remove(Gtk.Widget child) {
this.listbox.remove(child);
update_previous_sibling_css_class();
}
// For some reason insert doesn't emit the add event
public new void insert(Gtk.Widget child, int position) {
base.insert(child, position);
this.listbox.insert(child, position);
update_previous_sibling_css_class();
}
// This is mostly taken form libhandy HdyExpanderRow
private void update_previous_sibling_css_class() {
var siblings = this.get_children();
unowned List<weak Gtk.Widget> l;
for (l = siblings; l != null && l.next != null && l.next.data != this; l = l.next) {
if (l != null && l.next != null) {
var row = l.next.data as ConversationRow;
if (row != null) {
if (row.is_expanded) {
l.data.get_style_context().add_class("geary-expanded-previous-sibling");
} else {
l.data.get_style_context().remove_class("geary-expanded-previous-sibling");
}
}
unowned var child = get_first_child() as ConversationRow;
if (child == null)
return;
unowned var next = child.get_next_sibling() as ConversationRow;
while (next != null) {
child = next;
next = child.get_next_sibling() as ConversationRow;
if (next.is_expanded) {
child.add_css_class("geary-expanded-previous-sibling");
} else {
child.remove_css_class("geary-expanded-previous-sibling");
}
}
}
@ -764,7 +752,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public async void load_conversation(Gee.Collection<Geary.EmailIdentifier> scroll_to,
Geary.SearchQuery? query)
throws GLib.Error {
set_sort_func(null);
this.listbox.set_sort_func(null);
Gee.Collection<Geary.Email>? all_email = this.conversation.get_emails(
Geary.App.Conversation.Ordering.SENT_DATE_ASCENDING
@ -850,7 +838,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
public void scroll_to_messages(Gee.Collection<Geary.EmailIdentifier> targets) {
// Get the currently displayed email, allowing for some
// padding at the top
Gtk.ListBoxRow? current_child = get_row_at_y(32);
Gtk.ListBoxRow? current_child = this.listbox.get_row_at_y(32);
// Find the row currently at the top of the viewport
EmailRow? current = null;
@ -858,7 +846,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
int pos = current_child.get_index();
do {
current = current_child as EmailRow;
current_child = get_row_at_index(--pos);
current_child = this.listbox.get_row_at_index(--pos);
} while (current == null && pos > 0);
}
@ -904,12 +892,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
ConversationEmail? view = get_selection_view();
if (view == null) {
EmailRow? last = null;
this.foreach((child) => {
EmailRow? row = child as EmailRow;
if (row != null) {
last = row;
}
});
for (int i = 0; true; i++) {
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
if (row == null)
break;
last = row;
}
if (last != null) {
view = last.view;
@ -953,7 +941,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// Use row param rather than row var from closure to avoid a
// circular ref.
row.should_scroll.connect((row) => { scroll_to_row(row); });
add(row);
append(row);
this.current_composer = row;
embed.composer.notify["saved-id"].connect(
@ -1060,22 +1048,21 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// Since first rows may have extra margin, remove that from
// the height of rows when adjusting scrolling.
Gtk.ListBoxRow initial_row = get_row_at_index(0);
Gtk.ListBoxRow initial_row = this.listbox.get_row_at_index(0);
int loading_height = 0;
if (initial_row is LoadingRow) {
loading_height = Util.Gtk.get_border_box_height(initial_row);
remove(initial_row);
// Adjust for the changed margin of the first row
var first_row = get_row_at_index(0);
var style = first_row.get_style_context();
var margin = style.get_margin(style.get_state());
var first_row = this.listbox.get_row_at_index(0);
var margin = first_row.get_style_context().get_margin();;
loading_height -= margin.top;
}
// None of these will be interesting, so just add them all,
// but keep the scrollbar adjusted so that the first
// interesting message remains visible.
Gtk.Adjustment listbox_adj = get_adjustment();
Gtk.Adjustment listbox_adj = this.listbox.get_adjustment();
int i_mail_loaded = 0;
foreach (Geary.Email email in to_insert) {
EmailRow row = add_email(email, false);
@ -1096,7 +1083,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
++i_mail_loaded;
}
set_sort_func(on_sort);
this.listbox.set_sort_func(on_sort);
if (query != null) {
// XXX this sucks for large conversations because it can take
@ -1184,19 +1171,22 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
);
ConversationMessage conversation_message = view.primary_message;
// XXX GTK4 - I think we can do this separately
#if 0
conversation_message.body_container.button_release_event.connect_after((event) => {
// Consume all non-consumed clicks so the row is not
// inadvertently activated after clicking on the
// email body.
return true;
});
#endif
EmailRow row = new EmailRow(view);
row.email_loaded.connect((e) => { email_loaded(e); });
this.email_rows.set(email.id, row);
if (append_row) {
add(row);
append(row);
} else {
insert(row, 0);
}
@ -1222,17 +1212,17 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// Use set_value rather than clamp_value since we want to
// scroll to the top of the window.
get_adjustment().set_value(y);
this.listbox.get_adjustment().set_value(y);
}
private void scroll_to_anchor(EmailRow row, int anchor_y) {
Gtk.Allocation? alloc = null;
row.get_allocation(out alloc);
int x = 0, y = 0;
row.view.primary_message.web_view_translate_coordinates(row, x, anchor_y, out x, out y);
double x = 0, y = 0;
row.view.primary_message.web_view_translate_coordinates(row, 0, anchor_y, out x, out y);
Gtk.Adjustment adj = get_adjustment();
Gtk.Adjustment adj = this.listbox.get_adjustment();
y = alloc.y + y;
adj.set_value(y);
@ -1244,15 +1234,18 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
private void check_mark_read() {
Gee.List<Geary.EmailIdentifier> email_ids =
new Gee.LinkedList<Geary.EmailIdentifier>();
Gtk.Adjustment adj = get_adjustment();
Gtk.Adjustment adj = this.listbox.get_adjustment();
int top_bound = (int) adj.value;
int bottom_bound = top_bound + (int) adj.page_size;
this.foreach((child) => {
for (int i = 0; true; i++) {
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
if (row == null)
break;
// Don't bother with not-yet-loaded emails since the
// size of the body will be off, affecting the visibility
// of emails further down the conversation.
EmailRow? row = child as EmailRow;
ConversationEmail? view = (row != null) ? row.view : null;
Geary.Email? email = (view != null) ? view.email : null;
if (row != null &&
@ -1261,8 +1254,8 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
!view.is_manually_read &&
email.is_unread().is_certain()) {
ConversationMessage conversation_message = view.primary_message;
int body_top = 0;
int body_left = 0;
double body_top = 0;
double body_left = 0;
conversation_message.web_view_translate_coordinates(
this,
0, 0,
@ -1270,12 +1263,12 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
);
int body_height = conversation_message.web_view_get_allocated_height();
int body_bottom = body_top + body_height;
int body_bottom = (int) body_top + body_height;
// Only mark the email as read if it's actually visible
if (body_height > 0 &&
body_bottom > top_bound &&
body_top + MARK_READ_PADDING < bottom_bound) {
(int) body_top + MARK_READ_PADDING < bottom_bound) {
email_ids.add(view.email.id);
// Since it can take some time for the new flags
@ -1284,7 +1277,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
view.is_manually_read = true;
}
}
});
}
if (email_ids.size > 0) {
mark_email(email_ids, null, Geary.EmailFlags.UNREAD);
@ -1401,7 +1394,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
// be appended last. Finally, don't let rows with active
// composers be collapsed.
if (row.is_expanded) {
if (get_row_at_index(row.get_index() + 1) != null) {
if (this.listbox.get_row_at_index(row.get_index() + 1) != null) {
row.collapse();
}
} else {
@ -1481,15 +1474,19 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
Geary.Email email = view.email;
var ids = new Gee.LinkedList<Geary.EmailIdentifier>();
ids.add(email.id);
this.foreach((row) => {
if (row.get_visible()) {
Geary.Email other = ((EmailRow) row).view.email;
if (Geary.Email.compare_sent_date_ascending(
email, other) < 0) {
ids.add(other.id);
}
for (int i = 0; true; i++) {
unowned var row = this.listbox.get_row_at_index(i) as EmailRow;
if (row == null)
break;
if (row.visible) {
Geary.Email other = row.view.email;
if (Geary.Email.compare_sent_date_ascending(
email, other) < 0) {
ids.add(other.id);
}
});
}
}
mark_email(ids, Geary.EmailFlags.UNREAD, null);
}
}

View file

@ -15,7 +15,7 @@
* embeds at least one instance of this class.
*/
[GtkTemplate (ui = "/org/gnome/Geary/conversation-message.ui")]
public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
public class ConversationMessage : Gtk.Box, Geary.BaseInterface {
private const string FROM_CLASS = "geary-from";
@ -65,9 +65,6 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
private string search_value;
private Gtk.Bin container;
public ContactFlowBoxChild(Application.Contact contact,
Geary.RFC822.MailboxAddress source,
Type address_type = Type.OTHER) {
@ -77,40 +74,34 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.search_value = source.to_searchable_string().casefold();
// Update prelight state when mouse-overed.
Gtk.EventBox events = new Gtk.EventBox();
events.add_events(
Gdk.EventMask.ENTER_NOTIFY_MASK |
Gdk.EventMask.LEAVE_NOTIFY_MASK
);
events.set_visible_window(false);
events.enter_notify_event.connect(on_prelight_in_event);
events.leave_notify_event.connect(on_prelight_out_event);
Gtk.EventControllerMotion controller = new Gtk.EventControllerMotion();
controller.enter.connect(on_prelight_enter);
controller.leave.connect(on_prelight_leave);
add_controller(controller);
add(events);
this.container = events;
set_halign(Gtk.Align.START);
this.contact.changed.connect(on_contact_changed);
update();
}
public override void destroy() {
public override void dispose() {
this.contact.changed.disconnect(on_contact_changed);
base.destroy();
base.dispose();
}
public bool highlight_search_term(string term) {
bool found = term in this.search_value;
if (found) {
get_style_context().add_class(MATCH_CLASS);
add_css_class(MATCH_CLASS);
} else {
get_style_context().remove_class(MATCH_CLASS);
remove_css_class(MATCH_CLASS);
}
return found;
}
public void unmark_search_terms() {
get_style_context().remove_class(MATCH_CLASS);
remove_css_class(MATCH_CLASS);
}
private void update() {
@ -120,28 +111,26 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
// both cases, but we can't yet include CSS classes in
// Pango markup. See Bug 766763.
Gtk.Grid address_parts = new Gtk.Grid();
var address_parts = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 0);
bool is_spoofed = this.source.is_spoofed();
if (is_spoofed) {
Gtk.Image spoof_img = new Gtk.Image.from_icon_name(
"dialog-warning-symbolic", Gtk.IconSize.SMALL_TOOLBAR
);
var spoof_img = new Gtk.Image.from_icon_name("dialog-warning-symbolic");
this.set_tooltip_text(
_("This email address may have been forged")
);
address_parts.add(spoof_img);
get_style_context().add_class(SPOOF_CLASS);
address_parts.append(spoof_img);
add_css_class(SPOOF_CLASS);
}
Gtk.Label primary = new Gtk.Label(null);
primary.ellipsize = Pango.EllipsizeMode.END;
primary.set_halign(Gtk.Align.START);
primary.get_style_context().add_class(PRIMARY_CLASS);
primary.add_css_class(PRIMARY_CLASS);
if (this.address_type == Type.FROM) {
primary.get_style_context().add_class(FROM_CLASS);
primary.add_css_class(FROM_CLASS);
}
address_parts.add(primary);
address_parts.append(primary);
string display_address = this.source.to_address_display("", "");
@ -173,32 +162,24 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
Gtk.Label secondary = new Gtk.Label(null);
secondary.ellipsize = Pango.EllipsizeMode.END;
secondary.set_halign(Gtk.Align.START);
secondary.get_style_context().add_class(Gtk.STYLE_CLASS_DIM_LABEL);
secondary.add_css_class("dim-label");
secondary.set_text(display_address);
address_parts.add(secondary);
address_parts.append(secondary);
}
Gtk.Widget? existing_ui = this.container.get_child();
if (existing_ui != null) {
this.container.remove(existing_ui);
}
this.container.add(address_parts);
show_all();
this.child = address_parts;
}
private void on_contact_changed() {
update();
}
private bool on_prelight_in_event(Gdk.Event event) {
private void on_prelight_enter(Gtk.EventControllerMotion controller, double x, double y) {
set_state_flags(Gtk.StateFlags.PRELIGHT, false);
return Gdk.EVENT_STOP;
}
private bool on_prelight_out_event(Gdk.Event event) {
private void on_prelight_leave(Gtk.EventControllerMotion controller) {
unset_state_flags(Gtk.StateFlags.PRELIGHT);
return Gdk.EVENT_STOP;
}
}
@ -207,7 +188,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
* A FlowBox that limits its contents to 12 items until a link is
* clicked to expand it. Used for to, cc, and bcc fields.
*/
public class ContactList : Gtk.FlowBox, Geary.BaseInterface {
public class ContactList : Adw.Bin, Geary.BaseInterface {
/**
* The number of results that will be displayed when not expanded.
* Note this is actually one less than the cutoff, which is 12; we
@ -216,43 +197,50 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
*/
private const int SHORT_RESULTS = 11;
private Gtk.FlowBox flowbox = new Gtk.FlowBox();
private Gtk.Label show_more;
private Gtk.Label show_less;
private bool expanded = false;
private int children = 0;
public signal void child_activated(Gtk.FlowBoxChild child);
construct {
this.show_more = this.create_label();
this.show_more.activate_link.connect(() => {
this.set_expanded(true);
});
base.add(this.show_more);
this.child = this.flowbox;
this.flowbox.column_spacing = 2;
this.flowbox.max_children_per_line = 4;
this.flowbox.selection_mode = Gtk.SelectionMode.NONE;
this.flowbox.child_activated.connect((f, c) => { child_activated(c); });
this.show_less = this.create_label();
this.show_more = create_label();
this.show_more.activate_link.connect(() => {
set_expanded(true);
});
this.flowbox.append(this.show_more);
this.show_less = create_label();
// Translators: Label text displayed when there are too
// many email addresses to be shown by default in an
// email's header, but they are all being shown anyway.
this.show_less.label = "<a href=''>%s</a>".printf(_("Show less"));
this.show_less.activate_link.connect(() => {
this.set_expanded(false);
set_expanded(false);
});
base.add(this.show_less);
this.flowbox.append(this.show_less);
this.set_filter_func(this.filter_func);
this.flowbox.set_filter_func(this.filter_func);
}
public override void add(Gtk.Widget child) {
public void add(Gtk.Widget child) {
// insert before the show_more and show_less labels
int length = (int) this.get_children().length();
base.insert(child, length - 2);
this.flowbox.insert(child, n_children() - 2);
this.children ++;
if (this.children >= SHORT_RESULTS && this.children <= SHORT_RESULTS + 2) {
this.invalidate_filter();
this.flowbox.invalidate_filter();
}
this.show_more.label = "<a href=''>%s</a>".printf(
@ -264,19 +252,27 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
);
}
private int n_children() {
int ret = 0;
unowned var child = this.flowbox.get_first_child();
while (child != null) {
ret++;
child = child.get_next_sibling();
}
return ret;
}
private Gtk.Label create_label() {
var label = new Gtk.Label("");
label.visible = true;
label.use_markup = true;
label.track_visited_links = false;
label.halign = START;
return label;
}
private void set_expanded(bool expanded) {
this.expanded = expanded;
this.invalidate_filter();
this.flowbox.invalidate_filter();
}
private bool filter_func(Gtk.FlowBoxChild child) {
@ -306,10 +302,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
/** Container for preview and full header widgets. */
[GtkChild] internal unowned Gtk.Grid summary { get; }
[GtkChild] internal unowned Gtk.Box summary { get; }
/** Container for message body components. */
[GtkChild] internal unowned Gtk.Grid body_container { get; }
[GtkChild] internal unowned Gtk.Box body_container { get; }
/** Conainer for message InfoBar widgets. */
[GtkChild] internal unowned Components.InfoBarStack info_bars { get; }
@ -338,28 +334,28 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
private GLib.DateTime? local_date = null;
[GtkChild] private unowned Hdy.Avatar avatar;
[GtkChild] private unowned Adw.Avatar avatar;
[GtkChild] private unowned Gtk.Revealer compact_revealer;
[GtkChild] private unowned Gtk.Box compact_header;
[GtkChild] private unowned Gtk.Label compact_from;
[GtkChild] private unowned Gtk.Label compact_date;
[GtkChild] private unowned Gtk.Label compact_body;
[GtkChild] private unowned Gtk.Revealer header_revealer;
[GtkChild] private unowned Gtk.Box expanded_header;
[GtkChild] private unowned Gtk.FlowBox from;
[GtkChild] private unowned Gtk.Label subject;
private string subject_searchable = "";
[GtkChild] private unowned Gtk.Label date;
[GtkChild] private unowned Gtk.Grid sender_header;
[GtkChild] private unowned Gtk.Box sender_header;
[GtkChild] private unowned Gtk.FlowBox sender_address;
[GtkChild] private unowned Gtk.Grid reply_to_header;
[GtkChild] private unowned Gtk.Box reply_to_header;
[GtkChild] private unowned Gtk.FlowBox reply_to_addresses;
[GtkChild] private unowned Gtk.Grid to_header;
[GtkChild] private unowned Gtk.Grid cc_header;
[GtkChild] private unowned Gtk.Grid bcc_header;
[GtkChild] private unowned ContactList to_list;
[GtkChild] private unowned ContactList cc_list;
[GtkChild] private unowned ContactList bcc_list;
[GtkChild] private unowned Gtk.Revealer body_revealer;
[GtkChild] private unowned Gtk.ProgressBar body_progress;
@ -371,7 +367,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
private string empty_from_label;
// The web_view's context menu
private Gtk.Menu? context_menu = null;
private Gtk.PopoverMenu? context_menu = null;
// Menu models for creating the context menu
private MenuModel context_menu_link;
@ -549,7 +545,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
// when the message has no from address.
this.empty_from_label = _("No sender");
this.compact_from.get_style_context().add_class(FROM_CLASS);
this.compact_from.add_css_class(FROM_CLASS);
if (preview != null) {
string clean_preview = preview;
@ -595,10 +591,11 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
if (viewer != null && viewer.previous_web_view != null) {
this.web_view = new ConversationWebView.with_related_view(
this.config,
null, /// XXX GTK4 is null okay here? I honestly think not ...
viewer.previous_web_view
);
} else {
this.web_view = new ConversationWebView(this.config);
this.web_view = new ConversationWebView(this.config, null); /// XXX GTK4 is null okay here? I honestly think not ...
}
if (viewer != null) {
viewer.previous_web_view = this.web_view;
@ -618,7 +615,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.web_view.set_hexpand(true);
this.web_view.set_vexpand(true);
this.web_view.show();
this.body_container.add(this.web_view);
this.body_container.append(this.web_view);
add_action(ACTION_COPY_SELECTION, false).activate.connect(() => {
web_view.copy_clipboard();
});
@ -634,13 +631,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
base_unref();
}
public override void destroy() {
public override void dispose() {
this.show_progress_timeout.reset();
this.hide_progress_timeout.reset();
this.progress_pulse.reset();
this.resources.clear();
this.searchable_addresses.clear();
base.destroy();
base.dispose();
}
public async string? get_selection_for_quoting() throws Error {
@ -696,10 +693,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
web_view.zoom_reset();
}
public void web_view_translate_coordinates(Gtk.Widget widget, int x, int anchor_y, out int x1, out int y1) {
public void web_view_translate_coordinates(Gtk.Widget widget, int x, int anchor_y, out double x1, out double y1) {
if (this.web_view == null)
initialize_web_view();
web_view.translate_coordinates(widget, x, anchor_y, out x1, out y1);
this.web_view.translate_coordinates(widget, x, anchor_y, out x1, out y1);
}
public int web_view_get_allocated_height() {
@ -714,8 +711,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
public void show_message_body(bool include_transitions=true) {
if (this.web_view == null)
initialize_web_view();
set_revealer(this.compact_revealer, false, include_transitions);
set_revealer(this.header_revealer, true, include_transitions);
this.compact_header.visible = false;
this.expanded_header.visible = true;
set_revealer(this.body_revealer, true, include_transitions);
}
@ -723,8 +720,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
* Hides the complete message and shows the compact headers.
*/
public void hide_message_body() {
compact_revealer.set_reveal_child(true);
header_revealer.set_reveal_child(false);
this.compact_header.visible = true;
this.expanded_header.visible = false;
body_revealer.set_reveal_child(false);
}
@ -830,7 +827,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
*/
public async void load_contacts(GLib.Cancellable cancellable)
throws GLib.Error {
var main = this.get_toplevel() as Application.MainWindow;
var main = this.get_root() as Application.MainWindow;
if (main != null && !cancellable.is_cancelled()) {
// Load the primary contact and avatar
if (this.primary_originator != null) {
@ -843,10 +840,14 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.avatar,
"text",
BindingFlags.SYNC_CREATE);
this.primary_contact.bind_property("avatar",
this.avatar,
"loadable-icon",
BindingFlags.SYNC_CREATE);
if (this.primary_contact.avatar != null) {
var icon_stream = yield this.primary_contact.avatar.load_async(48, null);
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(icon_stream, 48, 48, true);
this.avatar.custom_image = Gdk.Texture.for_pixbuf(pixbuf);
} else {
this.avatar.custom_image = null;
}
}
}
@ -864,13 +865,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
cancellable
);
yield fill_header_addresses(
this.to_header, headers.to, cancellable
this.to_list, headers.to, cancellable
);
yield fill_header_addresses(
this.cc_header, headers.cc, cancellable
this.cc_list, headers.cc, cancellable
);
yield fill_header_addresses(
this.bcc_header, headers.bcc, cancellable
this.bcc_list, headers.bcc, cancellable
);
}
}
@ -930,10 +931,10 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
string match = raw_match.casefold();
if (this.subject_searchable.contains(match)) {
this.subject.get_style_context().add_class(MATCH_CLASS);
this.subject.add_css_class(MATCH_CLASS);
++headers_found;
} else {
this.subject.get_style_context().remove_class(MATCH_CLASS);
this.subject.remove_css_class(MATCH_CLASS);
}
foreach (ContactFlowBoxChild address in this.searchable_addresses) {
@ -1052,17 +1053,16 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
ContactFlowBoxChild.Type.FROM
);
this.searchable_addresses.add(child);
this.from.add(child);
this.from.append(child);
}
} else {
Gtk.Label label = new Gtk.Label(null);
label.set_text(this.empty_from_label);
Gtk.FlowBoxChild child = new Gtk.FlowBoxChild();
child.add(label);
child.child = label;
child.set_halign(Gtk.Align.START);
child.show_all();
this.from.add(child);
this.from.append(child);
}
// Show the Sender header addresses if present, but only if
@ -1075,7 +1075,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
);
this.searchable_addresses.add(child);
this.sender_header.show();
this.sender_address.add(child);
this.sender_address.append(child);
}
// Show any Reply-To header addresses if present, but only if
@ -1088,31 +1088,36 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
address
);
this.searchable_addresses.add(child);
this.reply_to_addresses.add(child);
this.reply_to_addresses.append(child);
this.reply_to_header.show();
}
}
}
}
private async void fill_header_addresses(Gtk.Grid header,
private async void fill_header_addresses(ContactList contact_list,
Geary.RFC822.MailboxAddresses? addresses,
GLib.Cancellable? cancellable)
throws GLib.Error {
if (addresses != null && addresses.size > 0) {
ContactList box = header.get_children().nth(0).data as ContactList;
if (box != null) {
foreach (Geary.RFC822.MailboxAddress address in addresses) {
ContactFlowBoxChild child = new ContactFlowBoxChild(
yield this.contacts.load(address, cancellable),
address
);
this.searchable_addresses.add(child);
box.add(child);
}
}
header.set_visible(true);
// We set the visibility on the parent, as there's usually a label that
// needs to become (in)visible too.
unowned Gtk.Box header = contact_list.get_parent() as Gtk.Box;
if (addresses == null || addresses.size <= 0) {
header.visible = false;
return;
}
foreach (Geary.RFC822.MailboxAddress address in addresses) {
ContactFlowBoxChild child = new ContactFlowBoxChild(
yield this.contacts.load(address, cancellable),
address
);
this.searchable_addresses.add(child);
contact_list.add(child);
}
header.visible = true;
}
// This delegate is called from within
@ -1191,7 +1196,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
this.body_placeholder = placeholder;
if (this.web_view != null)
this.web_view.hide();
this.body_container.add(placeholder);
this.body_container.append(placeholder);
show_message_body(true);
} else {
if (this.web_view != null)
@ -1250,7 +1255,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
[GtkCallback]
private void on_address_box_child_activated(Gtk.FlowBox box,
private void on_address_box_child_activated(Gtk.Widget _unused,
Gtk.FlowBoxChild child) {
ContactFlowBoxChild address_child = child as ContactFlowBoxChild;
if (address_child != null) {
@ -1284,10 +1289,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
private bool on_context_menu(WebKit.WebView view,
WebKit.ContextMenu context_menu,
Gdk.Event event,
WebKit.HitTestResult hit_test) {
if (this.context_menu != null) {
this.context_menu.detach();
this.context_menu.popdown();
}
// Build a new context menu every time the user clicks because
@ -1332,9 +1336,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
model.append_section(null, context_menu_inspector);
}
this.context_menu = new Gtk.Menu.from_model(model);
this.context_menu.attach_to_widget(this, null);
this.context_menu.popup_at_pointer(event);
this.context_menu = new Gtk.PopoverMenu.from_model(model);
this.context_menu.set_parent(this);
var event = context_menu.get_event();
double x = 0, y = 0;
if (event != null && event.get_position(out x, out y)) {
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
this.context_menu.set_pointing_to(rect);
}
this.context_menu.popup();
return true;
}
@ -1378,7 +1388,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
// Escape text and especially URLs since we got them from the
// HREF, and Gtk.Label.set_markup is a strict parser.
var main = get_toplevel() as Application.MainWindow;
var main = get_root() as Application.MainWindow;
good_link.set_markup(
Markup.printf_escaped("<a href=\"%s\">%s</a>", text_href, text_label)
@ -1400,7 +1410,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
);
link_popover.set_relative_to(this.web_view);
link_popover.set_parent(this.web_view);
link_popover.set_pointing_to(location);
link_popover.closed.connect_after(() => { link_popover.destroy(); });
link_popover.popup();
@ -1424,18 +1434,13 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
_("Showing remote images allows the sender to track you")
);
var menu_image = new Gtk.Image();
menu_image.icon_name = "view-more-symbolic";
var menu_button = new Gtk.MenuButton();
menu_button.use_popover = true;
menu_button.image = menu_image;
menu_button.icon_name = "view-more-symbolic";
menu_button.menu_model = this.show_images_menu;
menu_button.halign = Gtk.Align.END;
menu_button.hexpand =true;
menu_button.show_all();
menu_button.hexpand = true;
this.remote_images_info_bar.get_action_area().add(menu_button);
this.remote_images_info_bar.get_action_area().append(menu_button);
} else {
this.remote_images_info_bar = new Components.InfoBar(
// Translators: Info bar status message
@ -1456,9 +1461,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
private void on_copy_link(Variant? param) {
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
clipboard.set_text(param.get_string(), -1);
clipboard.store();
Gdk.Clipboard clipboard = get_clipboard();
clipboard.set_text(param.get_string());
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
try {
clipboard.store_async.end(res);
} catch (Error err) {
debug("Couldn't copy link to clipboard: %s", err.message);
}
});
}
private void on_copy_email_address(Variant? param) {
@ -1466,9 +1477,15 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
if (value.has_prefix(MAILTO_URI_PREFIX)) {
value = value.substring(MAILTO_URI_PREFIX.length, -1);
}
Gtk.Clipboard clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD);
clipboard.set_text(value, -1);
clipboard.store();
Gdk.Clipboard clipboard = get_clipboard();
clipboard.set_text(value);
clipboard.store_async.begin(Priority.DEFAULT, null, (obj, res) => {
try {
clipboard.store_async.end(res);
} catch (Error err) {
debug("Couldn't copy email address to clipboard: %s", err.message);
}
});
}
private void on_save_image(Variant? param) {
@ -1520,7 +1537,8 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
show_images(false);
if (this.primary_contact != null) {
var email_addresses = this.primary_contact.email_addresses;
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
this.config.add_images_trusted_domain(email.domain);
break;
}
@ -1547,7 +1565,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
}
});
} else {
var main = this.get_toplevel() as Application.MainWindow;
var main = this.get_root() as Application.MainWindow;
if (main != null) {
main.application.show_uri.begin(link);
}

View file

@ -10,7 +10,7 @@
* Displays the messages in a conversation and in-window composers.
*/
[GtkTemplate (ui = "/org/gnome/Geary/conversation-viewer.ui")]
public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
public class ConversationViewer : Adw.Bin, Geary.BaseInterface {
/**
* The current conversation listbox, if any.
@ -37,14 +37,19 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
private Gee.Set<Geary.App.Conversation>? selection_while_composing = null;
private GLib.Cancellable? find_cancellable = null;
[GtkChild] public unowned Components.ConversationHeaderBar headerbar;
[GtkChild] private unowned Gtk.Stack stack;
// Stack pages
[GtkChild] private unowned Gtk.Spinner loading_page;
[GtkChild] private unowned Gtk.Grid no_conversations_page;
[GtkChild] private unowned Gtk.Grid conversation_page;
[GtkChild] private unowned Gtk.Grid multiple_conversations_page;
[GtkChild] private unowned Gtk.Grid empty_folder_page;
[GtkChild] private unowned Gtk.Grid empty_search_page;
[GtkChild] private unowned Gtk.Grid composer_page;
[GtkChild] private unowned Adw.Spinner loading_page;
[GtkChild] private unowned Adw.StatusPage no_conversations_page;
[GtkChild] private unowned Gtk.Box conversation_page;
[GtkChild] private unowned Adw.StatusPage multiple_conversations_page;
[GtkChild] private unowned Adw.StatusPage empty_folder_page;
[GtkChild] private unowned Adw.StatusPage empty_search_page;
[GtkChild] private unowned Adw.Bin composer_page;
[GtkChild] private unowned Gtk.ScrolledWindow conversation_scroller;
[GtkChild] internal unowned Gtk.SearchBar conversation_find_bar;
@ -75,70 +80,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
base_ref();
this.config = config;
Hdy.StatusPage no_conversations =
new Hdy.StatusPage();
no_conversations.icon_name = "folder-symbolic";
// Translators: Title label for placeholder when no
// conversations have been selected.
no_conversations.title = _("No Conversations Selected");
// Translators: Sub-title label for placeholder when no
// conversations have been selected.
no_conversations.description = _(
"Selecting a conversation from the list will display it here."
);
no_conversations.hexpand = true;
no_conversations.vexpand = true;
no_conversations.show ();
this.no_conversations_page.add(no_conversations);
Hdy.StatusPage multi_conversations =
new Hdy.StatusPage();
multi_conversations.icon_name = "folder-symbolic";
// Translators: Title label for placeholder when multiple
// conversations have been selected.
multi_conversations.title = _("Multiple Conversations Selected");
// Translators: Sub-title label for placeholder when multiple
// conversations have been selected.
multi_conversations.description = _(
"Choosing an action will apply to all selected conversations."
);
multi_conversations.hexpand = true;
multi_conversations.vexpand = true;
multi_conversations.show ();
this.multiple_conversations_page.add(multi_conversations);
Hdy.StatusPage empty_folder =
new Hdy.StatusPage();
empty_folder.icon_name = "folder-symbolic";
// Translators: Title label for placeholder when no
// conversations have exist in a folder.
empty_folder.title = _("No Conversations Found");
// Translators: Sub-title label for placeholder when no
// conversations have exist in a folder.
empty_folder.description = _(
"This folder does not contain any conversations."
);
empty_folder.hexpand = true;
empty_folder.vexpand = true;
empty_folder.show ();
this.empty_folder_page.add(empty_folder);
Hdy.StatusPage empty_search =
new Hdy.StatusPage();
empty_search.icon_name = "folder-symbolic";
// Translators: Title label for placeholder when no
// conversations have been found in a search.
empty_search.title = _("No Conversations Found");
// Translators: Sub-title label for placeholder when no
// conversations have been found in a search.
empty_search.description = _(
"Your search returned no results, try refining your search terms."
);
empty_search.hexpand = true;
empty_search.vexpand = true;
empty_search.show ();
this.empty_search_page.add(empty_search);
this.conversation_find_undo = new Components.EntryUndo(
this.conversation_find_entry
);
@ -155,10 +96,10 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
* Puts the view into composer mode, showing a full-height composer.
*/
public void do_compose(Composer.Widget composer) {
var main_window = get_toplevel() as Application.MainWindow;
var main_window = get_root() as Application.MainWindow;
if (main_window != null) {
Composer.Box box = new Composer.Box(
composer, main_window.conversation_headerbar
composer, this.headerbar
);
this.current_composer = composer;
@ -169,7 +110,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
conversation_list.unselect_all();
box.vanished.connect(on_composer_closed);
this.composer_page.add(box);
this.composer_page.child = box;
set_visible_child(this.composer_page);
composer.update_window_title();
}
@ -214,7 +155,6 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
* Shows the loading UI.
*/
public void show_loading() {
this.loading_page.start();
set_visible_child(this.loading_page);
}
@ -283,13 +223,16 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
this.conversation_find_prev.set_sensitive(false);
new_list.search.matches_updated.connect((count) => {
bool found = count > 0;
//XXX GTK4 - Gtk.SearchEntry doesn't have a icon API anymore
#if 0
this.conversation_find_entry.set_icon_from_icon_name(
Gtk.EntryIconPosition.PRIMARY,
found || Geary.String.is_empty(this.conversation_find_entry.text)
? "edit-find-symbolic" : "computer-fail-symbolic"
);
this.conversation_find_next.set_sensitive(found);
this.conversation_find_prev.set_sensitive(found);
#endif
this.conversation_find_next.sensitive = found;
this.conversation_find_prev.sensitive = found;
});
add_new_list(new_list);
set_visible_child(this.conversation_page);
@ -318,10 +261,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
// are not set on the list - it makes changing focus jumpy
// when a row or its web_view are larger than the viewport.
Gtk.Viewport viewport = new Gtk.Viewport(null, null);
viewport.show();
viewport.add(list);
viewport.child = list;
this.conversation_scroller.add(viewport);
this.conversation_scroller.child = viewport;
}
// Remove any existing conversation list, cancelling its loading
@ -329,7 +271,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
// Remove the viewport that contains the current list
Gtk.Widget? scrolled_child = this.conversation_scroller.get_child();
if (scrolled_child != null) {
conversation_scroller.remove(scrolled_child);
this.conversation_scroller.child = null;
}
// Reset the scrollbars to their initial positions
@ -344,10 +286,14 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
return scrolled_child;
}
public Gtk.Widget get_visible_child() {
return this.stack.visible_child;
}
/**
* Sets the currently visible page of the stack.
*/
private new void set_visible_child(Gtk.Widget widget) {
private void set_visible_child(Gtk.Widget widget) {
debug("Showing: %s", widget.get_name());
Gtk.Widget current = get_visible_child();
if (current == this.conversation_page) {
@ -358,12 +304,8 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
// etc.
remove_current_list();
}
} else if (current == this.loading_page) {
// Stop the spinner running so it doesn't trigger repaints
// and wake up Geary even when idle. See Bug 783025.
this.loading_page.stop();
}
base.set_visible_child(widget);
this.stack.set_visible_child(widget);
}
private async void update_find_results() {
@ -471,7 +413,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
}
[GtkCallback]
private bool on_conversation_scroll() {
private bool on_conversation_scroll(Gtk.EventControllerScroll controller,
double dx,
double dy) {
if (this.current_list != null) {
this.current_list.mark_visible_read();
}
@ -484,7 +428,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
set_visible_child(this.conversation_page);
// Restore the old selection
var main_window = get_toplevel() as Application.MainWindow;
var main_window = get_root() as Application.MainWindow;
if (main_window != null) {
main_window.update_title();

View file

@ -61,8 +61,8 @@ public class ConversationWebView : Components.WebView {
*
* A new WebKitGTK WebProcess will be constructed for this view.
*/
public ConversationWebView(Application.Configuration config) {
base(config);
public ConversationWebView(Application.Configuration config, GLib.File? cache_dir) {
base(config, cache_dir);
init();
// These only need to be added when creating a new WebProcess,
@ -79,6 +79,7 @@ public class ConversationWebView : Components.WebView {
*/
internal ConversationWebView.with_related_view(
Application.Configuration config,
GLib.File? cache_dir,
ConversationWebView related
) {
base.with_related_view(config, related);
@ -185,38 +186,46 @@ public class ConversationWebView : Components.WebView {
get_find_controller().search_finish();
}
public override bool key_press_event(Gdk.EventKey event) {
private bool on_key_pressed(Gtk.EventControllerKey controller,
uint keyval,
uint keycode,
Gdk.ModifierType state) {
//XXX GTK4 not sure what to do here
// WebView consumes a number of key presses for scrolling
// itself internally, but we want them to navigate around in
// ConversationListBox, so don't forward any on.
bool ret = Gdk.EVENT_PROPAGATE;
if (!(((int) event.keyval) in BLACKLISTED_KEY_CODES)) {
ret = base.key_press_event(event);
}
// if (!(((int) keyval) in BLACKLISTED_KEY_CODES)) {
// ret = base.key_press_event(event);
// }
return ret;
}
public override void get_preferred_height(out int minimum_height,
out int natural_height) {
// XXX clamp height to something not too outrageous so we
// don't get an XServer error trying to allocate a massive
// window.
const uint max_pixels = 8 * 1024 * 1024;
int width = get_allocated_width();
int height = this.preferred_height;
if (height * width > max_pixels) {
height = (int) Math.floor(max_pixels / (double) width);
public override void measure(Gtk.Orientation orientation,
int for_size,
out int minimum,
out int natural,
out int minimum_baseline,
out int natural_baseline) {
if (orientation == Gtk.Orientation.HORIZONTAL) {
// We always want the view to be sized according to the available
// space in the parent, not by the width of the web view.
minimum = natural = 0;
} else {
// XXX clamp height to something not too outrageous so we
// don't get an XServer error trying to allocate a massive
// window.
const uint max_pixels = 8 * 1024 * 1024;
int width = get_allocated_width();
int height = this.preferred_height;
if (height * width > max_pixels) {
height = (int) Math.floor(max_pixels / (double) width);
}
minimum = natural = height;
}
minimum_height = natural_height = height;
}
// Overridden since we always what the view to be sized according
// to the available space in the parent, not by the width of the
// web view.
public override void get_preferred_width(out int minimum_height,
out int natural_height) {
minimum_height = natural_height = 0;
minimum_baseline = natural_baseline = -1;
}
private void init() {
@ -225,6 +234,10 @@ public class ConversationWebView : Components.WebView {
);
this.notify["preferred-height"].connect(() => queue_resize());
Gtk.EventControllerKey controller = new Gtk.EventControllerKey();
controller.key_pressed.connect(on_key_pressed);
add_controller(controller);
}
private void on_deceptive_link_clicked(GLib.Variant? parameters) {

View file

@ -1,128 +0,0 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
class AlertDialog : Object {
private Gtk.MessageDialog dialog;
public AlertDialog(Gtk.Window? parent, Gtk.MessageType message_type, string title,
string? description, string? ok_button, string? cancel_button, string? tertiary_button,
Gtk.ResponseType tertiary_response_type, string? ok_action_type,
string? tertiary_action_type = "", Gtk.ResponseType? default_response = null) {
dialog = new Gtk.MessageDialog(parent, Gtk.DialogFlags.DESTROY_WITH_PARENT, message_type,
Gtk.ButtonsType.NONE, "");
dialog.text = title;
dialog.secondary_text = description;
if (!Geary.String.is_empty_or_whitespace(tertiary_button)) {
Gtk.Widget? button = dialog.add_button(tertiary_button, tertiary_response_type);
if (!Geary.String.is_empty_or_whitespace(tertiary_action_type)) {
button.get_style_context().add_class(tertiary_action_type);
}
}
if (!Geary.String.is_empty_or_whitespace(cancel_button))
dialog.add_button(cancel_button, Gtk.ResponseType.CANCEL);
if (!Geary.String.is_empty_or_whitespace(ok_button)) {
Gtk.Widget? button = dialog.add_button(ok_button, Gtk.ResponseType.OK);
if (!Geary.String.is_empty_or_whitespace(ok_action_type)) {
button.get_style_context().add_class(ok_action_type);
}
}
if (default_response != null) {
dialog.set_default_response(default_response);
}
}
public void use_secondary_markup(bool markup) {
dialog.secondary_use_markup = markup;
}
public Gtk.Box get_message_area() {
return (Gtk.Box) dialog.get_message_area();
}
public void set_focus_response(Gtk.ResponseType response) {
Gtk.Widget? to_focus = dialog.get_widget_for_response(response);
if (to_focus != null)
to_focus.grab_focus();
}
// Runs dialog, destroys it, and returns selected response
public Gtk.ResponseType run() {
Gtk.ResponseType response = (Gtk.ResponseType) dialog.run();
dialog.destroy();
return response;
}
}
class ConfirmationDialog : AlertDialog {
public ConfirmationDialog(Gtk.Window? parent, string title, string? description,
string? ok_button, string? ok_action_type = "") {
base (parent, Gtk.MessageType.WARNING, title, description, ok_button, Stock._CANCEL,
null, Gtk.ResponseType.NONE, ok_action_type);
}
}
class TernaryConfirmationDialog : AlertDialog {
public TernaryConfirmationDialog(Gtk.Window? parent, string title, string? description,
string? ok_button, string? tertiary_button, Gtk.ResponseType tertiary_response_type,
string? ok_action_type = "", string? tertiary_action_type = "",
Gtk.ResponseType? default_response = null) {
base (parent, Gtk.MessageType.WARNING, title, description, ok_button, Stock._CANCEL,
tertiary_button, tertiary_response_type, ok_action_type, tertiary_action_type,
default_response);
}
}
class ErrorDialog : AlertDialog {
public ErrorDialog(Gtk.Window? parent, string title, string? description) {
base (parent, Gtk.MessageType.ERROR, title, description, Stock._OK, null, null,
Gtk.ResponseType.NONE, null);
}
}
class QuestionDialog : AlertDialog {
public bool is_checked { get; private set; default = false; }
private Gtk.CheckButton? checkbutton = null;
public QuestionDialog(Gtk.Window? parent, string title, string? description,
string yes_button, string no_button) {
base (parent, Gtk.MessageType.QUESTION, title, description, yes_button, no_button, null,
Gtk.ResponseType.NONE, "suggested-action");
}
public QuestionDialog.with_checkbox(Gtk.Window? parent, string title, string? description,
string yes_button, string no_button, string checkbox_label, bool checkbox_default) {
this (parent, title, description, yes_button, no_button);
checkbutton = new Gtk.CheckButton.with_mnemonic(checkbox_label);
checkbutton.active = checkbox_default;
checkbutton.toggled.connect(on_checkbox_toggled);
get_message_area().pack_start(checkbutton);
// this must be done once all the packing is completed
get_message_area().show_all();
// the check box may have grabbed keyboard focus, so we put it back to the button
set_focus_response(Gtk.ResponseType.OK);
is_checked = checkbox_default;
}
private void on_checkbox_toggled() {
is_checked = checkbutton.active;
}
}

View file

@ -1,104 +0,0 @@
/* Copyright 2016 Software Freedom Conservancy Inc.
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A FileChooser-like object for choosing attachments for a message.
*/
public class AttachmentDialog : Object {
private const int PREVIEW_SIZE = 180;
private const int PREVIEW_PADDING = 3;
private Application.Configuration config;
private Gtk.FileChooserNative? chooser = null;
private Gtk.Image preview_image = new Gtk.Image();
public delegate bool Attacher(File attachment_file, bool alert_errors = true);
public AttachmentDialog(Gtk.Window? parent, Application.Configuration config) {
this.config = config;
this.chooser = new Gtk.FileChooserNative(_("Choose a file"), parent, Gtk.FileChooserAction.OPEN, _("_Attach"), Stock._CANCEL);
this.chooser.set_local_only(false);
this.chooser.set_select_multiple(true);
// preview widget is not supported on Win32 (this will fallback to gtk file chooser)
// and possibly by some org.freedesktop.portal.FileChooser (preview will be ignored).
this.chooser.set_preview_widget(this.preview_image);
this.chooser.use_preview_label = false;
this.chooser.update_preview.connect(on_update_preview);
}
public void add_filter(owned Gtk.FileFilter filter) {
this.chooser.add_filter(filter);
}
public SList<File> get_files() {
return this.chooser.get_files();
}
public int run() {
return this.chooser.run();
}
public void hide() {
this.chooser.hide();
}
public void destroy() {
this.chooser.destroy();
}
private void on_update_preview() {
string? filename = chooser.get_preview_filename();
if (filename == null) {
chooser.set_preview_widget_active(false);
return;
}
// read the image format data first
int width = 0;
int height = 0;
Gdk.PixbufFormat? format = Gdk.Pixbuf.get_file_info(filename, out width, out height);
if (format == null) {
chooser.set_preview_widget_active(false);
return;
}
// if the image is too big, resize it
Gdk.Pixbuf pixbuf;
try {
pixbuf = new Gdk.Pixbuf.from_file_at_scale(filename, PREVIEW_SIZE, PREVIEW_SIZE, true);
} catch (Error e) {
chooser.set_preview_widget_active(false);
return;
}
if (pixbuf == null) {
chooser.set_preview_widget_active(false);
return;
}
pixbuf = pixbuf.apply_embedded_orientation();
// distribute the extra space around the image
int extra_space = PREVIEW_SIZE - pixbuf.width;
int smaller_half = extra_space/2;
int larger_half = extra_space - smaller_half;
// pad the image manually (avoids rounding errors)
preview_image.set_margin_start(PREVIEW_PADDING + smaller_half);
preview_image.set_margin_end(PREVIEW_PADDING + larger_half);
// show the preview
preview_image.set_from_pixbuf(pixbuf);
chooser.set_preview_widget_active(true);
}
}

View file

@ -4,7 +4,9 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class CertificateWarningDialog {
[GtkTemplate (ui = "/org/gnome/Geary/certificate-warning-dialog.ui")]
public class CertificateWarningDialog : Adw.AlertDialog {
public enum Result {
DONT_TRUST,
TRUST,
@ -13,59 +15,49 @@ public class CertificateWarningDialog {
private const string BULLET = "&#8226; ";
private Gtk.Dialog dialog;
[GtkChild] private unowned Gtk.Label top_label;
[GtkChild] private unowned Gtk.Label warnings_label;
[GtkChild] private unowned Gtk.Label trust_label;
[GtkChild] private unowned Gtk.Label dont_trust_label;
[GtkChild] private unowned Gtk.Label contact_label;
public CertificateWarningDialog(Gtk.Window? parent,
Geary.AccountInformation account,
public CertificateWarningDialog(Geary.AccountInformation account,
Geary.ServiceInformation service,
Geary.Endpoint endpoint,
bool is_validation) {
Gtk.Builder builder = GioUtil.create_builder("certificate_warning_dialog.glade");
this.title = _("Untrusted Connection: %s").printf(account.display_name);
dialog = (Gtk.Dialog) builder.get_object("CertificateWarningDialog");
dialog.transient_for = parent;
dialog.modal = true;
Gtk.Label title_label = (Gtk.Label) builder.get_object("untrusted_connection_label");
Gtk.Label top_label = (Gtk.Label) builder.get_object("top_label");
Gtk.Label warnings_label = (Gtk.Label) builder.get_object("warnings_label");
Gtk.Label trust_label = (Gtk.Label) builder.get_object("trust_label");
Gtk.Label dont_trust_label = (Gtk.Label) builder.get_object("dont_trust_label");
Gtk.Label contact_label = (Gtk.Label) builder.get_object("contact_label");
title_label.label = _("Untrusted Connection: %s").printf(account.display_name);
top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf(
this.top_label.label = _("The identity of the %s mail server at %s:%u could not be verified.").printf(
service.protocol.to_value(), service.host, service.port);
warnings_label.label = generate_warning_list(
this.warnings_label.label = generate_warning_list(
endpoint.tls_validation_warnings
);
warnings_label.use_markup = true;
this.warnings_label.use_markup = true;
trust_label.label =
this.trust_label.label =
"<b>"
+_("Selecting “Trust This Server” or “Always Trust This Server” may cause your username and password to be transmitted insecurely.")
+ "</b>";
trust_label.use_markup = true;
this.trust_label.use_markup = true;
if (is_validation) {
// could be a new or existing account
dont_trust_label.label =
this.dont_trust_label.label =
"<b>"
+ _("Selecting “Dont Trust This Server” will cause Geary not to access this server.")
+ "</b> "
+ _("Geary will not add or update this email account.");
} else {
// a registered account
dont_trust_label.label =
this.dont_trust_label.label =
"<b>"
+ _("Selecting “Dont Trust This Server” will cause Geary to stop accessing this account.")
+ "</b> ";
}
dont_trust_label.use_markup = true;
this.dont_trust_label.use_markup = true;
contact_label.label =
this.contact_label.label =
_("Contact your system administrator or email service provider if you have any question about these issues.");
}
@ -96,17 +88,14 @@ public class CertificateWarningDialog {
return builder.str;
}
public Result run() {
dialog.show_all();
int response = dialog.run();
dialog.destroy();
public async Result run(Gtk.Window? parent) {
string response = yield choose(parent, null);
// these values are defined in the Glade file
switch (response) {
case 1:
case "trust":
return Result.TRUST;
case 2:
case "always-trust":
return Result.ALWAYS_TRUST;
default:

View file

@ -9,10 +9,9 @@
* Displays technical details when a problem has been reported.
*/
[GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")]
public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
public class Dialogs.ProblemDetailsDialog : Adw.Dialog {
private const string ACTION_CLOSE = "problem-details-close";
private const string ACTION_SEARCH_TOGGLE = "toggle-search";
private const string ACTION_SEARCH_ACTIVATE = "activate-search";
@ -21,14 +20,11 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
};
private const ActionEntry[] WINDOW_ACTIONS = {
{ Action.Window.CLOSE, on_close },
{ ACTION_CLOSE, on_close },
{ ACTION_SEARCH_TOGGLE, on_logs_search_toggled, null, "false" },
{ ACTION_SEARCH_ACTIVATE, on_logs_search_activated },
};
public static void add_accelerators(Application.Client app) {
app.add_window_accelerators(ACTION_CLOSE, { "Escape" } );
app.add_window_accelerators(ACTION_SEARCH_ACTIVATE, { "<Ctrl>F" } );
}
@ -48,14 +44,8 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
private Geary.ServiceInformation? service;
public ProblemDetailsDialog(Gtk.Window? parent,
Application.Client application,
public ProblemDetailsDialog(Application.Client application,
Geary.ProblemReport report) {
Object(
transient_for: parent,
use_header_bar: 1
);
Geary.AccountProblemReport? account_report =
report as Geary.AccountProblemReport;
Geary.ServiceProblemReport? service_report =
@ -79,9 +69,7 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
error, account, service
);
this.log_pane = new Components.InspectorLogView(
application.config, account
);
this.log_pane = new Components.InspectorLogView(account);
this.log_pane.load(report.earliest_log, report.latest_log);
this.log_pane.record_selection_changed.connect(
on_logs_selection_changed
@ -101,11 +89,15 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
this.stack.add_titled(this.system_pane, "system_pane", _("System"));
}
public override bool key_press_event(Gdk.EventKey event) {
[GtkCallback]
private bool on_key_pressed(Gtk.EventControllerKey controller,
uint keyval,
uint keycode,
Gdk.ModifierType state) {
bool ret = Gdk.EVENT_PROPAGATE;
if (this.log_pane.search_mode_enabled &&
event.keyval == Gdk.Key.Escape) {
keyval == Gdk.Key.Escape) {
// Manually deactivate search so the button stays in sync
this.search_button.set_active(false);
ret = Gdk.EVENT_STOP;
@ -115,18 +107,19 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
this.log_pane.search_mode_enabled) {
// Ensure <Space> and others are passed to the search
// entry before getting used as an accelerator.
ret = this.log_pane.handle_key_press(event);
ret = controller.forward(this.log_pane);
}
if (ret == Gdk.EVENT_PROPAGATE) {
ret = base.key_press_event(event);
}
//XXX GTK4 not sure how to handle this
// if (ret == Gdk.EVENT_PROPAGATE) {
// ret = base.key_press_event(event);
// }
if (ret == Gdk.EVENT_PROPAGATE &&
!this.log_pane.search_mode_enabled) {
// Nothing has handled the event yet, and search is not
// active, so see if we want to activate it now.
ret = this.log_pane.handle_key_press(event);
ret = controller.forward(this.log_pane);
if (ret == Gdk.EVENT_STOP) {
this.search_button.set_active(true);
}
@ -135,10 +128,9 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
return ret;
}
private async void save(string path,
private async void save(GLib.File dest,
GLib.Cancellable? cancellable)
throws GLib.Error {
GLib.File dest = GLib.File.new_for_path(path);
GLib.FileIOStream dest_io = yield dest.replace_readwrite_async(
null,
false,
@ -207,39 +199,29 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
string clipboard_value = (string) bytes.get_data();
if (!Geary.String.is_empty(clipboard_value)) {
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(clipboard_value, -1);
get_clipboard().set_text(clipboard_value);
}
}
[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 Problem Report - %F %T.txt"
)
save_as.begin();
}
private async void save_as() {
var dialog = new Gtk.FileDialog();
dialog.title = _("Save As");
dialog.accept_label = _("Save As");
dialog.initial_name = new DateTime.now_local().format(
"Geary Problem Report - %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 problem report data: %s", err.message
);
}
}
);
try {
File? file = yield dialog.save(get_root() as Gtk.Window, null);
if (file != null)
yield this.save(file, null);
} catch (Error err) {
warning("Failed to save problem report data: %s", err.message);
}
}
@ -257,9 +239,4 @@ public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
private void on_logs_search_activated() {
this.search_button.set_active(true);
}
private void on_close() {
destroy();
}
}

View file

@ -8,74 +8,50 @@
* Displays a dialog for collecting the user's password, without allowing them to change their
* other data.
*/
public class PasswordDialog {
// We can't keep these in the glade file, because Gnome doesn't want markup in translatable
// strings, and Glade doesn't support the "larger" size attribute. See this bug report for
// details: https://bugzilla.gnome.org/show_bug.cgi?id=679006
private const string PRIMARY_TEXT_MARKUP = "<span weight=\"bold\" size=\"larger\">%s</span>";
private const string PRIMARY_TEXT_FIRST_TRY = _("Geary requires your email password to continue");
[GtkTemplate (ui = "/org/gnome/Geary/password-dialog.ui")]
public class PasswordDialog : Adw.AlertDialog {
private Gtk.Dialog dialog;
private Gtk.Entry entry_password;
private Gtk.CheckButton check_remember_password;
private Gtk.Button ok_button;
public string password { get; private set; default = ""; }
public bool remember_password { get; private set; }
[GtkChild] private unowned Adw.PreferencesGroup prefs_group;
[GtkChild] private unowned Adw.ActionRow username_row;
[GtkChild] private unowned Adw.PasswordEntryRow password_row;
[GtkChild] private unowned Adw.SwitchRow remember_password_row;
public PasswordDialog(Gtk.Window? parent,
Geary.AccountInformation account,
Geary.ServiceInformation service,
Geary.Credentials? credentials) {
Gtk.Builder builder = GioUtil.create_builder("password-dialog.glade");
dialog = (Gtk.Dialog) builder.get_object("PasswordDialog");
dialog.transient_for = parent;
dialog.set_type_hint(Gdk.WindowTypeHint.DIALOG);
dialog.set_default_response(Gtk.ResponseType.OK);
entry_password = (Gtk.Entry) builder.get_object("entry: password");
check_remember_password = (Gtk.CheckButton) builder.get_object("check: remember_password");
Gtk.Label label_username = (Gtk.Label) builder.get_object("label: username");
Gtk.Label label_smtp = (Gtk.Label) builder.get_object("label: smtp");
// Load translated text for labels with markup unsupported by glade.
Gtk.Label primary_text_label = (Gtk.Label) builder.get_object("primary_text_label");
primary_text_label.set_markup(PRIMARY_TEXT_MARKUP.printf(PRIMARY_TEXT_FIRST_TRY));
if (credentials != null) {
label_username.set_text(credentials.user);
entry_password.set_text(credentials.token ?? "");
this.username_row.subtitle = credentials.user;
this.password_row.text = credentials.token ?? "";
}
check_remember_password.active = service.remember_password;
this.remember_password_row.active = service.remember_password;
if ((service.protocol == Geary.Protocol.SMTP)) {
label_smtp.show();
this.prefs_group.title = _("SMTP Credentials");
}
ok_button = (Gtk.Button) builder.get_object("authenticate_button");
refresh_ok_button_sensitivity();
entry_password.changed.connect(refresh_ok_button_sensitivity);
this.password_row.changed.connect(refresh_ok_button_sensitivity);
}
private void refresh_ok_button_sensitivity() {
ok_button.sensitive = !Geary.String.is_empty_or_whitespace(entry_password.get_text());
string password = this.password_row.text;
set_response_enabled("authenticate", !Geary.String.is_empty_or_whitespace(password));
}
public bool run() {
dialog.show();
public async string? get_password(Gtk.Window? parent,
out bool remember_password) {
string response = yield choose(parent, null);
Gtk.ResponseType response = (Gtk.ResponseType) dialog.run();
if (response == Gtk.ResponseType.OK) {
password = entry_password.get_text();
remember_password = check_remember_password.active;
if (response == "cancel") {
remember_password = false;
return null;
}
dialog.destroy();
return (response == Gtk.ResponseType.OK);
remember_password = this.remember_password_row.active;
string password = this.password_row.text;
close();
return password;
}
}

View file

@ -80,6 +80,8 @@ public class FolderList.FolderEntry :
entry_changed();
}
//XXX GTK4 I have no idea yet
#if 0
public bool internal_drop_received(Sidebar.Tree parent,
Gdk.DragContext context,
Gtk.SelectionData data) {
@ -104,6 +106,7 @@ public class FolderList.FolderEntry :
}
return handled;
}
#endif
public override int get_count() {
switch (this.context.displayed_count) {

View file

@ -7,10 +7,6 @@
public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
public const Gtk.TargetEntry[] TARGET_ENTRY_LIST = {
{ "application/x-geary-mail", Gtk.TargetFlags.SAME_APP, 0 }
};
private const int INBOX_ORDINAL = -2; // First account branch is zero
private const int SEARCH_ORDINAL = -1;
@ -29,7 +25,9 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
public Tree() {
base(TARGET_ENTRY_LIST, Gdk.DragAction.COPY | Gdk.DragAction.MOVE, drop_handler);
//XXX GTK4 need to set up proper GdkContentFormats here
base(new Gdk.ContentFormats({ "application/x-geary-mail" }),
Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
base_ref();
set_activate_on_single_click(true);
entry_selected.connect(on_entry_selected);
@ -37,9 +35,12 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
// GtkTreeView binds Ctrl+N to "move cursor to next". Not so interested in that, so we'll
// remove it.
//XXX GTK4
#if 0
unowned Gtk.BindingSet? binding_set = Gtk.BindingSet.find("GtkTreeView");
assert(binding_set != null);
Gtk.BindingEntry.remove(binding_set, Gdk.Key.N, Gdk.ModifierType.CONTROL_MASK);
#endif
this.visible = true;
}
@ -48,9 +49,20 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
base_unref();
}
public override void get_preferred_width(out int minimum_size, out int natural_size) {
minimum_size = 360;
natural_size = 500;
public override void measure(Gtk.Orientation orientation,
int for_size,
out int minimum,
out int natural,
out int minimum_baseline,
out int natural_baseline) {
if (orientation == Gtk.Orientation.HORIZONTAL) {
minimum = 180;
natural = 300;
} else {
//XXX GTK4 - I have no idea what to put here
}
minimum_baseline = natural_baseline = -1;
}
public void set_has_new(Geary.Folder folder, bool has_new) {
@ -68,10 +80,6 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
}
}
private void drop_handler(Gdk.DragContext context, Sidebar.Entry? entry,
Gtk.SelectionData data, uint info, uint time) {
}
private FolderEntry? get_folder_entry(Geary.Folder folder) {
AccountBranch? account_branch = account_branches.get(folder.account);
return (account_branch == null ? null :
@ -80,7 +88,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
public override bool accept_cursor_changed() {
bool can_switch = true;
var parent = get_toplevel() as Application.MainWindow;
var parent = get_root() as Application.MainWindow;
if (parent != null) {
can_switch = parent.close_composer(false);
}
@ -221,6 +229,8 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
folder_selected(null);
}
// XXX GTK4 I'm not sur eif this is needed still?
#if 0
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
// Run the base version first.
bool ret = base.drag_motion(context, x, y, time);
@ -236,6 +246,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
}
return ret;
}
#endif
public void set_search(Geary.Engine engine,
Geary.App.SearchFolder search_folder) {

View file

@ -41,7 +41,10 @@ client_vala_sources = files(
'accounts/accounts-editor-list-pane.vala',
'accounts/accounts-editor-row.vala',
'accounts/accounts-editor-servers-pane.vala',
'accounts/accounts-mailbox-editor-dialog.vala',
'accounts/accounts-service-information-widget.vala',
'accounts/accounts-signature-web-view.vala',
'accounts/accounts-tls-combo-row.vala',
'accounts/accounts-manager.vala',
'client-action.vala',
@ -49,31 +52,29 @@ client_vala_sources = files(
'components/components-attachment-pane.vala',
'components/components-conversation-actions.vala',
'components/components-entry-undo.vala',
'components/components-headerbar-application.vala',
'components/components-headerbar-conversation-list.vala',
'components/components-headerbar-conversation.vala',
'components/components-info-bar-stack.vala',
'components/components-info-bar.vala',
'components/components-inspector.vala',
'components/components-in-app-notification.vala',
'components/components-inspector-error-view.vala',
'components/components-inspector-log-view.vala',
'components/components-inspector-system-view.vala',
'components/components-placeholder-pane.vala',
'components/components-preferences-window.vala',
'components/components-preferences-dialog.vala',
'components/components-problem-report-info-bar.vala',
'components/components-reflow-box.c',
'components/components-search-bar.vala',
'components/components-validator.vala',
'components/components-validator-group.vala',
'components/components-web-view.vala',
'components/count-badge.vala',
'components/folder-popover.vala',
'components/folder-popover-row.vala',
'components/icon-factory.vala',
'components/monitored-progress-bar.vala',
'components/monitored-spinner.vala',
'components/stock.vala',
'composer/composer-addresses-row.vala',
'composer/composer-application-interface.vala',
'composer/composer-box.vala',
'composer/composer-container.vala',
@ -100,8 +101,6 @@ client_vala_sources = files(
'conversation-viewer/conversation-viewer.vala',
'conversation-viewer/conversation-web-view.vala',
'dialogs/alert-dialog.vala',
'dialogs/attachment-dialog.vala',
'dialogs/certificate-warning-dialog.vala',
'dialogs/dialogs-problem-details-dialog.vala',
'dialogs/password-dialog.vala',
@ -162,18 +161,18 @@ client_dependencies = [
gio,
gmime,
goa,
gspell,
gtk,
icu_uc,
javascriptcoregtk,
json_glib,
libhandy,
libadwaita,
libmath,
libpeas,
libsecret,
libspelling,
libxml,
posix,
webkit2gtk,
webkitgtk,
]
client_build_dir = meson.current_build_dir()
@ -191,7 +190,7 @@ client_vala_args += [
)
]
if webkit2gtk.version().version_compare('<2.31')
if webkitgtk.version().version_compare('<2.31')
client_vala_args += [ '--define=WEBKIT_PLUGINS_SUPPORTED' ]
endif

View file

@ -97,7 +97,7 @@ public class Plugin.DesktopNotifications :
Email email
) throws GLib.Error {
string title = to_notitication_title(folder.account, total);
GLib.Icon icon = null;
GLib.Icon? icon = null;
Geary.RFC822.MailboxAddress? originator = email.get_primary_originator();
if (originator != null) {
ContactStore contacts =
@ -132,20 +132,44 @@ public class Plugin.DesktopNotifications :
);
}
int window_scale = 1;
Gdk.Display? display = Gdk.Display.get_default();
if (display != null) {
Gdk.Monitor? monitor = display.get_primary_monitor();
if (monitor != null) {
window_scale = monitor.scale_factor;
}
Gdk.Texture texture;
if (icon != null) {
var icon_stream = yield ((LoadableIcon) icon).load_async(32, null);
var pixbuf = yield new Gdk.Pixbuf.from_stream_at_scale_async(icon_stream, 32, 32, false);
texture = Gdk.Texture.for_pixbuf(pixbuf);
} else {
texture = generate_fallback_avatar(title);
}
var avatar = new Hdy.Avatar(32, title, true);
avatar.loadable_icon = icon as GLib.LoadableIcon;
icon = yield avatar.draw_to_pixbuf_async(32, window_scale, null);
issue_arrived_notification(title, body, texture, folder, email.identifier);
}
issue_arrived_notification(title, body, icon, folder, email.identifier);
private Gdk.Texture generate_fallback_avatar(string title) {
Gsk.Renderer renderer = new Gsk.VulkanRenderer();
try {
renderer.realize(null);
} catch (GLib.Error error) {
warning("Couldn't realize vulkan renderer: %s", error.message);
renderer = new Gsk.CairoRenderer();
try {
renderer.realize(null);
} catch (GLib.Error error) {
warning("Couldn't realize Cairo renderer: %s", error.message);
}
}
var avatar = new Adw.Avatar(32, title, true);
var paintable = new Gtk.WidgetPaintable(avatar);
// Ideally we could use Adw.Avatar.draw_to_texture(),
// but that unfortunately relies on a Gtk.Native existing already
var snapshot = new Gtk.Snapshot();
paintable.snapshot(snapshot, 32, 32);
Gsk.RenderNode node = snapshot.to_node();
Gdk.Texture texture = renderer.render_texture(node, null);
renderer.unrealize();
return texture;
}
private void notify_general(Folder folder, int total, int added) {

View file

@ -178,7 +178,7 @@ public class Plugin.MailMerge :
private async void merge_email(EmailIdentifier id,
GLib.File? default_csv_file) {
var csv_file = default_csv_file ?? show_merge_data_chooser();
var csv_file = default_csv_file ?? yield show_merge_data_chooser();
if (csv_file != null) {
try {
var csv_input = yield csv_file.read_async(
@ -297,7 +297,7 @@ public class Plugin.MailMerge :
}
private async void load_composer_data(Composer composer) {
var data = show_merge_data_chooser();
var data = yield show_merge_data_chooser();
if (data != null) {
var insert_field_action = new GLib.SimpleAction(
ACTION_INSERT_FIELD,
@ -388,26 +388,20 @@ public class Plugin.MailMerge :
return action_bar;
}
private GLib.File? show_merge_data_chooser() {
var chooser = new Gtk.FileChooserNative(
/// Translators: File chooser title after invoking mail
/// merge in composer
_("Mail Merge"),
null, OPEN,
_("_Open"),
_("_Cancel")
);
private async GLib.File? show_merge_data_chooser() {
var dialog = new Gtk.FileDialog();
/// Translators: Filechooser title after invoking mail merge in composer
dialog.title = _("Mail Merge");
var csv_filter = new Gtk.FileFilter();
/// Translators: File chooser filer label
csv_filter.set_filter_name(_("Comma separated values (CSV)"));
csv_filter.add_mime_type("text/csv");
chooser.add_filter(csv_filter);
var filters = new GLib.ListStore(typeof(Gtk.FileFilter));
filters.append(csv_filter);
dialog.filters = filters;
return (
chooser.run() == Gtk.ResponseType.ACCEPT
? chooser.get_file()
: null
);
return yield dialog.open(null, null);
}
private void insert_field(Composer composer, string field) {

View file

@ -59,4 +59,4 @@ plugin_test = executable(
install: false
)
test(plugin_name + '-test', plugin_test)
# test(plugin_name + '-test', plugin_test)

View file

@ -6,7 +6,6 @@
plugin_dependencies = [
config_dep,
folks,
gdk,
client_dep,
engine_dep,
gee,
@ -14,10 +13,10 @@ plugin_dependencies = [
goa,
gtk,
javascriptcoregtk,
libhandy,
libadwaita,
libmath,
libpeas,
webkit2gtk,
webkitgtk,
]
plugin_c_args = geary_c_args

View file

@ -85,6 +85,7 @@ public class Sidebar.Header : Sidebar.Grouping, Sidebar.EmphasizableEntry {
public interface Sidebar.Contextable : Object {
// Return null if the context menu should not be invoked for this event
public abstract Gtk.Menu? get_sidebar_context_menu(Gdk.EventButton event);
//XXX GTK4 is this used?
// public abstract Gtk.PopoverMenu? get_sidebar_context_menu(Gdk.EventButton event);
}

View file

@ -27,24 +27,19 @@ public class SidebarCountCellRenderer : Gtk.CellRenderer {
natural_size = minimum_size;
}
public override void render(Cairo.Context ctx, Gtk.Widget widget, Gdk.Rectangle background_area,
Gdk.Rectangle cell_area, Gtk.CellRendererState flags) {
unread_count.count = counter;
public override void snapshot(Gtk.Snapshot snapshot,
Gtk.Widget widget,
Gdk.Rectangle background_area,
Gdk.Rectangle cell_area,
Gtk.CellRendererState flags) {
this.unread_count.count = this.counter;
Graphene.Rect cell_rect = { { cell_area.x, cell_area.y } , { cell_area.width, cell_area.height } };
Cairo.Context ctx = snapshot.append_cairo(cell_rect);
// Compute x and y locations to right-align and vertically center the count.
int x = cell_area.x + (cell_area.width - unread_count.get_width(widget)) - HORIZONTAL_MARGIN;
int y = cell_area.y + ((cell_area.height - unread_count.get_height(widget)) / 2);
unread_count.render(widget, ctx, x, y, false);
}
// This is implemented because it's required; ignore it and look at get_preferred_width() instead.
public override void get_size(Gtk.Widget widget, Gdk.Rectangle? cell_area, out int x_offset,
out int y_offset, out int width, out int height) {
// Set values to avoid compiler warning.
x_offset = 0;
y_offset = 0;
width = 0;
height = 0;
}
}

View file

@ -50,12 +50,16 @@ public interface Sidebar.DestroyableEntry : Sidebar.Entry {
}
public interface Sidebar.InternalDropTargetEntry : Sidebar.Entry {
//XXX GTK4 I have no idea yet
#if 0
// Returns true if drop was successful
public abstract bool internal_drop_received(Sidebar.Tree parent,
Gdk.DragContext context,
Gtk.SelectionData data);
#endif
}
public interface Sidebar.InternalDragSourceEntry : Sidebar.Entry {
public abstract void prepare_selection_data(Gtk.SelectionData data);
//XXX GTK4: is this even used?
// public abstract void prepare_selection_data(Gtk.SelectionData data);
}

View file

@ -9,8 +9,7 @@ public class Sidebar.Tree : Gtk.TreeView {
// Only one ExternalDropHandler can be registered with the Tree; it's responsible for completing
// the "drag-data-received" signal properly.
public delegate void ExternalDropHandler(Gdk.DragContext context, Sidebar.Entry? entry,
Gtk.SelectionData data, uint info, uint time);
public delegate void ExternalDropHandler(Gtk.DropTarget context, Sidebar.Entry? entry);
private class EntryWrapper : Object {
public Sidebar.Entry entry;
@ -72,7 +71,7 @@ public class Sidebar.Tree : Gtk.TreeView {
private int editing_disabled = 0;
private bool mask_entry_selected_signal = false;
private weak EntryWrapper? selected_wrapper = null;
private Gtk.Menu? default_context_menu = null;
private Gtk.PopoverMenu? default_context_menu = null;
private bool is_internal_drag_in_progress = false;
private Sidebar.Entry? internal_drag_source_entry = null;
private Gtk.TreeRowReference? old_path_ref = null;
@ -89,11 +88,10 @@ public class Sidebar.Tree : Gtk.TreeView {
public signal void branch_shown(Sidebar.Branch branch, bool shown);
public Tree(Gtk.TargetEntry[] target_entries, Gdk.DragAction actions,
ExternalDropHandler drop_handler, Gtk.IconTheme? theme = null) {
public Tree(Gdk.ContentFormats formats, Gdk.DragAction actions, Gtk.IconTheme? theme = null) {
set_model(store);
icon_theme = theme;
get_style_context().add_class("sidebar");
add_css_class("navigation-sidebar");
text_column = new Gtk.TreeViewColumn();
text_column.set_expand(true);
@ -131,7 +129,7 @@ public class Sidebar.Tree : Gtk.TreeView {
// It Would Be Nice if the target entries and actions were gleaned by querying each
// Sidebar.Entry as it was added, but that's a tad too complicated for our needs
// currently
enable_model_drag_dest(target_entries, actions);
enable_model_drag_dest(formats, actions);
// Drag source removed as per http://redmine.yorba.org/issues/4701
//
@ -143,11 +141,23 @@ public class Sidebar.Tree : Gtk.TreeView {
this.drop_handler = drop_handler;
popup_menu.connect(on_context_menu_keypress);
Gtk.DragSource drag_source = new Gtk.DragSource();
drag_source.drag_begin.connect(on_drag_source_begin);
drag_source.drag_end.connect(on_drag_source_end);
drag_source.prepare.connect(on_drag_source_prepare);
add_controller(drag_source);
drag_begin.connect(on_drag_begin);
drag_end.connect(on_drag_end);
drag_motion.connect(on_drag_motion);
//XXX GTK4 - need to figure out the params still
Gtk.DropTarget drop_target = new Gtk.DropTarget(Type.INVALID, Gdk.DragAction.COPY | Gdk.DragAction.MOVE);
drop_target.enter.connect(on_drop_target_enter);
add_controller(drop_target);
var key_controller = new Gtk.EventControllerKey();
key_controller.key_pressed.connect(on_key_pressed);
add_controller(key_controller);
var click_gesture = new Gtk.GestureClick();
click_gesture.pressed.connect(on_button_pressed);
add_controller(click_gesture);
}
~Tree() {
@ -172,29 +182,31 @@ public class Sidebar.Tree : Gtk.TreeView {
renderer.visible = counter_renderer != null && counter_renderer.counter > 0;
}
private void on_drag_begin(Gdk.DragContext ctx) {
is_internal_drag_in_progress = true;
private void on_drag_source_begin(Gtk.DragSource drag_source, Gdk.Drag drag) {
this.is_internal_drag_in_progress = true;
}
private void on_drag_end(Gdk.DragContext ctx) {
is_internal_drag_in_progress = false;
internal_drag_source_entry = null;
private void on_drag_source_end(Gtk.DragSource drag_source, Gdk.Drag drag, bool delete_data) {
this.is_internal_drag_in_progress = false;
this.internal_drag_source_entry = null;
}
private bool on_drag_motion (Gdk.DragContext context, int x, int y, uint time_) {
if (is_internal_drag_in_progress && internal_drag_source_entry == null) {
private Gdk.DragAction on_drop_target_enter(Gtk.DropTarget drop_target, double x, double y) {
if (this.is_internal_drag_in_progress && this.internal_drag_source_entry == null) {
Gtk.TreePath? path;
Gtk.TreeViewDropPosition position;
get_dest_row_at_pos(x, y, out path, out position);
get_dest_row_at_pos((int) x, (int) y, out path, out position);
if (path != null) {
EntryWrapper wrapper = get_wrapper_at_path(path);
if (wrapper != null)
internal_drag_source_entry = wrapper.entry;
if (wrapper != null) {
this.internal_drag_source_entry = wrapper.entry;
return Gdk.DragAction.COPY | Gdk.DragAction.MOVE;
}
}
}
return false;
return 0;
}
private bool has_wrapper(Sidebar.Entry entry) {
@ -231,8 +243,8 @@ public class Sidebar.Tree : Gtk.TreeView {
return get_wrapper_at_iter(iter);
}
public void set_default_context_menu(Gtk.Menu context_menu) {
default_context_menu = context_menu;
public void set_default_context_menu(Gtk.PopoverMenu context_menu) {
this.default_context_menu = context_menu;
}
// Note that this method will result in the "entry-selected" signal to fire if mask_signal
@ -296,7 +308,7 @@ public class Sidebar.Tree : Gtk.TreeView {
return true;
}
public override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn column) {
public override void row_activated(Gtk.TreePath path, Gtk.TreeViewColumn? column) {
if (column != text_column)
return;
@ -750,17 +762,10 @@ public class Sidebar.Tree : Gtk.TreeView {
return (wrapper != null) ? (wrapper.entry is Sidebar.SelectableEntry) : false;
}
private Gtk.TreePath? get_path_from_event(Gdk.EventButton event) {
int x, y;
Gdk.ModifierType mask;
event.window.get_device_position(
event.get_seat().get_pointer(),
out x, out y, out mask
);
private Gtk.TreePath? get_path_from_position(double x, double y) {
int cell_x, cell_y;
Gtk.TreePath path;
return get_path_at_pos(x, y, out path, null, out cell_x, out cell_y) ? path : null;
return get_path_at_pos((int) x, (int) y, out path, null, out cell_x, out cell_y) ? path : null;
}
private Gtk.TreePath? get_current_path() {
@ -771,63 +776,57 @@ public class Sidebar.Tree : Gtk.TreeView {
return rows.length() != 0 ? rows.nth_data(0) : null;
}
private bool on_context_menu_keypress() {
GLib.List<Gtk.TreePath> rows = get_selection().get_selected_rows(null);
if (rows == null)
return false;
Gtk.TreePath? path = rows.data;
if (path == null)
return false;
scroll_to_cell(path, null, false, 0, 0);
return popup_context_menu(path);
}
private bool popup_context_menu(Gtk.TreePath path, Gdk.EventButton? event = null) {
private bool popup_context_menu(Gtk.TreePath path, Gdk.Rectangle? area = null) {
EntryWrapper? wrapper = get_wrapper_at_path(path);
if (wrapper == null)
return false;
//XXX GTK4
#if 0
Sidebar.Contextable? contextable = wrapper.entry as Sidebar.Contextable;
if (contextable == null)
return false;
Gtk.Menu? context_menu = contextable.get_sidebar_context_menu(event);
Gtk.PopoverMenu? context_menu = contextable.get_sidebar_context_menu(event);
if (context_menu == null)
return false;
context_menu.popup_at_pointer(event);
if (area != null)
context_menu.set_pointing_to(area);
context_menu.popup();
#endif
return true;
}
private bool popup_default_context_menu(Gdk.EventButton event) {
if (default_context_menu != null)
default_context_menu.popup_at_pointer(event);
return true;
private void popup_default_context_menu(Gdk.Rectangle area) {
if (this.default_context_menu == null)
return;
this.default_context_menu.set_pointing_to(area);
this.default_context_menu.popup();
}
public override bool button_press_event(Gdk.EventButton event) {
Gtk.TreePath? path = get_path_from_event(event);
private void on_button_pressed(Gtk.GestureClick click_gesture, int n_pressed, double x, double y) {
Gtk.TreePath? path = get_path_from_position(x, y);
if (event.button == 3 && event.type == Gdk.EventType.BUTTON_PRESS) {
var button = click_gesture.get_current_button();
if (button == Gdk.BUTTON_SECONDARY && n_pressed == 1) {
Gdk.Rectangle rect = { (int) x, (int) y, 1, 1 };
// single right click
if (path != null)
popup_context_menu(path, event);
popup_context_menu(path, rect);
else
popup_default_context_menu(event);
} else if (event.button == 1 && event.type == Gdk.EventType.BUTTON_PRESS) {
popup_default_context_menu(rect);
} else if (button == Gdk.BUTTON_PRIMARY) {
if (path == null) {
old_path_ref = null;
return base.button_press_event(event);
return;
}
EntryWrapper? wrapper = get_wrapper_at_path(path);
if (wrapper == null) {
old_path_ref = null;
return base.button_press_event(event);
return;
}
// Is this a click on an already-highlighted tree item?
@ -836,7 +835,7 @@ public class Sidebar.Tree : Gtk.TreeView {
// yes, don't allow single-click editing, but
// pass the event on for dragging.
text_renderer.editable = false;
return base.button_press_event(event);
return;
}
// Got click on different tree item, make sure it is editable
@ -849,13 +848,11 @@ public class Sidebar.Tree : Gtk.TreeView {
// Remember what tree item is highlighted for next time.
old_path_ref = new Gtk.TreeRowReference(store, path);
}
return base.button_press_event(event);
}
public override bool key_press_event(Gdk.EventKey event) {
private bool on_key_pressed(Gtk.EventControllerKey key_controller, uint keyval, uint keycode, Gdk.ModifierType state) {
bool handled = false;
switch (Gdk.keyval_name(event.keyval)) {
switch (Gdk.keyval_name(keyval)) {
case "F2":
handled = rename_in_place();
break;
@ -865,9 +862,6 @@ public class Sidebar.Tree : Gtk.TreeView {
handled = (path != null) ? destroy_path(path) : false;
break;
}
if (!handled) {
handled = base.key_press_event(event);
}
return handled;
}
@ -905,35 +899,40 @@ public class Sidebar.Tree : Gtk.TreeView {
return true;
}
public override void drag_data_get(Gdk.DragContext context, Gtk.SelectionData selection_data,
uint info, uint time) {
InternalDragSourceEntry? drag_source = null;
private Gdk.ContentProvider? on_drag_source_prepare(Gtk.DragSource drag_source,
double x,
double y) {
InternalDragSourceEntry? drag_source_entry = null;
if (internal_drag_source_entry != null) {
Sidebar.SelectableEntry selectable =
internal_drag_source_entry as Sidebar.SelectableEntry;
if (selectable == null) {
drag_source = internal_drag_source_entry as InternalDragSourceEntry;
drag_source_entry = internal_drag_source_entry as InternalDragSourceEntry;
}
}
if (drag_source == null) {
if (drag_source_entry == null) {
Gtk.TreePath? selected_path = get_selected_path();
if (selected_path == null)
return;
return null;
EntryWrapper? wrapper = get_wrapper_at_path(selected_path);
if (wrapper == null)
return;
return null;
drag_source = wrapper.entry as InternalDragSourceEntry;
if (drag_source == null)
return;
drag_source_entry = wrapper.entry as InternalDragSourceEntry;
if (drag_source_entry == null)
return null;
}
drag_source.prepare_selection_data(selection_data);
//XXX GTK4, it looks like nothing is implementing this?
// drag_source_entry.prepare_selection_data(selection_data);
return null; //XXX GTK4 what do I return here?
}
//XXX GTK4 not sure how to do this yet
#if 0
public override void drag_data_received(Gdk.DragContext context, int x, int y,
Gtk.SelectionData selection_data, uint info, uint time) {
@ -974,10 +973,13 @@ public class Sidebar.Tree : Gtk.TreeView {
return;
}
//XXX GTK4 I have no idea yet
#if 0
bool success = targetable.internal_drop_received(
this, context, selection_data
);
Gtk.drag_finish(context, success, false, time);
#endif
}
public override bool drag_motion(Gdk.DragContext context, int x, int y, uint time) {
@ -998,6 +1000,7 @@ public class Sidebar.Tree : Gtk.TreeView {
return has_dest;
}
#endif
// Returns true if path is renameable, and selects the path as well.
private bool can_rename_path(Gtk.TreePath path) {
@ -1038,7 +1041,7 @@ public class Sidebar.Tree : Gtk.TreeView {
if (editable is Gtk.Entry) {
text_entry = (Gtk.Entry) editable;
text_entry.editing_done.connect(on_editing_done);
text_entry.focus_out_event.connect(on_editing_focus_out);
// text_entry.focus_out_event.connect(on_editing_focus_out);
text_entry.editable = true;
}
}
@ -1047,7 +1050,7 @@ public class Sidebar.Tree : Gtk.TreeView {
text_entry.editable = false;
text_entry.editing_done.disconnect(on_editing_done);
text_entry.focus_out_event.disconnect(on_editing_focus_out);
// text_entry.focus_out_event.disconnect(on_editing_focus_out);
}
private void on_editing_done() {
@ -1061,14 +1064,17 @@ public class Sidebar.Tree : Gtk.TreeView {
}
text_entry.editing_done.disconnect(on_editing_done);
text_entry.focus_out_event.disconnect(on_editing_focus_out);
// text_entry.focus_out_event.disconnect(on_editing_focus_out);
}
//XXX GTK4 I(m not sure how to remove the focus controller again, so commenting out for now
#if 0
private bool on_editing_focus_out(Gdk.EventFocus event) {
// We'll return false here, in case other parts of the app
// want to know if the button press event that caused
// us to lose focus have been fully handled.
return false;
}
#endif
}

View file

@ -23,7 +23,8 @@ namespace Util.Contact {
return true;
// Contact domain trusted
} else {
foreach (Geary.RFC822.MailboxAddress email in email_addresses) {
for (uint i = 0; i < email_addresses.get_n_items(); i++) {
var email = (Geary.RFC822.MailboxAddress) email_addresses.get_item(i);
if (email.domain in domains) {
return true;
}

View file

@ -85,8 +85,7 @@ namespace Util.Gtk {
*/
public inline int get_border_box_height(global::Gtk.Widget widget) {
global::Gtk.StyleContext style = widget.get_style_context();
global::Gtk.StateFlags flags = style.get_state();
global::Gtk.Border margin = style.get_margin(flags);
global::Gtk.Border margin = style.get_margin();
return widget.get_allocated_height() - margin.top - margin.bottom;
}
@ -218,15 +217,6 @@ namespace Util.Gtk {
return new_url;
}
public Gdk.RGBA rgba(double red, double green, double blue, double alpha) {
return Gdk.RGBA() {
red = red,
green = green,
blue = blue,
alpha = alpha
};
}
/* Connect this to Gtk.Widget.query_tooltip signal, will only show tooltip if label ellipsized */
public bool query_tooltip_label(global::Gtk.Widget widget, int x, int y, bool keyboard, global::Gtk.Tooltip tooltip) {
global::Gtk.Label label = widget as global::Gtk.Label;

View file

@ -8,8 +8,8 @@
/**
* Initialises GearyWebExtension for WebKit web processes.
*/
public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension extension,
Variant data) {
public void webkit_web_process_extension_initialize_with_user_data(WebKit.WebProcessExtension extension,
Variant data) {
bool logging_enabled = data.get_boolean();
Geary.Logging.init();
@ -26,7 +26,7 @@ public void webkit_web_extension_initialize_with_user_data(WebKit.WebExtension e
}
/**
* A WebExtension that manages Geary-specific behaviours in web processes.
* A WebProcessExtension that manages Geary-specific behaviours in web processes.
*/
public class GearyWebExtension : Object {
@ -43,15 +43,17 @@ public class GearyWebExtension : Object {
private const string EXTENSION_CLASS_SEND = "send";
private const string EXTENSION_CLASS_ALLOW_REMOTE_LOAD = "allowRemoteResourceLoad";
private WebKit.WebExtension extension;
private WebKit.WebProcessExtension extension;
public GearyWebExtension(WebKit.WebExtension extension) {
public GearyWebExtension(WebKit.WebProcessExtension extension) {
this.extension = extension;
extension.page_created.connect(on_page_created);
WebKit.ScriptWorld.get_default().window_object_cleared.connect(on_window_object_cleared);
}
//XXX GTK4 - it seems this is no longer possible?
#if 0
private void on_console_message(WebKit.WebPage page,
WebKit.ConsoleMessage message) {
string source = message.get_source_id();
@ -63,6 +65,7 @@ public class GearyWebExtension : Object {
message.get_text()
);
}
#endif
private bool on_send_request(WebKit.WebPage page,
WebKit.URIRequest request,
@ -128,9 +131,10 @@ public class GearyWebExtension : Object {
);
}
private void on_page_created(WebKit.WebExtension extension,
private void on_page_created(WebKit.WebProcessExtension extension,
WebKit.WebPage page) {
page.console_message_sent.connect(on_console_message);
//XXX GTK4
// page.console_message_sent.connect(on_console_message);
page.send_request.connect(on_send_request);
page.user_message_received.connect(on_page_message_received);
}

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="ImapConsole" parent="AdwApplicationWindow">
<property name="title">IMAP Console</property>
<property name="default-width">800</property>
<property name="default-height">600</property>
<child>
<object class="AdwToolbarView">
<property name="top-bar-style">raised</property>
<child type="top">
<object class="AdwHeaderBar">
</object>
</child>
<property name="content">
<object class="GtkBox" id="layout">
<property name="orientation">vertical</property>
<property name="spacing">4</property>
<child>
<object class="GtkScrolledWindow" id="scrolled_console">
<property name="vexpand">True</property>
<property name="hscrollbar-policy">automatic</property>
<property name="vscrollbar-policy">automatic</property>
<child>
<object class="GtkTextView" id="console">
<property name="editable">False</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkEntry" id="cmdline">
<signal name="activate" handler="on_activate"/>
</object>
</child>
<child>
<object class="GtkStatusbar" id="statusbar">
</object>
</child>
</object>
</property>
</object>
</child>
</template>
</interface>

Some files were not shown because too many files have changed in this diff Show more