Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3ed978e2 |
163 changed files with 13716 additions and 16536 deletions
|
|
@ -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
|
||||
|
|
|
|||
39
meson.build
39
meson.build
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
6
po/ca.po
6
po/ca.po
|
|
@ -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 "_Don’t 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
116
po/cs.po
|
|
@ -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 © 2016 – 2020 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:"
|
||||
|
|
|
|||
136
po/hu.po
136
po/hu.po
|
|
@ -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:"
|
||||
|
|
|
|||
363
po/pt_BR.po
363
po/pt_BR.po
|
|
@ -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 "Geary’s 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 "Geary’s 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
120
po/sv.po
|
|
@ -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
357
po/tr.po
|
|
@ -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 "Geary’s features include:"
|
||||
msgstr "Geary’nin ö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 HTML’de 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ı HTML’de 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 "Geary’s features include:"
|
||||
msgstr "Geary’nin ö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"
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
128
src/client/accounts/accounts-mailbox-editor-dialog.vala
Normal file
128
src/client/accounts/accounts-mailbox-editor-dialog.vala
Normal 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();
|
||||
}
|
||||
}
|
||||
173
src/client/accounts/accounts-service-information-widget.vala
Normal file
173
src/client/accounts/accounts-service-information-widget.vala
Normal 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?
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
55
src/client/accounts/accounts-tls-combo-row.vala
Normal file
55
src/client/accounts/accounts-tls-combo-row.vala
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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, _("Don’t _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 = _("Don’t _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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
159
src/client/components/components-preferences-dialog.vala
Normal file
159
src/client/components/components-preferences-dialog.vala
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
91
src/client/components/components-validator-group.vala
Normal file
91
src/client/components/components-validator-group.vala
Normal 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) {}
|
||||
}
|
||||
|
|
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
457
src/client/composer/composer-addresses-row.vala
Normal file
457
src/client/composer/composer-addresses-row.vala
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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("");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = "• ";
|
||||
|
||||
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 “Don’t 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 “Don’t 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:
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -59,4 +59,4 @@ plugin_test = executable(
|
|||
install: false
|
||||
)
|
||||
|
||||
test(plugin_name + '-test', plugin_test)
|
||||
# test(plugin_name + '-test', plugin_test)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
51
src/console/imap-console.ui
Normal file
51
src/console/imap-console.ui
Normal 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
Loading…
Add table
Add a link
Reference in a new issue