From 49316a935ce8d423d64daa9989639e343705563b Mon Sep 17 00:00:00 2001 From: Jakub Date: Wed, 27 May 2020 15:58:50 +0200 Subject: [PATCH] Shared GUI for Bridge and Import/Export --- .gitignore | 30 +- BUILDS.md | 23 +- Changelog.md | 42 + Makefile | 63 +- README.md | 8 +- cmd/Desktop-Bridge/main.go | 2 +- cmd/Import-Export/main.go | 2 +- go.mod | 2 + go.sum | 8 + internal/frontend/frontend.go | 10 +- internal/frontend/qml/GuiIE.qml | 417 +++++++ .../qml/ImportExportUI/AccountDelegate.qml | 432 +++++++ .../frontend/qml/ImportExportUI/Credits.qml | 92 ++ .../frontend/qml/ImportExportUI/DateBox.qml | 220 ++++ .../frontend/qml/ImportExportUI/DateInput.qml | 243 ++++ .../frontend/qml/ImportExportUI/DateRange.qml | 121 ++ .../qml/ImportExportUI/DateRangeFunctions.qml | 81 ++ .../qml/ImportExportUI/DateRangeMenu.qml | 151 +++ .../qml/ImportExportUI/DialogExport.qml | 457 ++++++++ .../qml/ImportExportUI/DialogImport.qml | 1027 +++++++++++++++++ .../qml/ImportExportUI/DialogYesNo.qml | 354 ++++++ .../qml/ImportExportUI/ExportStructure.qml | 151 +++ .../qml/ImportExportUI/FilterStructure.qml | 55 + .../qml/ImportExportUI/FolderRowButton.qml | 99 ++ .../frontend/qml/ImportExportUI/HelpView.qml | 129 +++ .../frontend/qml/ImportExportUI/IEStyle.qml | 106 ++ .../qml/ImportExportUI/ImportDelegate.qml | 164 +++ .../qml/ImportExportUI/ImportReport.qml | 216 ++++ .../qml/ImportExportUI/ImportReportCell.qml | 67 ++ .../qml/ImportExportUI/ImportSourceButton.qml | 89 ++ .../qml/ImportExportUI/ImportStructure.qml | 149 +++ .../qml/ImportExportUI/InlineDateRange.qml | 128 ++ .../qml/ImportExportUI/InlineLabelSelect.qml | 227 ++++ .../qml/ImportExportUI/LabelIconList.qml | 96 ++ .../qml/ImportExportUI/MainWindow.qml | 473 ++++++++ .../qml/ImportExportUI/OutputFormat.qml | 84 ++ .../qml/ImportExportUI/PopupEditFolder.qml | 311 +++++ .../qml/ImportExportUI/SelectFolderMenu.qml | 362 ++++++ .../qml/ImportExportUI/SelectLabelsMenu.qml | 29 + .../qml/ImportExportUI/SettingsView.qml | 148 +++ .../qml/ImportExportUI/VersionInfo.qml | 115 ++ internal/frontend/qml/ImportExportUI/qmldir | 31 + .../frontend/qml/ProtonUI/AccountView.qml | 4 +- .../frontend/qml/ProtonUI/BugReportWindow.qml | 1 + .../frontend/qml/ProtonUI/CheckBoxLabel.qml | 2 + .../frontend/qml/ProtonUI/DialogUpdate.qml | 14 +- internal/frontend/qml/ProtonUI/InputField.qml | 16 + .../frontend/qml/ProtonUI/PopupMessage.qml | 56 +- .../qml/ProtonUI/RoundedRectangle.qml | 115 ++ internal/frontend/qml/ProtonUI/Style.qml | 25 + .../frontend/qml/ProtonUI/WindowTitleBar.qml | 4 +- internal/frontend/qml/ProtonUI/qmldir | 1 + internal/frontend/qml/tst_GuiIE.qml | 970 ++++++++++++++++ internal/frontend/qt-common/Makefile.local | 6 + internal/frontend/qt-common/account_model.go | 236 ++++ internal/frontend/qt-common/accounts.go | 259 +++++ .../{qt/logs.cpp => qt-common/common.cpp} | 21 +- internal/frontend/qt-common/common.go | 130 +++ .../{qt/logs.h => qt-common/common.h} | 5 +- internal/frontend/qt-common/notification.go | 40 + internal/frontend/qt-common/path_status.go | 81 ++ internal/frontend/qt-ie/Makefile.local | 60 + internal/frontend/qt-ie/README.md | 55 + internal/frontend/qt-ie/enums.go | 68 ++ internal/frontend/qt-ie/error_list.go | 129 +++ internal/frontend/qt-ie/export.go | 125 ++ internal/frontend/qt-ie/folder_functions.go | 539 +++++++++ internal/frontend/qt-ie/folder_structure.go | 196 ++++ .../frontend/qt-ie/folder_structure_test.go | 65 ++ internal/frontend/qt-ie/frontend.go | 497 ++++++++ internal/frontend/qt-ie/frontend_nogui.go | 55 + internal/frontend/qt-ie/import.go | 89 ++ .../{qt/logs.go => qt-ie/notification.go} | 24 +- internal/frontend/qt-ie/types.go | 25 + internal/frontend/qt-ie/ui.go | 189 +++ internal/frontend/qt/Makefile.local | 8 +- internal/frontend/qt/frontend.go | 22 +- internal/frontend/qt/resources.qrc | 77 -- internal/frontend/qt/ui.go | 2 +- internal/frontend/resources.qrc | 116 ++ .../frontend/share/icons/envelope_open.png | Bin 0 -> 22900 bytes internal/frontend/share/icons/folder_open.png | Bin 0 -> 8875 bytes internal/frontend/share/icons/ie.icns | Bin 0 -> 272787 bytes internal/frontend/share/icons/ie.ico | Bin 0 -> 569990 bytes internal/frontend/share/icons/ie.svg | 31 + internal/frontend/types/types.go | 2 + internal/importexport/credits.go | 4 +- internal/importexport/importexport.go | 27 + internal/store/user_mailbox.go | 14 +- internal/transfer/mailbox.go | 15 + internal/transfer/mailbox_test.go | 43 + internal/transfer/transfer.go | 4 +- pkg/pmapi/labels.go | 18 + pkg/pmapi/labels_test.go | 16 + utils/credits.sh | 14 +- utils/enums.sh | 149 +++ 96 files changed, 11469 insertions(+), 209 deletions(-) create mode 100644 internal/frontend/qml/GuiIE.qml create mode 100644 internal/frontend/qml/ImportExportUI/AccountDelegate.qml create mode 100644 internal/frontend/qml/ImportExportUI/Credits.qml create mode 100644 internal/frontend/qml/ImportExportUI/DateBox.qml create mode 100644 internal/frontend/qml/ImportExportUI/DateInput.qml create mode 100644 internal/frontend/qml/ImportExportUI/DateRange.qml create mode 100644 internal/frontend/qml/ImportExportUI/DateRangeFunctions.qml create mode 100644 internal/frontend/qml/ImportExportUI/DateRangeMenu.qml create mode 100644 internal/frontend/qml/ImportExportUI/DialogExport.qml create mode 100644 internal/frontend/qml/ImportExportUI/DialogImport.qml create mode 100644 internal/frontend/qml/ImportExportUI/DialogYesNo.qml create mode 100644 internal/frontend/qml/ImportExportUI/ExportStructure.qml create mode 100644 internal/frontend/qml/ImportExportUI/FilterStructure.qml create mode 100644 internal/frontend/qml/ImportExportUI/FolderRowButton.qml create mode 100644 internal/frontend/qml/ImportExportUI/HelpView.qml create mode 100644 internal/frontend/qml/ImportExportUI/IEStyle.qml create mode 100644 internal/frontend/qml/ImportExportUI/ImportDelegate.qml create mode 100644 internal/frontend/qml/ImportExportUI/ImportReport.qml create mode 100644 internal/frontend/qml/ImportExportUI/ImportReportCell.qml create mode 100644 internal/frontend/qml/ImportExportUI/ImportSourceButton.qml create mode 100644 internal/frontend/qml/ImportExportUI/ImportStructure.qml create mode 100644 internal/frontend/qml/ImportExportUI/InlineDateRange.qml create mode 100644 internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml create mode 100644 internal/frontend/qml/ImportExportUI/LabelIconList.qml create mode 100644 internal/frontend/qml/ImportExportUI/MainWindow.qml create mode 100644 internal/frontend/qml/ImportExportUI/OutputFormat.qml create mode 100644 internal/frontend/qml/ImportExportUI/PopupEditFolder.qml create mode 100644 internal/frontend/qml/ImportExportUI/SelectFolderMenu.qml create mode 100644 internal/frontend/qml/ImportExportUI/SelectLabelsMenu.qml create mode 100644 internal/frontend/qml/ImportExportUI/SettingsView.qml create mode 100644 internal/frontend/qml/ImportExportUI/VersionInfo.qml create mode 100644 internal/frontend/qml/ImportExportUI/qmldir create mode 100644 internal/frontend/qml/ProtonUI/RoundedRectangle.qml create mode 100644 internal/frontend/qml/tst_GuiIE.qml create mode 100644 internal/frontend/qt-common/Makefile.local create mode 100644 internal/frontend/qt-common/account_model.go create mode 100644 internal/frontend/qt-common/accounts.go rename internal/frontend/{qt/logs.cpp => qt-common/common.cpp} (58%) create mode 100644 internal/frontend/qt-common/common.go rename internal/frontend/{qt/logs.h => qt-common/common.h} (71%) create mode 100644 internal/frontend/qt-common/notification.go create mode 100644 internal/frontend/qt-common/path_status.go create mode 100644 internal/frontend/qt-ie/Makefile.local create mode 100644 internal/frontend/qt-ie/README.md create mode 100644 internal/frontend/qt-ie/enums.go create mode 100644 internal/frontend/qt-ie/error_list.go create mode 100644 internal/frontend/qt-ie/export.go create mode 100644 internal/frontend/qt-ie/folder_functions.go create mode 100644 internal/frontend/qt-ie/folder_structure.go create mode 100644 internal/frontend/qt-ie/folder_structure_test.go create mode 100644 internal/frontend/qt-ie/frontend.go create mode 100644 internal/frontend/qt-ie/frontend_nogui.go create mode 100644 internal/frontend/qt-ie/import.go rename internal/frontend/{qt/logs.go => qt-ie/notification.go} (72%) create mode 100644 internal/frontend/qt-ie/types.go create mode 100644 internal/frontend/qt-ie/ui.go delete mode 100644 internal/frontend/qt/resources.qrc create mode 100644 internal/frontend/resources.qrc create mode 100644 internal/frontend/share/icons/envelope_open.png create mode 100644 internal/frontend/share/icons/folder_open.png create mode 100644 internal/frontend/share/icons/ie.icns create mode 100644 internal/frontend/share/icons/ie.ico create mode 100644 internal/frontend/share/icons/ie.svg create mode 100644 utils/enums.sh diff --git a/.gitignore b/.gitignore index 4f29a61e..f795287c 100644 --- a/.gitignore +++ b/.gitignore @@ -18,23 +18,29 @@ coverage.html mem.pprof # Auto generated frontend -frontend/qml/BridgeUI/*.qmlc -frontend/qml/ProtonUI/*.qmlc -frontend/qml/ProtonUI/fontawesome.ttf -frontend/qml/ProtonUI/images +internal/frontend/qml/BridgeUI/*.qmlc +internal/frontend/qml/ImportExportUI/*.qmlc +internal/frontend/qml/ProtonUI/*.qmlc +internal/frontend/qml/ProtonUI/fontawesome.ttf +internal/frontend/qml/ProtonUI/images +internal/frontend/qml/ImportExportUI/images frontend/qml/*.qmlc # Build files bridge_darwin_*.tgz cmd/Desktop-Bridge/deploy -internal/frontend/qt/moc.cpp -internal/frontend/qt/moc.go -internal/frontend/qt/moc.h -internal/frontend/qt/moc_cgo_darwin_darwin_amd64.go -internal/frontend/qt/moc_moc.h -internal/frontend/qt/rcc.cpp -internal/frontend/qt/rcc_cgo_darwin_darwin_amd64.go +internal/frontend/qt*/moc.cpp +internal/frontend/qt*/moc.go +internal/frontend/qt*/moc.h +internal/frontend/qt*/moc_cgo_*.go +internal/frontend/qt*/moc_moc.h +internal/frontend/qt*/rcc.cpp +internal/frontend/qt*/rcc.qrc +internal/frontend/qt*/rcc_cgo_*.go + internal/frontend/rcc.cpp internal/frontend/rcc.qrc -internal/frontend/rcc_cgo_darwin_darwin_amd64.go +internal/frontend/rcc_cgo_*.go vendor-cache/ + +/main.go \ No newline at end of file diff --git a/BUILDS.md b/BUILDS.md index 79afae80..3a1db764 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -1,4 +1,4 @@ -# Building ProtonMail Bridge app +# Building ProtonMail Bridge and Import-Export app ## Prerequisites * Go 1.13 @@ -19,6 +19,8 @@ Otherwise, the sending of crash reports will be disabled. export MSYSTEM= ``` + +### Build Bridge * in project root run ```bash @@ -26,9 +28,22 @@ make build ``` * The result will be stored in `./cmd/Destop-Bridge/deploy/${GOOS}/` - * for `linux`, the binary will have the name of the project directory (e.g `bridge`) - * for `windows`, the binary will have the file extension `.exe` (e.g `bridge.exe`) - * for `darwin`, the application will be created with name of the project directory (e.g `bridge.app`) + * for `linux`, the binary will have the name of the project directory (e.g `proton-bridge`) + * for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`) + * for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`) + +### Build Import-Export +* in project root run + +```bash +make build-ie +``` + +* The result will be stored in `./cmd/Import-Export/deploy/${GOOS}/` + * for `linux`, the binary will have the name of the project directory (e.g `proton-bridge`) + * for `windows`, the binary will have the file extension `.exe` (e.g `proton-bridge.exe`) + * for `darwin`, the application will be created with name of the project directory (e.g `proton-bridge.app`) + ## Useful tests, lints and checks In order to be able to run following commands please install the development dependencies: diff --git a/Changelog.md b/Changelog.md index 15220207..05861d1c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -60,6 +60,48 @@ Changelog [format](http://keepachangelog.com/en/1.0.0/) * GODT-280 Migrate to gopenpgp v2. * `Unlock()` call on pmapi-client unlocks both User keys and Address keys. * Salt is available via `AuthSalt()` method. +* GODT-394 Don't check SMTP message send time in integration tests. +* GODT-380 Adding IE GUI to Bridge repo +* GODT-380 Adding IE GUI to Bridge repo and building + * BR: extend functionality of PopupDialog + * BR: makefile APP_VERSION instead of BRIDGE_VERSION + * BR: use common logs function for Qt + * BR: change `go.progressDescription` to `string` + * IE: Rounded button has fa-icon + * IE: `Upgrade` → `Update` + * IE: Moving `AccountModel` to `qt-common` + * IE: Added `ReportBug` to `internal/importexport` + * IE: Added event watch in GUI + * IE: Removed `onLoginFinished` +* GODT-388 support for both bridge and import/export credentials by package users +* GODT-387 store factory to make store optional +* GODT-386 renamed bridge to general users and keep bridge only for bridge stuff +* GODT-308 better user error message when request is canceled +* GODT-312 validate recipient emails in send before asking for their public keys + +### Fixed +* GODT-356 Fix crash when removing account while mail client is fetching messages (regression from GODT-204). +* GODT-390 Don't logout user if AuthRefresh fails because internet was off. +* GODT-358 Bad timeouts with Alternative Routing. +* GODT-363 Drafts are not deleted when already created on webapp. +* GODT-390 Don't logout user if AuthRefresh fails because internet was off. +* GODT-341 Fixed flaky unittest for Store synchronization cooldown. +* Crash when failing to match necessary html element. +* Crash in message.combineParts when copying nil slice. +* Handle double charset better by using local ParseMediaType instead of mime.ParseMediaType. +* Don't remove log dir. +* GODT-422 Fix element not found (avoid listing credentials, prefer getting). +* GODT-404 Don't keep connections to proxy servers alive if user disables DoH. +* Ensure DoH is used at startup to load users for the initial auth. +* Issue causing deadlock when reloading users keys due to double-locking of a mutex. + +## [v1.2.7] Donghai-hotfix - beta (2020-05-07) + +### Added +* IMAP mailbox info update when new mailbox is created. +* GODT-72 Use ISO-8859-1 encoding if charset is not specified and it isn't UTF-8. + +### Changed * GODT-308 Better user error message when request is canceled. * GODT-162 User Agent does not contain bridge version, only client in format `client name/client version (os)`. * GODT-258 Update go-imap to v1. diff --git a/Makefile b/Makefile index e9592cff..0d959d64 100644 --- a/Makefile +++ b/Makefile @@ -3,19 +3,20 @@ export GO111MODULE=on # By default, the target OS is the same as the host OS, # but this can be overridden by setting TARGET_OS to "windows"/"darwin"/"linux". GOOS:=$(shell go env GOOS) +TARGET_CMD?=Desktop-Bridge TARGET_OS?=${GOOS} ## Build -.PHONY: build build-nogui check-has-go +.PHONY: build build-ie build-nogui build-ie-nogui check-has-go -BRIDGE_VERSION?=$(shell git describe --abbrev=0 --tags)-git +APP_VERSION?=$(shell git describe --abbrev=0 --tags)-git REVISION:=$(shell git rev-parse --short=10 HEAD) BUILD_TIME:=$(shell date +%FT%T%z) BUILD_TAGS?=pmapi_prod BUILD_FLAGS:=-tags='${BUILD_TAGS}' BUILD_FLAGS_NOGUI:=-tags='${BUILD_TAGS} nogui' -GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/pkg/constants.,Version=${BRIDGE_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME}) +GO_LDFLAGS:=$(addprefix -X github.com/ProtonMail/proton-bridge/pkg/constants.,Version=${APP_VERSION} Revision=${REVISION} BuildTime=${BUILD_TIME}) ifneq "${BUILD_LDFLAGS}" "" GO_LDFLAGS+= ${BUILD_LDFLAGS} endif @@ -23,7 +24,7 @@ GO_LDFLAGS:=-ldflags '${GO_LDFLAGS}' BUILD_FLAGS+= ${GO_LDFLAGS} BUILD_FLAGS_NOGUI+= ${GO_LDFLAGS} -DEPLOY_DIR:=cmd/Desktop-Bridge/deploy +DEPLOY_DIR:=cmd/${TARGET_CMD}/deploy ICO_FILES:= EXE:=$(shell basename ${CURDIR}) @@ -36,13 +37,22 @@ ifeq "${TARGET_OS}" "darwin" EXE:=${EXE}.app/Contents/MacOS/${EXE} endif EXE_TARGET:=${DEPLOY_DIR}/${TARGET_OS}/${EXE} + TGZ_TARGET:=bridge_${TARGET_OS}_${REVISION}.tgz +ifeq "${TARGET_CMD}" "Import-Export" + TGZ_TARGET:=ie_${TARGET_OS}_${REVISION}.tgz +endif build: ${TGZ_TARGET} +build-ie: + TARGET_CMD=Import-Export $(MAKE) build build-nogui: - go build ${BUILD_FLAGS_NOGUI} -o Desktop-Bridge cmd/Desktop-Bridge/main.go + go build ${BUILD_FLAGS_NOGUI} -o ${TARGET_CMD} cmd/${TARGET_CMD}/main.go + +build-ie-nogui: + TARGET_CMD=Import-Export $(MAKE) build-nogui ${TGZ_TARGET}: ${DEPLOY_DIR}/${TARGET_OS} rm -f $@ @@ -74,9 +84,9 @@ endif ${EXE_TARGET}: check-has-go gofiles ${ICO_FILES} update-vendor rm -rf deploy ${TARGET_OS} ${DEPLOY_DIR} - cp cmd/Desktop-Bridge/main.go . + cp cmd/${TARGET_CMD}/main.go . qtdeploy ${BUILD_FLAGS} ${QT_BUILD_TARGET} - mv deploy cmd/Desktop-Bridge + mv deploy cmd/${TARGET_CMD} rm -rf ${TARGET_OS} main.go logo.ico: ./internal/frontend/share/icons/logo.ico @@ -213,7 +223,7 @@ gofiles: ./internal/bridge/credits.go ./internal/bridge/release_notes.go ./inter ## Run and debug -.PHONY: run run-qt run-qt-cli run-nogui run-nogui-cli run-debug qmlpreview qt-fronted-clean clean +.PHONY: run run-ie run-qt run-ie-qt run-qt-cli run-nogui run-ie-nogui run-nogui-cli run-debug run-qml-preview run-ie-qml-preview clean-fronted-qt clean-fronted-qt-ie clean-fronted-qt-common clean VERBOSITY?=debug-client RUN_FLAGS:=-m -l=${VERBOSITY} @@ -225,27 +235,42 @@ run-qt-cli: ${EXE_TARGET} PROTONMAIL_ENV=dev ./$< ${RUN_FLAGS} -c run-nogui: clean-vendor gofiles - PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/Desktop-Bridge/main.go ${RUN_FLAGS} | tee last.log + PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} | tee last.log run-nogui-cli: clean-vendor gofiles - PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/Desktop-Bridge/main.go ${RUN_FLAGS} -c - -run-ie: - PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/Import-Export/main.go ${RUN_FLAGS} -c + PROTONMAIL_ENV=dev go run ${BUILD_FLAGS_NOGUI} cmd/${TARGET_CMD}/main.go ${RUN_FLAGS} -c run-debug: - PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS_NOGUI}" cmd/Desktop-Bridge/main.go -- ${RUN_FLAGS} + PROTONMAIL_ENV=dev dlv debug --build-flags "${BUILD_FLAGS_NOGUI}" cmd/${TARGET_CMD}/main.go -- ${RUN_FLAGS} run-qml-preview: - make -C internal/frontend/qt -f Makefile.local qmlpreview + $(MAKE) -C internal/frontend/qt -f Makefile.local qmlpreview + +run-ie-qml-preview: + $(MAKE) -C internal/frontend/qt-ie -f Makefile.local qmlpreview + +run-ie: + TARGET_CMD=Import-Export $(MAKE) run +run-ie-nogui: + TARGET_CMD=Import-Export $(MAKE) run-nogui +run-ie-qt: + TARGET_CMD=Import-Export $(MAKE) run-qt clean-frontend-qt: - make -C internal/frontend/qt -f Makefile.local clean + $(MAKE) -C internal/frontend/qt -f Makefile.local clean -clean-vendor: clean-frontend-qt +clean-frontend-qt-ie: + $(MAKE) -C internal/frontend/qt-ie -f Makefile.local clean + +clean-frontend-qt-common: + $(MAKE) -C internal/frontend/qt-common -f Makefile.local clean + + +clean-vendor: clean-frontend-qt clean-frontend-qt-ie clean-frontend-qt-common rm -rf ./vendor -clean: clean-frontend-qt +clean: clean-vendor rm -rf vendor-cache rm -rf cmd/Desktop-Bridge/deploy - rm -f build last.log mem.pprof + rm -rf cmd/Import-Export/deploy + rm -f build last.log mem.pprof main.go rm -rf logo.ico icon.rc icon_windows.syso internal/frontend/qt/icon_windows.syso diff --git a/README.md b/README.md index 2d6f714e..059d018c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# ProtonMail Bridge +# ProtonMail Bridge and Import Export Copyright (c) 2020 Proton Technologies AG This repository holds the ProtonMail Bridge application. @@ -7,7 +7,7 @@ For licensing information see [COPYING](./COPYING.md). For contribution policy see [CONTRIBUTING](./CONTRIBUTING.md). -## Description +## Description Bridge ProtonMail Bridge for e-mail clients. When launched, Bridge will initialize local IMAP/SMTP servers and render @@ -24,6 +24,8 @@ background. More details [on the public website](https://protonmail.com/bridge). +## Description Import-Export +TODO ## Keychain You need to have a keychain in order to run the ProtonMail Bridge. On Mac or @@ -39,7 +41,7 @@ or - `BRIDGESTRICTMODE`: tells bridge to turn on `bbolt`'s "strict mode" which checks the database after every `Commit`. Set to `1` to enable. ### Dev build or run -- `BRIDGE_VERSION`: set the bridge app version used during testing or building +- `APP_VERSION`: set the bridge app version used during testing or building - `PROTONMAIL_ENV`: when set to `dev` it is not using Sentry to report crashes - `VERBOSITY`: set log level used during test time and by the makefile diff --git a/cmd/Desktop-Bridge/main.go b/cmd/Desktop-Bridge/main.go index 7b9050df..1c48218f 100644 --- a/cmd/Desktop-Bridge/main.go +++ b/cmd/Desktop-Bridge/main.go @@ -145,7 +145,7 @@ func (ph *panicHandler) HandlePanic() { } config.HandlePanic(ph.cfg, fmt.Sprintf("Recover: %v", r)) - frontend.HandlePanic() + frontend.HandlePanic("ProtonMail Bridge") *ph.err = cli.NewExitError("Panic and restart", 255) numberOfCrashes++ diff --git a/cmd/Import-Export/main.go b/cmd/Import-Export/main.go index 36f490f3..f605afc7 100644 --- a/cmd/Import-Export/main.go +++ b/cmd/Import-Export/main.go @@ -113,7 +113,7 @@ func (ph *panicHandler) HandlePanic() { } config.HandlePanic(ph.cfg, fmt.Sprintf("Recover: %v", r)) - frontend.HandlePanic() + frontend.HandlePanic("ProtonMail Import-Export") *ph.err = cli.NewExitError("Panic and restart", 255) numberOfCrashes++ diff --git a/go.mod b/go.mod index 167b7846..cb4c80ba 100644 --- a/go.mod +++ b/go.mod @@ -62,6 +62,8 @@ require ( github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/stretchr/testify v1.6.1 github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e + github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200603231648-26cdb75b6f22 // indirect + github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200603231648-26cdb75b6f22 // indirect github.com/twinj/uuid v1.0.0 // indirect github.com/urfave/cli v1.22.4 go.etcd.io/bbolt v1.3.5 diff --git a/go.sum b/go.sum index d58e6f62..79d0f39d 100644 --- a/go.sum +++ b/go.sum @@ -178,6 +178,14 @@ github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e h1:G0DQ/TRQyrEZjtLlLwevFjaRiG8eeCMlq9WXQ2OO2bk= github.com/therecipe/qt v0.0.0-20200701200531-7f61353ee73e/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us= +github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41 h1:yBVcrpbaQYJBdKT2pxTdlL4hBE/eM4UPcyj9YpyvSok= +github.com/therecipe/qt v0.0.0-20200126204426-5074eb6d8c41/go.mod h1:SUUR2j3aE1z6/g76SdD6NwACEpvCxb3fvG82eKbD6us= +github.com/therecipe/qt v0.0.0-20200603231648-26cdb75b6f22 h1:UrNr8EZueA1eREFmG5gVHBeeOuwW2GbzI9VfdB5uK+c= +github.com/therecipe/qt/internal/binding/files/docs v0.0.0-20191019224306-1097424d656c h1:/VhcwU7WuFEVgDHZ9V8PIYAyYqQ6KNxFUjBMOf2aFZM= +github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200603231648-26cdb75b6f22 h1:FumuOkCw78iheUI3eIYhAgtsj/0HQBAib/jXk1cslJw= +github.com/therecipe/qt/internal/binding/files/docs/5.12.0 v0.0.0-20200603231648-26cdb75b6f22/go.mod h1:7m8PDYDEtEVqfjoUQc2UrFqhG0CDmoVJjRlQxexndFc= +github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200603231648-26cdb75b6f22 h1:aYzTBQ/hC6FtbaRnyylxlhbSGMPnyD5lAzVO3Ae6emA= +github.com/therecipe/qt/internal/binding/files/docs/5.13.0 v0.0.0-20200603231648-26cdb75b6f22/go.mod h1:mH55Ek7AZcdns5KPp99O0bg+78el64YCYWHiQKrOdt4= github.com/twinj/uuid v1.0.0 h1:fzz7COZnDrXGTAOHGuUGYd6sG+JMq+AoE7+Jlu0przk= github.com/twinj/uuid v1.0.0/go.mod h1:mMgcE1RHFUFqe5AfiwlINXisXfDGro23fWdPUfOMjRY= github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA= diff --git a/internal/frontend/frontend.go b/internal/frontend/frontend.go index 33f660de..91a39c63 100644 --- a/internal/frontend/frontend.go +++ b/internal/frontend/frontend.go @@ -24,6 +24,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/frontend/cli" cliie "github.com/ProtonMail/proton-bridge/internal/frontend/cli-ie" "github.com/ProtonMail/proton-bridge/internal/frontend/qt" + qtie "github.com/ProtonMail/proton-bridge/internal/frontend/qt-ie" "github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/internal/importexport" "github.com/ProtonMail/proton-bridge/pkg/config" @@ -42,12 +43,12 @@ type Frontend interface { } // HandlePanic handles panics which occur for users with GUI. -func HandlePanic() { +func HandlePanic(appName string) { notify := notificator.New(notificator.Options{ DefaultIcon: "../frontend/ui/icon/icon.png", - AppName: "ProtonMail Bridge", + AppName: appName, }) - _ = notify.Push("Fatal Error", "The ProtonMail Bridge has encountered a fatal error. ", "/frontend/icon/icon.png", notificator.UR_CRITICAL) + _ = notify.Push("Fatal Error", "The "+appName+" has encountered a fatal error. ", "/frontend/icon/icon.png", notificator.UR_CRITICAL) } // New returns initialized frontend based on `frontendType`, which can be `cli` or `qt`. @@ -118,7 +119,6 @@ func newImportExport( case "cli": return cliie.New(panicHandler, config, eventListener, updates, ie) default: - return cliie.New(panicHandler, config, eventListener, updates, ie) - //return qt.New(panicHandler, config, eventListener, updates, ie) + return qtie.New(version, buildVersion, panicHandler, config, eventListener, updates, ie) } } diff --git a/internal/frontend/qml/GuiIE.qml b/internal/frontend/qml/GuiIE.qml new file mode 100644 index 00000000..04c117c4 --- /dev/null +++ b/internal/frontend/qml/GuiIE.qml @@ -0,0 +1,417 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// This is main qml file + +import QtQuick 2.8 +import ImportExportUI 1.0 +import ProtonUI 1.0 + +// All imports from dynamic must be loaded before +import QtQuick.Window 2.2 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 + +Item { + id: gui + property alias winMain: winMain + property bool isFirstWindow: true + property int warningFlags: 0 + + property var locale : Qt.locale("en_US") + property date netBday : new Date("1989-03-13T00:00:00") + property var allYears : getYearList(1970,(new Date()).getFullYear()) + property var allMonths : getMonthList(1,12) + property var allDays : getDayList(1,31) + + property var enums : JSON.parse('{"pathOK":1,"pathEmptyPath":2,"pathWrongPath":4,"pathNotADir":8,"pathWrongPermissions":16,"pathDirEmpty":32,"errUnknownError":0,"errEventAPILogout":1,"errUpdateAPI":2,"errUpdateJSON":3,"errUserAuth":4,"errQApplication":18,"errEmailExportFailed":6,"errEmailExportMissing":7,"errNothingToImport":8,"errEmailImportFailed":12,"errDraftImportFailed":13,"errDraftLabelFailed":14,"errEncryptMessageAttachment":15,"errEncryptMessage":16,"errNoInternetWhileImport":17,"errUnlockUser":5,"errSourceMessageNotSelected":19,"errCannotParseMail":5000,"errWrongLoginOrPassword":5001,"errWrongServerPathOrPort":5002,"errWrongAuthMethod":5003,"errIMAPFetchFailed":5004,"errLocalSourceLoadFailed":1000,"errPMLoadFailed":1001,"errRemoteSourceLoadFailed":1002,"errLoadAccountList":1005,"errExit":1006,"errRetry":1007,"errAsk":1008,"errImportFailed":1009,"errCreateLabelFailed":1010,"errCreateFolderFailed":1011,"errUpdateLabelFailed":1012,"errUpdateFolderFailed":1013,"errFillFolderName":1014,"errSelectFolderColor":1015,"errNoInternet":1016,"folderTypeSystem":"","folderTypeLabel":"label","folderTypeFolder":"folder","folderTypeExternal":"external","progressInit":"init","progressLooping":"looping","statusNoInternet":"noInternet","statusCheckingInternet":"internetCheck","statusNewVersionAvailable":"oldVersion","statusUpToDate":"upToDate","statusForceUpdate":"forceupdate"}') + + IEStyle{} + + MainWindow { + id: winMain + + visible : true + Component.onCompleted: { + winMain.showAndRise() + } + } + + BugReportWindow { + id:bugreportWin + clientVersion.visible: false + onPrefill : { + userAddress.text="" + if (accountsModel.count>0) { + var addressList = accountsModel.get(0).aliases.split(";") + if (addressList.length>0) { + userAddress.text = addressList[0] + } + } + } + } + + // Signals from Go + Connections { + target: go + + + + + + + + + + + + + + + + + onProcessFinished : { + winMain.dialogAddUser.hide() + winMain.dialogGlobal.hide() + } + onOpenManual : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/") + + onNotifyBubble : { + //go.highlightSystray() + winMain.bubleNote.text = message + winMain.bubleNote.place(tabIndex) + winMain.bubleNote.show() + winMain.showAndRise() + } + onBubbleClosed : { + if (winMain.updateState=="uptodate") { + //go.normalSystray() + } + } + + onSetConnectionStatus: { + go.isConnectionOK = isAvailable + if (go.isConnectionOK) { + if( winMain.updateState==gui.enums.statusNoInternet) { + go.setUpdateState(gui.enums.statusUpToDate) + } + } else { + go.setUpdateState(gui.enums.statusNoInternet) + } + } + + onRunCheckVersion : { + go.setUpdateState(gui.enums.statusUpToDate) + winMain.dialogGlobal.state=gui.enums.statusCheckingInternet + winMain.dialogGlobal.show() + go.isNewVersionAvailable(showMessage) + } + + onSetUpdateState : { + // once app is outdated prevent from state change + if (winMain.updateState != gui.enums.statusForceUpdate) { + winMain.updateState = updateState + } + } + + onSetAddAccountWarning : winMain.dialogAddUser.setWarning(message, 0) + + onNotifyVersionIsTheLatest : { + winMain.popupMessage.show( + qsTr("You have the latest version!", "todo") + ) + } + + onNotifyError : { + var name = go.errorDescription.slice(0, go.errorDescription.indexOf("\n")) + var errorMessage = go.errorDescription.slice(go.errorDescription.indexOf("\n")) + switch (errCode) { + case gui.enums.errPMLoadFailed : + winMain.popupMessage.show ( qsTr ( "Loading ProtonMail folders and labels was not successful." , "Error message" ) ) + winMain.dialogExport.hide() + break + case gui.enums.errLocalSourceLoadFailed : + winMain.popupMessage.show(qsTr( + "Loading local folder structure was not successful. "+ + "Folder does not contain valid MBOX or EML file.", + "Error message when can not find correct files in folder." + )) + winMain.dialogImport.hide() + break + case gui.enums.errRemoteSourceLoadFailed : + winMain.popupMessage.show ( qsTr ( "Loading remote source structure was not successful." , "Error message" ) ) + winMain.dialogImport.hide() + break + case gui.enums.errWrongServerPathOrPort : + winMain.popupMessage.show ( qsTr ( "Cannot contact server - incorrect server address and port." , "Error message" ) ) + winMain.dialogImport.decrementCurrentIndex() + break + case gui.enums.errWrongLoginOrPassword : + winMain.popupMessage.show ( qsTr ( "Cannot authenticate - Incorrect email or password." , "Error message" ) ) + winMain.dialogImport.decrementCurrentIndex() + break ; + case gui.enums.errWrongAuthMethod : + winMain.popupMessage.show ( qsTr ( "Cannot authenticate - Please use secured authentication method." , "Error message" ) ) + winMain.dialogImport.decrementCurrentIndex() + break ; + + + case gui.enums.errFillFolderName: + winMain.popupMessage.show(qsTr ( + "Please fill the name.", + "Error message when user did not fill the name of folder or label" + )) + break + case gui.enums.errCreateLabelFailed: + winMain.popupMessage.show(qsTr( + "Cannot create label with name \"%1\"\n%2", + "Error message when it is not possible to create new label, arg1 folder name, arg2 error reason" + ).arg(name).arg(errorMessage)) + break + case gui.enums.errCreateFolderFailed: + winMain.popupMessage.show(qsTr( + "Cannot create folder with name \"%1\"\n%2", + "Error message when it is not possible to create new folder, arg1 folder name, arg2 error reason" + ).arg(name).arg(errorMessage)) + break + + case gui.enums.errNothingToImport: + winMain.popupMessage.show ( qsTr ( "No emails left to import after date range applied. Please, change the date range to continue." , "Error message" ) ) + winMain.dialogImport.decrementCurrentIndex() + break + + case gui.enums.errNoInternetWhileImport: + case gui.enums.errNoInternet: + go.setConnectionStatus(false) + winMain.popupMessage.show ( go.canNotReachAPI ) + break + + case gui.enums.errPMAPIMessageTooLarge: + case gui.enums.errIMAPFetchFailed: + case gui.enums.errEmailImportFailed : + case gui.enums.errDraftImportFailed : + case gui.enums.errDraftLabelFailed : + case gui.enums.errEncryptMessageAttachment: + case gui.enums.errEncryptMessage: + //winMain.dialogImport.ask_retry_skip_cancel(name, errorMessage) + console.log("Import error", errCode, go.errorDescription) + winMain.popupMessage.show(qsTr("Error during import: \n%1\n please see log files for more details.", "message of generic error").arg(go.errorDescription)) + winMain.dialogImport.hide() + break; + + case gui.enums.errUnknownError : default: + console.log("Unknown Error", errCode, go.errorDescription) + winMain.popupMessage.show(qsTr("The program encounter an unknown error \n%1\n please see log files for more details.", "message of generic error").arg(go.errorDescription)) + winMain.dialogExport.hide() + winMain.dialogImport.hide() + winMain.dialogAddUser.hide() + winMain.dialogGlobal.hide() + } + } + + onNotifyUpdate : { + go.setUpdateState("forceUpdate") + if (!winMain.dialogUpdate.visible) { + gui.openMainWindow(true) + go.runCheckVersion(false) + winMain.dialogUpdate.show() + } + } + + onNotifyLogout : { + go.notifyBubble(0, qsTr("Account %1 has been disconnected. Please log in to continue to use the Import-Export with this account.").arg(accname) ) + } + + onNotifyAddressChanged : { + go.notifyBubble(0, qsTr("The address list has been changed for account %1. You may need to reconfigure the settings in your email client.").arg(accname) ) + } + + onNotifyAddressChangedLogout : { + go.notifyBubble(0, qsTr("The address list has been changed for account %1. You have to reconfigure the settings in your email client.").arg(accname) ) + } + + + onNotifyKeychainRebuild : { + go.notifyBubble(1, qsTr( + "Your MacOS keychain is probably corrupted. Please consult the instructions in our FAQ.", + "notification message" + )) + } + + onNotifyHasNoKeychain : { + gui.winMain.dialogGlobal.state="noKeychain" + gui.winMain.dialogGlobal.show() + } + + + onExportStructureLoadFinished: { + if (okay) winMain.dialogExport.okay() + else winMain.dialogExport.cancel() + } + onImportStructuresLoadFinished: { + if (okay) winMain.dialogImport.okay() + else winMain.dialogImport.cancel() + } + + onSimpleErrorHappen: { + if (winMain.dialogImport.visible == true) { + winMain.dialogImport.hide() + } + if (winMain.dialogExport.visible == true) { + winMain.dialogExport.hide() + } + } + } + + function folderIcon(folderName, folderType) { // translations + switch (folderName.toLowerCase()) { + case "inbox" : return Style.fa.inbox + case "sent" : return Style.fa.send + case "spam" : + case "junk" : return Style.fa.ban + case "draft" : return Style.fa.file_o + case "starred" : return Style.fa.star_o + case "trash" : return Style.fa.trash_o + case "archive" : return Style.fa.archive + default: return folderType == gui.enums.folderTypeLabel ? Style.fa.tag : Style.fa.folder_open + } + return Style.fa.sticky_note_o + } + + function folderTypeTitle(folderType) { // translations + if (folderType==gui.enums.folderTypeSystem ) return "" + if (folderType==gui.enums.folderTypeLabel ) return qsTr("Labels" , "todo") + if (folderType==gui.enums.folderTypeFolder ) return qsTr("Folders" , "todo") + return "Undef" + } + + function isFolderEmpty() { + return "true" + } + + function getUnixTime(dateString) { + var d = new Date(dateString) + var n = d.getTime() + if (n != n) return -1 + return n + } + + function getYearList(minY,maxY) { + var years = new Array() + for (var i=0; i<=maxY-minY;i++) { + years[i] = (maxY-i).toString() + } + //console.log("getYearList:", years) + return years + } + + function getMonthList(minM,maxM) { + var months = new Array() + for (var i=0; i<=maxM-minM;i++) { + var iMonth = new Date(1989,(i+minM-1),13) + months[i] = iMonth.toLocaleString(gui.locale, "MMM") + } + //console.log("getMonthList:", months[0], months) + return months + } + + function getDayList(minD,maxD) { + var days = new Array() + for (var i=0; i<=maxD-minD;i++) { + days[i] = gui.prependZeros(i+minD,2) + } + return days + } + + function prependZeros(num,desiredLength) { + var s = num+"" + while (s.length < desiredLength) s="0"+s + return s + } + + function daysInMonth(year,month) { + if (typeof(year) !== 'number') { + year = parseInt(year) + } + if (typeof(month) !== 'number') { + month = Date.fromLocaleDateString( gui.locale, "1970-"+month+"-10", "yyyy-MMM-dd").getMonth()+1 + } + var maxDays = (new Date(year,month,0)).getDate() + if (isNaN(maxDays)) maxDays = 0 + //console.log(" daysInMonth", year, month, maxDays) + return maxDays + } + + function niceDateTime() { + var stamp = new Date() + var nice = getMonthList(stamp.getMonth()+1, stamp.getMonth()+1)[0] + nice += "-" + getDayList(stamp.getDate(), stamp.getDate())[0] + nice += "-" + getYearList(stamp.getFullYear(), stamp.getFullYear())[0] + nice += " " + gui.prependZeros(stamp.getHours(),2) + nice += ":" + gui.prependZeros(stamp.getMinutes(),2) + return nice + } + + /* + // Debug + Connections { + target: structureExternal + + onDataChanged: { + console.log("external data changed") + } + } + + // Debug + Connections { + target: structurePM + + onSelectedLabelsChanged: console.log("PM sel labels:", structurePM.selectedLabels) + onSelectedFoldersChanged: console.log("PM sel folders:", structurePM.selectedFolders) + onDataChanged: { + console.log("PM data changed") + } + } + */ + + Timer { + id: checkVersionTimer + repeat : true + triggeredOnStart: false + interval : Style.main.verCheckRepeatTime + onTriggered : go.runCheckVersion(false) + } + + property string areYouSureYouWantToQuit : qsTr("Tool does not finished all the jobs. Do you really want to quit?") + // On start + Component.onCompleted : { + // set spell messages + go.wrongCredentials = qsTr("Incorrect username or password." , "notification", -1) + go.wrongMailboxPassword = qsTr("Incorrect mailbox password." , "notification", -1) + go.canNotReachAPI = qsTr("Cannot contact server, please check your internet connection." , "notification", -1) + go.versionCheckFailed = qsTr("Version check was unsuccessful. Please try again later." , "notification", -1) + go.credentialsNotRemoved = qsTr("Credentials could not be removed." , "notification", -1) + go.bugNotSent = qsTr("Unable to submit bug report." , "notification", -1) + go.bugReportSent = qsTr("Bug report successfully sent." , "notification", -1) + + go.runCheckVersion(false) + checkVersionTimer.start() + + gui.allMonths = getMonthList(1,12) + gui.allMonthsChanged() + } +} diff --git a/internal/frontend/qml/ImportExportUI/AccountDelegate.qml b/internal/frontend/qml/ImportExportUI/AccountDelegate.qml new file mode 100644 index 00000000..caa6d490 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/AccountDelegate.qml @@ -0,0 +1,432 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +import QtQuick 2.8 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +// NOTE: Keep the Column so the height and width is inherited from content +Column { + id: root + state: status + anchors.left: parent.left + + property real row_width: 50 * Style.px + property int row_height: Style.accounts.heightAccount + property var listalias : aliases.split(";") + property int iAccount: index + + property real spacingLastButtons: (row_width - exportAccount.anchors.leftMargin -Style.main.rightMargin - exportAccount.width - logoutAccount.width - deleteAccount.width)/2 + + Accessible.role: go.goos=="windows" ? Accessible.Grouping : Accessible.Row + Accessible.name: qsTr("Account %1, status %2", "Accessible text describing account row with arguments: account name and status (connected/disconnected), resp.").arg(account).arg(statusMark.text) + Accessible.description: Accessible.name + Accessible.ignored: !enabled || !visible + + // Main row + Rectangle { + id: mainaccRow + anchors.left: parent.left + width : row_width + height : row_height + state: { return isExpanded ? "expanded" : "collapsed" } + color: Style.main.background + + property string actionName : ( + isExpanded ? + qsTr("Collapse row for account %2", "Accessible text of button showing additional configuration of account") : + qsTr("Expand row for account %2", "Accessible text of button hiding additional configuration of account") + ). arg(account) + + + // override by other buttons + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked : { + if (root.state=="connected") { + mainaccRow.toggle_accountSettings() + } + } + cursorShape : root.state == "connected" ? Qt.PointingHandCursor : Qt.ArrowCursor + hoverEnabled: true + onEntered: { + if (mainaccRow.state=="collapsed") { + mainaccRow.color = Qt.lighter(Style.main.background,1.1) + } + } + onExited: { + if (mainaccRow.state=="collapsed") { + mainaccRow.color = Style.main.background + } + } + } + + // toggle down/up icon + Text { + id: toggleIcon + anchors { + left : parent.left + verticalCenter : parent.verticalCenter + leftMargin : Style.main.leftMargin + } + color: Style.main.text + font { + pointSize : Style.accounts.sizeChevron * Style.pt + family : Style.fontawesome.name + } + text: Style.fa.chevron_down + + MouseArea { + anchors.fill: parent + onClicked : { + if (root.state=="connected") { + mainaccRow.toggle_accountSettings() + } + } + cursorShape : root.state == "connected" ? Qt.PointingHandCursor : Qt.ArrowCursor + Accessible.role: Accessible.Button + Accessible.name: mainaccRow.actionName + Accessible.description: mainaccRow.actionName + Accessible.onPressAction : { + if (root.state=="connected") { + mainaccRow.toggle_accountSettings() + } + } + Accessible.ignored: root.state!="connected" || !root.enabled + } + } + + // account name + TextMetrics { + id: accountMetrics + font : accountName.font + elide: Qt.ElideMiddle + elideWidth: ( + statusMark.anchors.leftMargin + - toggleIcon.anchors.leftMargin + ) + text: account + } + Text { + id: accountName + anchors { + verticalCenter : parent.verticalCenter + left : toggleIcon.left + leftMargin : Style.main.leftMargin + } + color: Style.main.text + font { + pointSize : (Style.main.fontSize+2*Style.px) * Style.pt + } + text: accountMetrics.elidedText + } + + // status + ClickIconText { + id: statusMark + anchors { + verticalCenter : parent.verticalCenter + left : parent.left + leftMargin : row_width/2 + } + text : qsTr("connected", "status of a listed logged-in account") + iconText : Style.fa.circle_o + textColor : Style.main.textGreen + enabled : false + Accessible.ignored: true + } + + // export + ClickIconText { + id: exportAccount + anchors { + verticalCenter : parent.verticalCenter + left : parent.left + leftMargin : 5.5*row_width/8 + } + text : qsTr("Export All", "todo") + iconText : Style.fa.floppy_o + textBold : true + textColor : Style.main.textBlue + onClicked: { + dialogExport.currentIndex = 0 + dialogExport.address = account + dialogExport.show() + } + } + + // logout + ClickIconText { + id: logoutAccount + anchors { + verticalCenter : parent.verticalCenter + left : exportAccount.right + leftMargin : root.spacingLastButtons + } + text : qsTr("Log out", "action to log out a connected account") + iconText : Style.fa.power_off + textBold : true + textColor : Style.main.textBlue + } + + // remove + ClickIconText { + id: deleteAccount + anchors { + verticalCenter : parent.verticalCenter + left : logoutAccount.right + leftMargin : root.spacingLastButtons + } + text : qsTr("Remove", "deletes an account from the account settings page") + iconText : Style.fa.trash_o + textColor : Style.main.text + onClicked : { + dialogGlobal.input=iAccount + dialogGlobal.state="deleteUser" + dialogGlobal.show() + } + } + + + // functions + function toggle_accountSettings() { + if (root.state=="connected") { + if (mainaccRow.state=="collapsed" ) { + mainaccRow.state="expanded" + } else { + mainaccRow.state="collapsed" + } + } + } + + states: [ + State { + name: "collapsed" + PropertyChanges { target : toggleIcon ; text : root.state=="connected" ? Style.fa.chevron_down : " " } + PropertyChanges { target : accountName ; font.bold : false } + PropertyChanges { target : mainaccRow ; color : Style.main.background } + PropertyChanges { target : addressList ; visible : false } + }, + State { + name: "expanded" + PropertyChanges { target : toggleIcon ; text : Style.fa.chevron_up } + PropertyChanges { target : accountName ; font.bold : true } + PropertyChanges { target : mainaccRow ; color : Style.accounts.backgroundExpanded } + PropertyChanges { target : addressList ; visible : true } + } + ] + } + + // List of adresses + Column { + id: addressList + anchors.left : parent.left + width: row_width + visible: false + property alias model : repeaterAddresses.model + + Repeater { + id: repeaterAddresses + model: ["one", "two"] + + Rectangle { + id: addressRow + anchors { + left : parent.left + right : parent.right + } + height: Style.accounts.heightAddrRow + color: Style.accounts.backgroundExpanded + + // iconText level down + Text { + id: levelDown + anchors { + left : parent.left + leftMargin : Style.accounts.leftMarginAddr + verticalCenter : wrapAddr.verticalCenter + } + text : Style.fa.level_up + font.family : Style.fontawesome.name + color : Style.main.textDisabled + rotation : 90 + } + + Rectangle { + id: wrapAddr + anchors { + top : parent.top + left : levelDown.right + right : parent.right + leftMargin : Style.main.leftMargin + rightMargin : Style.main.rightMargin + } + height: Style.accounts.heightAddr + border { + width : Style.main.border + color : Style.main.line + } + color: Style.accounts.backgroundAddrRow + + TextMetrics { + id: addressMetrics + font: address.font + elideWidth: ( + wrapAddr.width + - address.anchors.leftMargin + - 2*exportAlias.width + - 3*exportAlias.anchors.rightMargin + ) + elide: Qt.ElideMiddle + text: modelData + } + + Text { + id: address + anchors { + verticalCenter : parent.verticalCenter + left: parent.left + leftMargin: Style.main.leftMargin + } + font.pointSize : Style.main.fontSize * Style.pt + color: Style.main.text + text: addressMetrics.elidedText + } + + // export + ClickIconText { + id: exportAlias + anchors { + verticalCenter: parent.verticalCenter + right: importAlias.left + rightMargin: Style.main.rightMargin + } + text: qsTr("Export", "todo") + iconText: Style.fa.floppy_o + textBold: true + textColor: Style.main.textBlue + onClicked: { + dialogExport.address = listalias[index] + dialogExport.show() + } + } + + // import + ClickIconText { + id: importAlias + anchors { + verticalCenter: parent.verticalCenter + right: parent.right + rightMargin: Style.main.rightMargin + } + text: qsTr("Import", "todo") + iconText: Style.fa.upload + textBold: true + textColor: enabled ? Style.main.textBlue : Style.main.textDisabled + onClicked: { + dialogImport.address = listalias[index] + dialogImport.show() + } + } + } + } + } + } + + // line + Rectangle { + id: line + color: Style.accounts.line + height: Style.accounts.heightLine + width: root.row_width + } + + + states: [ + State { + name: "connected" + PropertyChanges { + target : addressList + model : listalias + } + PropertyChanges { + target : toggleIcon + color : Style.main.text + } + PropertyChanges { + target : accountName + color : Style.main.text + } + PropertyChanges { + target : statusMark + textColor : Style.main.textGreen + text : qsTr("connected", "status of a listed logged-in account") + iconText : Style.fa.circle + } + PropertyChanges { + target: exportAccount + visible: true + } + PropertyChanges { + target : logoutAccount + text : qsTr("Log out", "action to log out a connected account") + onClicked : { + mainaccRow.state="collapsed" + dialogGlobal.state = "logout" + dialogGlobal.input = root.iAccount + dialogGlobal.show() + dialogGlobal.confirmed() + } + } + }, + State { + name: "disconnected" + PropertyChanges { + target : addressList + model : 0 + } + PropertyChanges { + target : toggleIcon + color : Style.main.textDisabled + } + PropertyChanges { + target : accountName + color : Style.main.textDisabled + } + PropertyChanges { + target : statusMark + textColor : Style.main.textDisabled + text : qsTr("disconnected", "status of a listed logged-out account") + iconText : Style.fa.circle_o + } + PropertyChanges { + target : logoutAccount + text : qsTr("Log in", "action to log in a disconnected account") + onClicked : { + dialogAddUser.username = root.listalias[0] + dialogAddUser.show() + dialogAddUser.inputPassword.focusInput = true + } + } + PropertyChanges { + target: exportAccount + visible: false + } + } + ] +} diff --git a/internal/frontend/qml/ImportExportUI/Credits.qml b/internal/frontend/qml/ImportExportUI/Credits.qml new file mode 100644 index 00000000..ef85265a --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/Credits.qml @@ -0,0 +1,92 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// credits + +import QtQuick 2.8 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Item { + id: root + Rectangle { + anchors.centerIn: parent + width: Style.main.width + height: root.parent.height - 6*Style.dialog.titleSize + color: "transparent" + + ListView { + anchors.fill: parent + clip: true + + model: [ + "github.com/0xAX/notificator" , + "github.com/abiosoft/ishell" , + "github.com/allan-simon/go-singleinstance" , + "github.com/andybalholm/cascadia" , + "github.com/bgentry/speakeasy" , + "github.com/boltdb/bolt" , + "github.com/docker/docker-credential-helpers" , + "github.com/emersion/go-imap" , + "github.com/emersion/go-imap-appendlimit" , + "github.com/emersion/go-imap-idle" , + "github.com/emersion/go-imap-move" , + "github.com/emersion/go-imap-quota" , + "github.com/emersion/go-imap-specialuse" , + "github.com/emersion/go-smtp" , + "github.com/emersion/go-textwrapper" , + "github.com/fsnotify/fsnotify" , + "github.com/jaytaylor/html2text" , + "github.com/jhillyerd/go.enmime" , + "github.com/k0kubun/pp" , + "github.com/kardianos/osext" , + "github.com/keybase/go-keychain" , + "github.com/mattn/go-colorable" , + "github.com/pkg/browser" , + "github.com/shibukawa/localsocket" , + "github.com/shibukawa/tobubus" , + "github.com/shirou/gopsutil" , + "github.com/sirupsen/logrus" , + "github.com/skratchdot/open-golang/open" , + "github.com/therecipe/qt" , + "github.com/thomasf/systray" , + "github.com/ugorji/go/codec" , + "github.com/urfave/cli" , + "" , + "Font Awesome 4.7.0", + "" , + "The Qt Company - Qt 5.9.1 LGPLv3" , + "" , + ] + + delegate: Text { + anchors.horizontalCenter: parent.horizontalCenter + text: modelData + color: Style.main.text + } + + footer: ButtonRounded { + anchors.horizontalCenter: parent.horizontalCenter + text: "Close" + onClicked: { + root.parent.hide() + } + } + } + } +} + diff --git a/internal/frontend/qml/ImportExportUI/DateBox.qml b/internal/frontend/qml/ImportExportUI/DateBox.qml new file mode 100644 index 00000000..b7b564d8 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DateBox.qml @@ -0,0 +1,220 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for year / month / day +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +ComboBox { + id: root + + property string placeholderText : "none" + property var dropDownStyle : Style.dropDownLight + property real radius : Style.dialog.radiusButton + property bool below : true + + onDownChanged : { + root.below = popup.y>0 + } + + + font.pointSize : Style.main.fontSize * Style.pt + + spacing : Style.dialog.spacing + height : Style.dialog.heightInput + width : 10*Style.px + + function updateWidth() { + // make the width according to localization ( especially for Months) + var max = 10*Style.px + if (root.model === undefined) return + for (var i=-1; i=0 ? root.displayText : placeholderText + font : root.font + color : root.enabled ? dropDownStyle.text : dropDownStyle.inactive + verticalAlignment : Text.AlignVCenter + elide : Text.ElideRight + } + + background: Rectangle { + color: Style.transparent + + MouseArea { + anchors.fill: parent + onClicked: root.down ? root.popup.close() : root.popup.open() + } + } + + + DelegateModel { // FIXME QML DelegateModel: Error creating delegate + id: filteredData + model: root.model + filterOnGroup: "filtered" + groups: DelegateModelGroup { + id: filtered + name: "filtered" + includeByDefault: true + } + delegate: root.delegate + } + + function filterItems(minIndex,maxIndex) { + // filter + var rowCount = filteredData.items.count + if (rowCount<=0) return + //console.log(" filter", root.placeholderText, rowCount, minIndex, maxIndex) + for (var iItem = 0; iItem < rowCount; iItem++) { + var entry = filteredData.items.get(iItem); + entry.inFiltered = ( iItem >= minIndex && iItem <= maxIndex ) + //console.log(" inserted ", iItem, rowCount, entry.model.modelData, entry.inFiltered ) + } + } + + delegate: ItemDelegate { + id: thisItem + width : view.width + height : Style.dialog.heightInput + leftPadding : root.spacing + rightPadding : root.spacing + topPadding : 0 + bottomPadding : 0 + + property int index : { + //console.log( "index: ", thisItem.DelegateModel.itemsIndex ) + return thisItem.DelegateModel.itemsIndex + } + + onClicked : { + //console.log("thisItem click", thisItem.index) + root.currentIndex = thisItem.index + root.activated(thisItem.index) + root.popup.close() + } + + + contentItem: Text { + text: modelData + color: dropDownStyle.text + font: root.font + elide: Text.ElideRight + verticalAlignment: Text.AlignVCenter + } + + background: Rectangle { + color: thisItem.hovered ? dropDownStyle.highlight : dropDownStyle.background + Text { + anchors{ + right: parent.right + rightMargin: root.spacing + verticalCenter: parent.verticalCenter + } + font { + family: Style.fontawesome.name + } + text: root.currentIndex == thisItem.index ? Style.fa.check : "" + color: thisItem.hovered ? dropDownStyle.text : dropDownStyle.highlight + } + + Rectangle { + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + height: Style.dialog.borderInput + color: dropDownStyle.separator + } + } + } + + popup: Popup { + y: root.height + x: -background.strokeWidth + width: root.width + 2*background.strokeWidth + modal: true + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + topPadding: background.radiusTopLeft + 2*background.strokeWidth + bottomPadding: background.radiusBottomLeft + 2*background.strokeWidth + leftPadding: 2*background.strokeWidth + rightPadding: 2*background.strokeWidth + + contentItem: ListView { + id: view + clip: true + implicitHeight: winMain.height/3 + model: filteredData // if you want to slide down to position: popup.visible ? root.delegateModel : null + currentIndex: root.currentIndex + + ScrollIndicator.vertical: ScrollIndicator { } + } + + background: RoundedRectangle { + radiusTopLeft : root.below ? 0 : root.radius + radiusBottomLeft : !root.below ? 0 : root.radius + radiusTopRight : radiusTopLeft + radiusBottomRight : radiusBottomLeft + fillColor : dropDownStyle.background + } + } + + Component.onCompleted: { + //console.log(" box ", label) + root.updateWidth() + root.filterItems(0,model.length-1) + } + + onModelChanged :{ + //console.log("model changed", root.placeholderText) + root.updateWidth() + root.filterItems(0,model.length-1) + } +} + diff --git a/internal/frontend/qml/ImportExportUI/DateInput.qml b/internal/frontend/qml/ImportExportUI/DateInput.qml new file mode 100644 index 00000000..b9b78a9b --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DateInput.qml @@ -0,0 +1,243 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for date +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + + width : row.width + (root.label == "" ? 0 : textlabel.width) + height : row.height + color : Style.transparent + + property alias label : textlabel.text + property string metricsLabel : root.label + property var dropDownStyle : Style.dropDownLight + + // dates + property date currentDate : new Date() // default now + property date minDate : new Date(0) // default epoch start + property date maxDate : new Date() // default now + property int unix : Math.floor(currentDate.getTime()/1000) + + onMinDateChanged: { + if (isNaN(minDate.getTime()) || minDate.getTime() > maxDate.getTime()) { + minDate = new Date(0) + } + //console.log(" minDate changed:", root.label, minDate.toDateString()) + updateRange() + } + onMaxDateChanged: { + if (isNaN(maxDate.getTime()) || minDate.getTime() > maxDate.getTime()) { + maxDate = new Date() + } + //console.log(" maxDate changed:", root.label, maxDate.toDateString()) + updateRange() + } + + RoundedRectangle { + id: background + anchors.fill : row + strokeColor : dropDownStyle.line + strokeWidth : Style.dialog.borderInput + fillColor : dropDownStyle.background + radiusTopLeft : row.children[0].down && !row.children[0].below ? 0 : Style.dialog.radiusButton + radiusBottomLeft : row.children[0].down && row.children[0].below ? 0 : Style.dialog.radiusButton + radiusTopRight : row.children[row.children.length-1].down && !row.children[row.children.length-1].below ? 0 : Style.dialog.radiusButton + radiusBottomRight : row.children[row.children.length-1].down && row.children[row.children.length-1].below ? 0 : Style.dialog.radiusButton + } + + TextMetrics { + id: textMetrics + text: root.metricsLabel+"M" + font: textlabel.font + } + + Text { + id: textlabel + anchors { + left : root.left + verticalCenter : root.verticalCenter + } + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: dropDownStyle.labelBold + } + color: dropDownStyle.text + width: textMetrics.width + verticalAlignment: Text.AlignVCenter + } + + Row { + id: row + + anchors { + left : root.label=="" ? root.left : textlabel.right + bottom : root.bottom + } + padding : Style.dialog.borderInput + + DateBox { + id: monthInput + placeholderText: qsTr("Month") + enabled: !allDates + model: gui.allMonths + onActivated: updateRange() + anchors.verticalCenter: parent.verticalCenter + dropDownStyle: root.dropDownStyle + } + + Rectangle { + width: Style.dialog.borderInput + height: monthInput.height + color: dropDownStyle.line + anchors.verticalCenter: parent.verticalCenter + } + + DateBox { + id: dayInput + placeholderText: qsTr("Day") + enabled: !allDates + model: gui.allDays + onActivated: updateRange() + anchors.verticalCenter: parent.verticalCenter + dropDownStyle: root.dropDownStyle + } + + Rectangle { + width: Style.dialog.borderInput + height: monthInput.height + color: dropDownStyle.line + } + + DateBox { + id: yearInput + placeholderText: qsTr("Year") + enabled: !allDates + model: gui.allYears + onActivated: updateRange() + anchors.verticalCenter: parent.verticalCenter + dropDownStyle: root.dropDownStyle + } + } + + + function setDate(d) { + //console.trace() + //console.log( " setDate ", label, d) + if (isNaN(d = parseInt(d))) return + var newUnix = Math.min(maxDate.getTime(), d*1000) // seconds to ms + newUnix = Math.max(minDate.getTime(), newUnix) + root.updateRange(new Date(newUnix)) + //console.log( " set ", currentDate.getTime()) + } + + + function updateRange(curr) { + if (curr === undefined || isNaN(curr.getTime())) curr = root.getCurrentDate() + //console.log( " update", label, curr, curr.getTime()) + //console.trace() + if (isNaN(curr.getTime())) return // shouldn't happen + // full system date range + var firstYear = parseInt(gui.allYears[0]) + var firstDay = parseInt(gui.allDays[0]) + if ( isNaN(firstYear) || isNaN(firstDay) ) return + // get minimal and maximal available year, month, day + // NOTE: The order is important!!! + var minYear = minDate.getFullYear() + var maxYear = maxDate.getFullYear() + var minMonth = (curr.getFullYear() == minYear ? minDate.getMonth() : 0 ) + var maxMonth = (curr.getFullYear() == maxYear ? maxDate.getMonth() : 11 ) + var minDay = ( + curr.getFullYear() == minYear && + curr.getMonth() == minMonth ? + minDate.getDate() : firstDay + ) + var maxDay = ( + curr.getFullYear() == maxYear && + curr.getMonth() == maxMonth ? + maxDate.getDate() : gui.daysInMonth(curr.getFullYear(), curr.getMonth()+1) + ) + + //console.log("update ranges: ", root.label, minYear, maxYear, minMonth+1, maxMonth+1, minDay, maxDay) + //console.log("update indexes: ", root.label, firstYear-minYear, firstYear-maxYear, minMonth, maxMonth, minDay-firstDay, maxDay-firstDay) + + + yearInput.filterItems(firstYear-maxYear, firstYear-minYear) + monthInput.filterItems(minMonth,maxMonth) // getMonth() is index not a month (i.e. Jan==0) + dayInput.filterItems(minDay-1,maxDay-1) + + // keep ordering from model not from filter + yearInput .currentIndex = firstYear - curr.getFullYear() + monthInput .currentIndex = curr.getMonth() // getMonth() is index not a month (i.e. Jan==0) + dayInput .currentIndex = curr.getDate()-firstDay + + /* + console.log( + "update current indexes: ", root.label, + curr.getFullYear() , '->' , yearInput.currentIndex , + gui.allMonths[curr.getMonth()] , '->' , monthInput.currentIndex , + curr.getDate() , '->' , dayInput.currentIndex + ) + */ + + // test if current date changed + if ( + yearInput.currentText == root.currentDate.getFullYear() && + monthInput.currentText == root.currentDate.toLocaleString(gui.locale, "MMM") && + dayInput.currentText == gui.prependZeros(root.currentDate.getDate(),2) + ) { + //console.log(" currentDate NOT changed", label, root.currentDate.toDateString()) + return + } + + root.currentDate = root.getCurrentDate() + // console.log(" currentDate changed", label, root.currentDate.toDateString()) + } + + // get current date from selected + function getCurrentDate() { + if (isNaN(root.currentDate.getTime())) { // wrong current ? + console.log("!WARNING! Wrong current date format", root.currentDate) + root.currentDate = new Date(0) + } + var currentString = "" + var currentUnix = root.currentDate.getTime() + if ( + yearInput.currentText != "" && + yearInput.currentText != yearInput.placeholderText && + monthInput.currentText != "" && + monthInput.currentText != monthInput.placeholderText + ) { + var day = gui.daysInMonth(yearInput.currentText, monthInput.currentText) + if (!isNaN(parseInt(dayInput.currentText))) { + day = Math.min(day, parseInt(dayInput.currentText)) + } + currentString = [ yearInput.currentText, monthInput.currentText, day].join("-") + currentUnix = Date.fromLocaleDateString( locale, currentString, "yyyy-MMM-d").getTime() + } + return new Date(Math.max( + minDate.getTime(), + Math.min(maxDate.getTime(), currentUnix) + )) + } +} + diff --git a/internal/frontend/qml/ImportExportUI/DateRange.qml b/internal/frontend/qml/ImportExportUI/DateRange.qml new file mode 100644 index 00000000..26ebc016 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DateRange.qml @@ -0,0 +1,121 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for date range +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + +Column { + id: dateRange + + property var structure : structureExternal + property string sourceID : structureExternal.getID ( -1 ) + + property alias allDates : allDatesBox.checked + property alias inputDateFrom : inputDateFrom + property alias inputDateTo : inputDateTo + + function setRange() {common.setRange()} + function applyRange() {common.applyRange()} + + property var dropDownStyle : Style.dropDownLight + property var isDark : dropDownStyle.background == Style.dropDownDark.background + + spacing: Style.dialog.spacing + + DateRangeFunctions {id:common} + + DateInput { + id: inputDateFrom + label: qsTr("From:") + currentDate: gui.netBday + maxDate: inputDateTo.currentDate + dropDownStyle: dateRange.dropDownStyle + } + + Rectangle { + width: inputDateTo.width + height: Style.dialog.borderInput / 2 + color: isDark ? dropDownStyle.separator : Style.transparent + } + + DateInput { + id: inputDateTo + label: qsTr("To:") + metricsLabel: inputDateFrom.label + currentDate: new Date() // now + minDate: inputDateFrom.currentDate + dropDownStyle: dateRange.dropDownStyle + } + + Rectangle { + width: inputDateTo.width + height: Style.dialog.borderInput + color: isDark ? dropDownStyle.separator : Style.transparent + } + + CheckBoxLabel { + id: allDatesBox + text : qsTr("All dates") + anchors.right : inputDateTo.right + checkedSymbol : Style.fa.toggle_on + uncheckedSymbol : Style.fa.toggle_off + uncheckedColor : Style.main.textDisabled + textColor : dropDownStyle.text + symbolPointSize : Style.dialog.iconSize * Style.pt * 1.1 + spacing : Style.dialog.spacing*2 + + TextMetrics { + id: metrics + text: allDatesBox.checkedSymbol + font { + family: Style.fontawesome.name + pointSize: allDatesBox.symbolPointSize + } + } + + Rectangle { + color: allDatesBox.checked ? dotBackground.color : Style.exporting.sliderBackground + width: metrics.width*0.9 + height: metrics.height*0.6 + radius: height/2 + z: -1 + + anchors { + left: allDatesBox.left + verticalCenter: allDatesBox.verticalCenter + leftMargin: 0.05 * metrics.width + } + + Rectangle { + id: dotBackground + color : Style.exporting.background + height : parent.height + width : height + radius : height/2 + anchors { + left : parent.left + verticalCenter : parent.verticalCenter + } + + } + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/DateRangeFunctions.qml b/internal/frontend/qml/ImportExportUI/DateRangeFunctions.qml new file mode 100644 index 00000000..8dceb497 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DateRangeFunctions.qml @@ -0,0 +1,81 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for date range +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Item { + id: root + /* + NOTE: need to be in obejct with + id: dateRange + + property var structure : structureExternal + property string sourceID : structureExternal.getID ( -1 ) + + property alias allDates : allDatesBox.checked + property alias inputDateFrom : inputDateFrom + property alias inputDateTo : inputDateTo + + function setRange() {common.setRange()} + function applyRange() {common.applyRange()} + */ + + function resetRange() { + inputDateFrom.setDate(gui.netBday.getTime()) + inputDateTo.setDate((new Date()).getTime()) + } + + function setRange(){ // unix time in seconds + var folderFrom = dateRange.structure.getFrom(dateRange.sourceID) + if (folderFrom===undefined) folderFrom = 0 + var folderTo = dateRange.structure.getTo(dateRange.sourceID) + if (folderTo===undefined) folderTo = 0 + if ( folderFrom == 0 && folderTo ==0 ) { + dateRange.allDates = true + } else { + dateRange.allDates = false + inputDateFrom.setDate(folderFrom) + inputDateTo.setDate(folderTo) + } + } + + function applyRange(){ // unix time is seconds + if (dateRange.allDates) structure.setFromToDate(dateRange.sourceID, 0, 0) + else { + var endOfDay = new Date(inputDateTo.unix*1000) + endOfDay.setHours(23,59,59,999) + var endOfDayUnix = parseInt(endOfDay.getTime()/1000) + structure.setFromToDate(dateRange.sourceID, inputDateFrom.unix, endOfDayUnix) + } + } + + Connections { + target: dateRange + onStructureChanged: setRange() + } + + Component.onCompleted: { + inputDateFrom.updateRange(gui.netBday) + inputDateTo.updateRange(new Date()) + setRange() + } +} + diff --git a/internal/frontend/qml/ImportExportUI/DateRangeMenu.qml b/internal/frontend/qml/ImportExportUI/DateRangeMenu.qml new file mode 100644 index 00000000..4ef6853b --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DateRangeMenu.qml @@ -0,0 +1,151 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of import folder and their target +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + +Rectangle { + id:root + + width : icon.width + indicator.width + 3*padding + height : icon.height + 3*padding + + property real padding : Style.dialog.spacing + property bool down : popup.visible + + property var structure : structureExternal + property string sourceID : structureExternal.getID(-1) + + color: Style.transparent + + RoundedRectangle { + anchors.fill: parent + radiusTopLeft: root.down ? 0 : Style.dialog.radiusButton + fillColor: root.down ? Style.main.textBlue : Style.transparent + } + + Text { + id: icon + text: Style.fa.calendar_o + anchors { + left : parent.left + leftMargin : root.padding + verticalCenter : parent.verticalCenter + } + + color: root.enabled ? ( + root.down ? Style.main.background : Style.main.text + ) : Style.main.textDisabled + + font.family : Style.fontawesome.name + + Text { + anchors { + verticalCenter: parent.bottom + horizontalCenter: parent.right + } + + color : !root.down && root.enabled ? Style.main.textRed : icon.color + text : Style.fa.exclamation_circle + visible : !dateRangeInput.allDates + font.pointSize : root.padding * Style.pt * 1.5 + font.family : Style.fontawesome.name + } + } + + + Text { + id: indicator + anchors { + right : parent.right + rightMargin : root.padding + verticalCenter : parent.verticalCenter + } + + text : root.down ? Style.fa.chevron_up : Style.fa.chevron_down + color : !root.down && root.enabled ? Style.main.textBlue : icon.color + font.family : Style.fontawesome.name + } + + MouseArea { + anchors.fill: root + onClicked: { + popup.open() + } + } + + Popup { + id: popup + + x : -width + modal : true + clip : true + + topPadding : 0 + + background: RoundedRectangle { + fillColor : Style.bubble.paneBackground + strokeColor : fillColor + radiusTopRight: 0 + + RoundedRectangle { + anchors { + left: parent.left + right: parent.right + top: parent.top + } + height: Style.dialog.heightInput + fillColor: Style.dropDownDark.highlight + strokeColor: fillColor + radiusTopRight: 0 + radiusBottomLeft: 0 + radiusBottomRight: 0 + } + } + + contentItem : Column { + spacing: Style.dialog.spacing + + Text { + anchors { + left: parent.left + } + + text : qsTr("Import date range") + font.bold : Style.dropDownDark.labelBold + color : Style.dropDownDark.text + height : Style.dialog.heightInput + verticalAlignment : Text.AlignVCenter + } + + DateRange { + id: dateRangeInput + allDates: true + structure: root.structure + sourceID: root.sourceID + dropDownStyle: Style.dropDownDark + } + } + + onAboutToShow : dateRangeInput.setRange() + onAboutToHide : dateRangeInput.applyRange() + } +} diff --git a/internal/frontend/qml/ImportExportUI/DialogExport.qml b/internal/frontend/qml/ImportExportUI/DialogExport.qml new file mode 100644 index 00000000..2d44fb74 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DialogExport.qml @@ -0,0 +1,457 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Export dialog +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +// TODO +// - make ErrorDialog module +// - map decision to error code : ask (default), skip () +// - what happens when import fails ? heuristic to find mail where to start from + +Dialog { + id: root + + enum Page { + LoadingStructure = 0, Options, Progress + } + + title : set_title() + + property string address + property alias finish: finish + + property string msgClearUnfished: qsTr ("Remove already exported files.") + + isDialogBusy : true // currentIndex == 0 || currentIndex == 3 + + signal cancel() + signal okay() + + + Rectangle { // 0 + id: dialogLoading + width: root.width + height: root.height + color: Style.transparent + + Text { + anchors.centerIn : dialogLoading + font.pointSize: Style.dialog.titleSize * Style.pt + color: Style.dialog.text + horizontalAlignment: Text.AlignHCenter + text: qsTr("Loading folders and labels for", "todo") +"\n" + address + } + } + + Rectangle { // 1 + id: dialogInput + width: root.width + height: root.height + color: Style.transparent + + Row { + id: inputRow + anchors { + topMargin : root.titleHeight + top : parent.top + horizontalCenter : parent.horizontalCenter + } + spacing: 3*Style.main.leftMargin + property real columnWidth : (root.width - Style.main.leftMargin - inputRow.spacing - Style.main.rightMargin) / 2 + property real columnHeight : root.height - root.titleHeight - Style.main.leftMargin + + + ExportStructure { + id: sourceFoldersInput + width : inputRow.columnWidth + height : inputRow.columnHeight + title : qsTr("From: %1", "todo").arg(address) + } + + Column { + spacing: (inputRow.columnHeight - dateRangeInput.height - outputFormatInput.height - outputPathInput.height - buttonRow.height - infotipEncrypted.height) / 4 + + DateRange{ + id: dateRangeInput + structure: structurePM + sourceID: structurePM.getID(-1) + } + + OutputFormat { + id: outputFormatInput + } + + Row { + spacing: Style.dialog.spacing + CheckBoxLabel { + id: exportEncrypted + text: qsTr("Export emails that cannot be decrypted as ciphertext") + anchors { + bottom: parent.bottom + bottomMargin: Style.dialog.fontSize/1.8 + } + } + + InfoToolTip { + id: infotipEncrypted + anchors { + verticalCenter: exportEncrypted.verticalCenter + } + info: qsTr("Checking this option will export all emails that cannot be decrypted in ciphertext. If this option is not checked, these emails will not be exported", "todo") + } + } + + FileAndFolderSelect { + id: outputPathInput + title: qsTr("Select location of export:", "todo") + width : inputRow.columnWidth // stretch folder input + } + + Row { + id: buttonRow + anchors.right : parent.right + spacing : Style.dialog.rightMargin + + ButtonRounded { + id:buttonCancel + fa_icon: Style.fa.times + text: qsTr("Cancel") + color_main: Style.main.textBlue + onClicked : root.cancel() + } + + ButtonRounded { + id: buttonNext + fa_icon: Style.fa.check + text: qsTr("Export","todo") + enabled: structurePM != 0 + color_main: Style.dialog.background + color_minor: enabled ? Style.dialog.textBlue : Style.main.textDisabled + isOpaque: true + onClicked : root.okay() + } + } + } + } + } + + Rectangle { // 2 + id: progressStatus + width: root.width + height: root.height + color: "transparent" + + Row { + anchors { + bottom: progressbarExport.top + bottomMargin: Style.dialog.heightSeparator + left: progressbarExport.left + } + spacing: Style.main.rightMargin + AccessibleText { + id: statusLabel + text : qsTr("Exporting to:") + font.pointSize: Style.main.iconSize * Style.pt + color : Style.main.text + } + AccessibleText { + anchors.baseline: statusLabel.baseline + text : go.progressDescription == gui.enums.progressInit ? outputPathInput.path : go.progressDescription + elide: Text.ElideMiddle + width: progressbarExport.width - parent.spacing - statusLabel.width + font.pointSize: Style.dialog.textSize * Style.pt + color : Style.main.textDisabled + } + } + + ProgressBar { + id: progressbarExport + implicitWidth : 2*progressStatus.width/3 + implicitHeight : Style.exporting.rowHeight + value: go.progress + property int current: go.total * go.progress + property bool isFinished: finishedPartBar.width == progressbarExport.width + anchors { + centerIn: parent + } + background: Rectangle { + radius : Style.exporting.boxRadius + color : Style.exporting.progressBackground + } + contentItem: Item { + Rectangle { + id: finishedPartBar + width : parent.width * progressbarExport.visualPosition + height : parent.height + radius : Style.exporting.boxRadius + gradient : Gradient { + GradientStop { position: 0.00; color: Qt.lighter(Style.exporting.progressStatus,1.1) } + GradientStop { position: 0.66; color: Style.exporting.progressStatus } + GradientStop { position: 1.00; color: Qt.darker(Style.exporting.progressStatus,1.1) } + } + + Behavior on width { + NumberAnimation { duration:800; easing.type: Easing.InOutQuad } + } + } + Text { + anchors.centerIn: parent + text: { + if (progressbarExport.isFinished) return qsTr("Export finished","todo") + if ( + go.progressDescription == gui.enums.progressInit || + (go.progress==0 && go.description=="") + ) { + if (go.total>1) return qsTr("Estimating the total number of messages (%1)","todo").arg(go.total) + else return qsTr("Estimating the total number of messages","todo") + } + var msg = qsTr("Exporting message %1 of %2 (%3%)","todo") + if (pauseButton.paused) msg = qsTr("Exporting paused at message %1 of %2 (%3%)","todo") + return msg.arg(progressbarExport.current).arg(go.total).arg(Math.floor(go.progress*100)) + } + color: Style.main.background + font { + pointSize: Style.dialog.fontSize * Style.pt + } + } + } + } + + Row { + anchors { + top: progressbarExport.bottom + topMargin : Style.dialog.heightSeparator + horizontalCenter: parent.horizontalCenter + } + spacing: Style.dialog.rightMargin + + ButtonRounded { + id: pauseButton + property bool paused : false + fa_icon : paused ? Style.fa.play : Style.fa.pause + text : paused ? qsTr("Resume") : qsTr("Pause") + color_main : Style.dialog.textBlue + onClicked : { + if (paused) { + if (winMain.updateState == gui.enums.statusNoInternet) { + go.notifyError(gui.enums.errNoInternet) + return + } + go.resumeProcess() + } else { + go.pauseProcess() + } + paused = !paused + pauseButton.focus=false + } + visible : !progressbarExport.isFinished + } + + ButtonRounded { + fa_icon : Style.fa.times + text : qsTr("Cancel") + color_main : Style.dialog.textBlue + visible : !progressbarExport.isFinished + onClicked : root.ask_cancel_progress() + } + + ButtonRounded { + id: finish + fa_icon : Style.fa.check + text : qsTr("Okay","todo") + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + visible : progressbarExport.isFinished + onClicked : root.okay() + } + } + + ClickIconText { + id: buttonHelp + anchors { + right : parent.right + bottom : parent.bottom + rightMargin : Style.main.rightMargin + bottomMargin : Style.main.rightMargin + } + textColor : Style.main.textDisabled + iconText : Style.fa.question_circle + text : qsTr("Help", "directs the user to the online user guide") + textBold : true + onClicked : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/") + } + } + + PopupMessage { + id: errorPopup + width: root.width + height: root.height + } + + function check_inputs() { + if (currentIndex == 1) { + // at least one email to export + if (structurePM.rowCount() == 0){ + errorPopup.show(qsTr("No emails found to export. Please try another address.", "todo")) + return false + } + // at least one source selected + if (!structurePM.atLeastOneSelected) { + errorPopup.show(qsTr("Please select at least one item to export.", "todo")) + return false + } + // check path + var folderCheck = go.checkPathStatus(outputPathInput.path) + switch (folderCheck) { + case gui.enums.pathEmptyPath: + errorPopup.show(qsTr("Missing export path. Please select an output folder.")) + break; + case gui.enums.pathWrongPath: + errorPopup.show(qsTr("Folder '%1' not found. Please select an output folder.").arg(outputPathInput.path)) + break; + case gui.enums.pathOK | gui.enums.pathNotADir: + errorPopup.show(qsTr("File '%1' is not a folder. Please select an output folder.").arg(outputPathInput.path)) + break; + case gui.enums.pathWrongPermissions: + errorPopup.show(qsTr("Cannot access folder '%1'. Please check folder permissions.").arg(outputPathInput.path)) + break; + } + if ( + (folderCheck&gui.enums.pathOK)==0 || + (folderCheck&gui.enums.pathNotADir)==gui.enums.pathNotADir + ) return false + if (winMain.updateState == gui.enums.statusNoInternet) { + errorPopup.show(qsTr("Please check your internet connection.")) + return false + } + } + return true + } + + function set_title() { + switch(root.currentIndex){ + case 1 : return qsTr("Select what you'd like to export:") + default: return "" + } + } + + function clear_status() { + go.progress=0.0 + go.total=0.0 + go.progressDescription=gui.enums.progressInit + } + + function ask_cancel_progress(){ + errorPopup.buttonYes.visible = true + errorPopup.buttonNo.visible = true + errorPopup.buttonOkay.visible = false + errorPopup.checkbox.text = root.msgClearUnfished + errorPopup.show ("Are you sure you want to cancel this export?") + } + + + onCancel : { + switch (root.currentIndex) { + case 0 : + case 1 : root.hide(); break; + case 2 : // progress bar + go.cancelProcess ( + errorPopup.checkbox.text == root.msgClearUnfished && + errorPopup.checkbox.checked + ); + // no break + default: + root.clear_status() + root.currentIndex=1 + } + } + + onOkay : { + var isOK = check_inputs() + if (!isOK) return + timer.interval= currentIndex==1 ? 1 : 300 + switch (root.currentIndex) { + case 2: // progress + root.clear_status() + root.hide() + break + case 0: // loading structure + dateRangeInput.setRange() + //no break + default: + incrementCurrentIndex() + timer.start() + } + } + + onShow: { + if (winMain.updateState==gui.enums.statusNoInternet) { + go.checkInternet() + if (winMain.updateState==gui.enums.statusNoInternet) { + go.notifyError(gui.enums.errNoInternet) + root.hide() + return + } + } + + root.clear_status() + root.currentIndex=0 + timer.interval = 300 + timer.start() + dateRangeInput.allDates = true + } + + Connections { + target: timer + onTriggered : { + switch (currentIndex) { + case 0: + go.loadStructureForExport(root.address) + sourceFoldersInput.hasItems = (structurePM.rowCount() > 0) + break + case 2: + dateRangeInput.applyRange() + go.startExport( + outputPathInput.path, + root.address, + outputFormatInput.checkedText, + exportEncrypted.checked + ) + break + } + } + } + + Connections { + target: errorPopup + + onClickedOkay : errorPopup.hide() + onClickedYes : { + root.cancel() + errorPopup.hide() + } + onClickedNo : { + go.resumeProcess() + errorPopup.hide() + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/DialogImport.qml b/internal/frontend/qml/ImportExportUI/DialogImport.qml new file mode 100644 index 00000000..380e3fd4 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DialogImport.qml @@ -0,0 +1,1027 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Export dialog +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Dialogs 1.0 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Dialog { + id: root + + enum Page { + SelectSourceType=0, ImapSource, LoadingStructure, SourceToTarget, Progress, Report + } + + title: "" // qsTr("Importing from: %1", "todo").arg(address) + + isDialogBusy: currentIndex==3 || currentIndex==4 + + property string address + property string inputPath : "" + property bool isFromFile : inputEmail.text == "" && root.inputPath != "" + property bool isFromIMAP : inputEmail.text != "" + property bool paused : false + + property string msgDontShowAgain : qsTr("Do not show this message again") + + + signal cancel() + signal okay() + + + Rectangle { // SelectSourceType + id: sourceType + width: parent.width + height: parent.height + color: "transparent" + + Text { + anchors { + horizontalCenter : parent.horizontalCenter + top : parent.top + topMargin : Style.dialog.titleSize + } + + font.pointSize: Style.dialog.titleSize * Style.pt + color: Style.dialog.text + text: qsTr("Please select the source of the emails that you would like to import:") + } + + Row { + anchors { + centerIn: parent + } + + ImportSourceButton { + id: imapButton + width: winMain.width/2 + iconText: "envelope_open" + text: qsTr("Import from account") + onClicked: root.incrementCurrentIndex() + } + + ImportSourceButton { + id: fileButton + width: winMain.width/2 + iconText: "folder_open" + text: qsTr("Import local files") + onClicked: { pathDialog.visible = true } + anchors.bottom: imapButton.bottom + } + } + + FileDialog { + id: pathDialog + title: "Select local folder to import" + folder: shortcuts.home + onAccepted: { + sanitizePath(pathDialog.fileUrl.toString()) + root.okay() + } + selectFolder: true + } + } + + Rectangle { // ImapSource + id: imapSource + + Text { + id: imapSourceTitle + anchors { + top : parent.top + topMargin: imapSourceTitle.height / 2 + horizontalCenter : parent.horizontalCenter + } + + font.pointSize: Style.dialog.titleSize * Style.pt + color: Style.dialog.text + text: qsTr("Sign in to your Account") + } + + Rectangle { // line + id: titleLine + anchors { + top: imapSourceTitle.bottom + topMargin: imapSourceTitle.height / 2 + horizontalCenter : parent.horizontalCenter + } + width: imapSourceContent.width + height: Style.main.heightLine + color: Style.main.line + } + + Rectangle { + id: imapSourceContent + anchors { + top: titleLine.bottom + topMargin: imapSourceTitle.height / 2 + bottom: buttonRow.top + } + width: winMain.width + color: Style.dialog.background + + Text { + id: note + anchors { + bottom: wrapper.top + bottomMargin: imapSourceTitle.height / 2 + horizontalCenter : parent.horizontalCenter + } + text: qsTr( + "Many email providers (Gmail, Yahoo, etc.) will require you to allow remote sign-on in order to perform import through IMAP. See this article for details about how to do this with your email account.", + "Note added at IMAP credential page." + ).arg("https://protonmail.com/support/knowledge-base/allowing-imap-access-and-entering-imap-details/") + font { + pointSize: Style.dialog.fontSize * Style.pt + } + color: Style.dialog.text + linkColor: Style.dialog.textBlue + wrapMode: Text.WordWrap + textFormat: Text.StyledText + horizontalAlignment: Text.AlignHCenter + + width: parent.width * 0.618 + onLinkActivated: Qt.openUrlExternally(link) + } + + Rectangle { + id: wrapper + anchors.centerIn: parent + width: firstRow.width + height: firstRow.height + secondRow.height + secondRow.anchors.topMargin + color: Style.transparent + Row { + id: firstRow + spacing: imapSourceTitle.height + + InputField { + id: inputEmail + iconText: Style.fa.user_circle + label: qsTr("Email", "todo") + ":" + onEditingFinished: { + root.guessEmailProvider() + } + onAccepted: if (root.check_inputs()) root.okay() + anchors.horizontalCenter: undefined + } + + InputField { + id: inputPassword + label : qsTr("Password:") + iconText : Style.fa.lock + isPassword: true + onAccepted: if (root.check_inputs()) root.okay() + anchors.horizontalCenter: undefined + } + } + + Row { + id: secondRow + spacing: imapSourceTitle.height + anchors { + top: firstRow.bottom + topMargin: 2*imapSourceTitle.height + } + + InputField { + id: inputServer + iconText: Style.fa.server + label: qsTr("Server address", "todo") + ":" + onAccepted: if (root.check_inputs()) root.okay() + anchors.horizontalCenter: undefined + } + + InputField { + id: inputPort + iconText: Style.fa.hashtag + label: qsTr("Port:") + onAccepted: if (root.check_inputs()) root.okay() + anchors.horizontalCenter: undefined + } + } + } + } + + Row { // Buttons + id:buttonRow + anchors { + right : parent.right + bottom : parent.bottom + rightMargin : Style.dialog.rightMargin + bottomMargin : Style.dialog.bottomMargin + } + spacing: Style.main.leftMargin + + ButtonRounded { + fa_icon : Style.fa.times + text : qsTr("Cancel", "todo") + color_main : Style.dialog.textBlue + onClicked : root.cancel() + } + + ButtonRounded { + fa_icon : Style.fa.check + text : qsTr("Next", "todo") + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + onClicked : root.okay() + } + } + } + + Rectangle { // LoadingStructure + id: loadingStructures + color : Style.dialog.background + width : parent.width + height : parent.height + + Text { + anchors { + verticalCenter : parent.verticalCenter + horizontalCenter : parent.horizontalCenter + topMargin : Style.dialog.titleSize + } + font.pointSize: Style.dialog.titleSize * Style.pt + color: Style.dialog.text + text: root.isFromFile ? qsTr("Loading folder structures, please wait...") : qsTr("Loading structure of IMAP account, please wait...") + } + } + + Rectangle { // SourceToTarget + id: dialogStructure + width : parent.width + height : parent.height + + // Import instructions + ImportStructure { + id: importInstructions + anchors.bottom : masterImportSettings.top + titleFrom : root.isFromFile ? root.inputPath : inputEmail.text + titleTo : root.address + } + + Rectangle { + id: masterImportSettings + height: 150 // fixme + anchors { + right : parent.right + left : parent.left + bottom : parent.bottom + + leftMargin : Style.main.leftMargin + rightMargin : Style.main.leftMargin + bottomMargin : Style.main.bottomMargin + } + color: Style.dialog.background + + Text { + id: labelMasterImportSettings + text: qsTr("Master import settings:") + + font { + bold: true + family: Style.fontawesome.name + pointSize: Style.main.fontSize * Style.pt + } + color: Style.main.text + + InfoToolTip { + info: qsTr( + "If master import date range is selected only emails within this range will be imported, unless it is specified differently in folder date range.", + "Text in master import settings tooltip." + ) + anchors { + left: parent.right + bottom: parent.bottom + leftMargin : Style.dialog.leftMargin + } + } + } + + // Reset all to default + ClickIconText { + anchors { + right: parent.right + bottom: labelMasterImportSettings.bottom + } + text:qsTr("Reset all settings to default") + iconText: Style.fa.refresh + textColor: Style.main.textBlue + onClicked: { + root.decrementCurrentIndex() + timer.start() + } + } + + Rectangle{ + id: line + anchors { + left : parent.left + right : parent.right + top : labelMasterImportSettings.bottom + + topMargin : Style.dialog.spacing + } + height : Style.main.border * 2 + color : Style.main.line + } + + InlineDateRange { + id: globalDateRange + anchors { + left : parent.left + top : line.bottom + topMargin : Style.dialog.topMargin + } + } + + // Add global label (inline) + InlineLabelSelect { + id: globalLabels + anchors { + left : parent.left + top : globalDateRange.bottom + topMargin : Style.dialog.topMargin + } + //labelWidth : globalDateRange.labelWidth + } + + // Buttons + Row { + spacing: Style.dialog.spacing + anchors{ + bottom : parent.bottom + right : parent.right + } + + ButtonRounded { + id: buttonCancelThree + fa_icon : Style.fa.times + text : qsTr("Cancel", "todo") + color_main : Style.dialog.textBlue + onClicked : root.cancel() + } + + ButtonRounded { + id: buttonNextThree + fa_icon : Style.fa.check + text : qsTr("Import", "todo") + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + onClicked : root.okay() + } + } + } + } + + Rectangle { // Progress + id: progressStatus + width : parent.width + height : parent.height + color: Style.transparent + + Column { + anchors.centerIn: progressStatus + spacing: Style.dialog.heightSeparator + + Row { // description + spacing: Style.main.rightMargin + AccessibleText { + id: statusLabel + text : qsTr("Importing from:") + font.pointSize: Style.main.iconSize * Style.pt + color : Style.main.text + } + AccessibleText { + anchors.baseline: statusLabel.baseline + text : { + var sourceFolder = root.isFromFile ? root.inputPath : inputEmail.text + if (go.progressDescription != gui.enums.progressInit && go.progress!=0) { + sourceFolder += "/" + sourceFolder += go.progressDescription + } + return sourceFolder + } + elide: Text.ElideMiddle + width: progressbarImport.width - parent.spacing - statusLabel.width + font.pointSize: Style.dialog.textSize * Style.pt + color : Style.main.textDisabled + } + } + + ProgressBar { + id: progressbarImport + implicitWidth : 2*progressStatus.width/3 + implicitHeight : Style.exporting.rowHeight + value: go.progress + property int current: go.total * go.progress + property bool isFinished: finishedPartBar.width == progressbarImport.width + + background: Rectangle { + radius : Style.exporting.boxRadius + color : Style.exporting.progressBackground + } + + contentItem: Item { + Rectangle { + id: finishedPartBar + width : parent.width * progressbarImport.visualPosition + height : parent.height + radius : Style.exporting.boxRadius + gradient : Gradient { + GradientStop { position: 0.00; color: Qt.lighter(Style.exporting.progressStatus,1.1) } + GradientStop { position: 0.66; color: Style.exporting.progressStatus } + GradientStop { position: 1.00; color: Qt.darker(Style.exporting.progressStatus,1.1) } + } + + Behavior on width { + NumberAnimation { duration:800; easing.type: Easing.InOutQuad } + } + } + Text { + anchors.centerIn: parent + text: { + if (progressbarImport.isFinished) return qsTr("Import finished","todo") + if ( + go.progressDescription == gui.enums.progressInit || + (go.progress == 0 && go.description=="") + ) return qsTr("Estimating the total number of messages","todo") + if ( + go.progressDescription == gui.enums.progressLooping + ) return qsTr("Loading first message","todo") + //var msg = qsTr("Importing message %1 of %2 (%3%)","todo") + var msg = qsTr("Importing messages %1 of %2 (%3%)","todo") + if (root.paused) msg = qsTr("Importing paused at %1 of %2 (%3%)","todo") + return msg.arg(progressbarImport.current).arg(go.total).arg(Math.floor(go.progress*100)) + } + color: Style.main.background + font { + pointSize: Style.dialog.fontSize * Style.pt + } + } + } + + onIsFinishedChanged: { // show report + console.log("Is finished ", progressbarImport.isFinished) + if (progressbarImport.isFinished && root.currentIndex == DialogImport.Page.Progress) { + root.incrementCurrentIndex() + } + } + } + + Text { + property int fails: go.progressFails + visible: fails > 0 + color : Style.main.textRed + font.family: Style.fontawesome.name + font.pointSize: Style.main.fontSize * Style.pt + anchors.horizontalCenter: parent.horizontalCenter + text: Style.fa.exclamation_circle + " " + ( + fails == 1 ? + qsTr("%1 message failed to be imported").arg(fails) : + qsTr("%1 messages failed to be imported").arg(fails) + ) + } + + Row { // buttons + spacing: Style.dialog.rightMargin + anchors.horizontalCenter: parent.horizontalCenter + + ButtonRounded { + id: pauseButton + fa_icon : root.paused ? Style.fa.play : Style.fa.pause + text : root.paused ? qsTr("Resume") : qsTr("Pause") + color_main : Style.dialog.textBlue + onClicked : { + if (root.paused) { + if (winMain.updateState == gui.enums.statusNoInternet) { + go.notifyError(gui.enums.errNoInternet) + return + } + go.resumeProcess() + } else { + go.pauseProcess() + } + root.paused = !root.paused + pauseButton.focus=false + } + visible : !progressbarImport.isFinished + } + + ButtonRounded { + fa_icon : Style.fa.times + text : qsTr("Cancel") + color_main : Style.dialog.textBlue + visible : !progressbarImport.isFinished + onClicked : root.ask_cancel_progress() + } + + ButtonRounded { + id: finish + fa_icon : Style.fa.check + text : qsTr("Okay","todo") + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + visible : progressbarImport.isFinished + onClicked : root.okay() + } + } + } + + ImportReport { + } + + ClickIconText { + id: buttonHelp + anchors { + bottom: progressStatus.bottom + right: progressStatus.right + margins: Style.main.rightMargin + } + + textColor : Style.main.textDisabled + iconText : Style.fa.question_circle + text : qsTr("Help", "directs the user to the online user guide") + textBold : true + onClicked : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/") + } + } + + Rectangle { // Report + id: finalReport + width : parent.width + height : parent.height + color: Style.transparent + + property int imported: go.total - go.progressFails + + + Column { + anchors.centerIn : finalReport + spacing : Style.dialog.heightSeparator + + Text { + text: Style.fa.check_circle + " " + qsTr("Import completed successfully") + anchors.horizontalCenter: parent.horizontalCenter + color: Style.main.textGreen + font.bold : true + font.family: Style.fontawesome.name + } + + Text { + text: qsTr("Import summary:
Total number of emails: %1
Imported emails: %2
Errors: %3").arg(go.total).arg(finalReport.imported).arg(go.progressFails) + anchors.horizontalCenter: parent.horizontalCenter + textFormat: Text.RichText + horizontalAlignment: Text.AlignHCenter + } + + Row { + spacing: Style.dialog.rightMargin + anchors.horizontalCenter: parent.horizontalCenter + + ButtonRounded { + fa_icon : Style.fa.info_circle + text : qsTr("View errors") + color_main : Style.dialog.textBlue + onClicked : { + if (go.importLogFileName=="") { + console.log("onViewErrors: missing import log file name") + return + } + go.loadImportReports(go.importLogFileName) + reportList.show() + } + } + + ButtonRounded { + fa_icon : Style.fa.send + text : qsTr("Report files") + color_main : Style.dialog.textBlue + onClicked : { + if (go.importLogFileName=="") { + console.log("onReportError: missing import log file name") + return + } + root.ask_send_report() + } + } + } + + ButtonRounded { + text : qsTr("Close") + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + anchors.horizontalCenter: parent.horizontalCenter + onClicked: root.okay() + } + } + + ImportReport { + id: reportList + anchors.fill: finalReport + } + + ClickIconText { + anchors { + bottom: finalReport.bottom + right: finalReport.right + margins: Style.main.rightMargin + } + + textColor : Style.main.textDisabled + iconText : Style.fa.question_circle + text : qsTr("Help", "directs the user to the online user guide") + textBold : true + onClicked : Qt.openUrlExternally("https://protonmail.com/support/categories/import-export/") + } + } + + function guessEmailProvider() { + var splitMail = inputEmail.text.split("@") + //console.log("finished ", splitMail) + if (splitMail.length != 2) return + switch (splitMail[1]){ + case "yandex.ru": + case "yandex.com": + case "ya.ru": + inputServer.text = "imap.yandex.ru" + inputPort.text = "993" + break + case "outlook.com": + case "hotmail.com": + case "live.com": + case "live.ru": + inputServer.text = "imap-mail.outlook.com" + inputPort.text = "993" + break + case "seznam.cz": + case "email.cz": + case "post.cz": + inputServer.text = "imap.seznam.cz" + inputPort.text = "993" + break + case "gmx.de": + inputServer.text = "imap.gmx.net" + inputPort.text = "993" + break + case "rundbox.com": + inputServer.text = "mail."+splitMail[1] + inputPort.text = "993" + break + case "fastmail.com": + case "aol.com": + case "orange.fr": + case "hushmail.com": + case "ntlworld.com": + case "aol.com": + case "gmx.com": + case "mail.com": + case "mail.ru": + case "gmail.com": + inputServer.text = "imap."+splitMail[1] + inputPort.text = "993" + break + case (splitMail[1].match(/^yahoo\./) || {}).input: + inputServer.text = "imap.mail.yahoo.com" + inputPort.text = "993" + break + default: + } + + return + } + + function setServerParams() { + switch (emailProvider.currentIndex) { + case 1: + inputServer.text = "imap.gmail.com" + inputPort.text = "993" + break + case 2: + inputServer.text = "imap.yandex.com" + inputPort.text = "993" + break + case 3: + inputServer.text = "imap.outlook.com" + inputPort.text = "993" + break + case 4: + inputServer.text = "imap.yahoo.com" + inputPort.text = "993" + break + } + + return + } + + function update_label_time() { + var d = new Date(); + var outstring = " " + outstring+=qsTr("Import") + outstring+="-" + outstring+=d.getDate() + outstring+="-" + outstring+=d.getMonth()+1 + outstring+="-" + outstring+=d.getFullYear() + outstring+=" " + outstring+=d.getHours() + outstring+=":" + outstring+=d.getMinutes() + outstring+=" " + importLabel.text = outstring + } + + function clear() { + go.resetSource() + root.inputPath = "" + clear_status() + inputEmail.clear() + inputPassword.clear() + inputServer.clear() + inputPort.clear() + reportList.hide() + globalLabels.reset() + } + + PopupMessage { + id: errorPopup + width : parent.width + height : parent.height + msgWidth : root.width * 0.6108 + } + + Connections { + target: errorPopup + + onClickedOkay : errorPopup.hide() + + onClickedYes : { + if (errorPopup.msgID == "ask_send_report") { + errorPopup.hide() + root.report_sent(go.sendImportReport(root.address,go.importLogFileName)) + return + } + root.cancel() + errorPopup.hide() + } + onClickedNo : { + if (errorPopup.msgID == "ask_send_report") { + errorPopup.hide() + return + } + go.resumeProcess() + errorPopup.hide() + } + + onClickedRetry : { + go.answerRetry() + errorPopup.hide() + } + onClickedSkip : { + go.answerSkip( + errorPopup.checkbox.text == root.msgDontShowAgain && + errorPopup.checkbox.checked + ) + errorPopup.hide() + } + onClickedCancel : { + root.cancel() + errorPopup.hide() + } + + /* + onClickedCancel : { + errorPopup.hide() + root.ask_cancel_progress() + } + */ + } + + + function check_inputs() { + var isOK = true + switch (currentIndex) { + case 0: // select source + var res = go.checkPathStatus(root.inputPath) + isOK = ( + (res&gui.enums.pathOK)==gui.enums.pathOK && + (res&gui.enums.pathNotADir)==0 && // is a dir + (res&gui.enums.pathDirEmpty)==0 // is nonempty + ) + if (!isOK) errorPopup.show(qsTr("Please select non-empty folder.")) + break + // check directory + case 1: // imap settings + if (!( + inputEmail . checkNonEmpty() && + inputPassword . checkNonEmpty() && + inputServer . checkNonEmpty() && + inputPort . checkNonEmpty() && + inputPort . checkIsANumber() + //emailProvider . currentIndex!=0 + )) isOK = false + go.checkInternet() + if (winMain.updateState == gui.enums.statusNoInternet) { // todo: use main error dialog for this + errorPopup.show(qsTr("Please check your internet connection.")) + return false + } + break + case 2: // loading structure + go.checkInternet() + if (winMain.updateState == gui.enums.statusNoInternet) { + errorPopup.show(qsTr("Please check your internet connection.")) + return false + } + break + case 3: // import insturctions + if (!structureExternal.hasTarget()) { + errorPopup.show(qsTr("Nothing selected for import.")) + return false + } + break + case 4: // import status + } + return isOK + } + + onCancel: { + switch (currentIndex) { + case DialogImport.Page.ImapSource: + case DialogImport.Page.LoadingStructure: + root.clear() + root.currentIndex=0 + break + case DialogImport.Page.SelectSourceType: + case DialogImport.Page.SourceToTarget: + case DialogImport.Page.Report: + root.hide() + break + case DialogImport.Page.Progress: + go.cancelProcess(false) + root.currentIndex=3 + root.clear_status() + globalLabels.reset() + break + } + } + + onOkay: { + var isOK = check_inputs() + if (isOK) { + timer.interval= currentIndex==0 || currentIndex==4 ? 10 : 300 + switch (currentIndex) { + case DialogImport.Page.SelectSourceType: // select source + currentIndex=2 + break + + case DialogImport.Page.SourceToTarget: + globalDateRange.applyRange() + if (globalLabels.labelSelected) { + var isOK = go.createLabelOrFolder( + winMain.dialogImport.address, + globalLabels.labelName, + globalLabels.labelColor, + true, + structureExternal.getID(-1) + ) + if (!isOK) return + } + incrementCurrentIndex() + break + + case DialogImport.Page.Report: + root.clear_status() + root.hide() + break + + case DialogImport.Page.LoadingStructure: + globalLabels.reset() + importInstructions.hasItems = (structureExternal.rowCount() > 0) + case DialogImport.Page.ImapSource: + default: + incrementCurrentIndex() + } + timer.start() + } + } + + onShow : { + root.clear() + if (winMain.updateState==gui.enums.statusNoInternet) { + go.checkInternet() + if (winMain.updateState==gui.enums.statusNoInternet) { + winMain.popupMessage.show(go.canNotReachAPI) + root.hide() + } + } + } + + onHide : { + root.clear() + } + + function clear_status() { // TODO: move this to Gui.qml + go.progress=0.0 + go.progressFails=0.0 + go.total=0.0 + go.progressDescription=gui.enums.progressInit + } + + function ask_send_report(){ + errorPopup.msgID="ask_send_report" + errorPopup.buttonYes.visible = true + errorPopup.buttonNo.visible = true + errorPopup.buttonOkay.visible = false + errorPopup.show (qsTr("Program will send the report of finished import process to our customer support. The report was filtered to remove all personal information.\n\nDo you want to send report?")) + } + + function report_sent(isOK){ + errorPopup.msgID="report_sent" + if (isOK) { + errorPopup.show (qsTr("Report sent successfully.")) + } else { + errorPopup.show (qsTr("Not able to send report. Please contact customer support at importexport@protonmail.com")) + } + } + + function ask_cancel_progress(){ + errorPopup.msgID="ask_cancel_progress" + errorPopup.buttonYes.visible = true + errorPopup.buttonNo.visible = true + errorPopup.buttonOkay.visible = false + errorPopup.show (qsTr("Are you sure you want to cancel this import?")) + } + + function ask_retry_skip_cancel(subject,errorMessage){ + errorPopup.msgID="ask_retry_skip_cancel" + errorPopup.buttonYes.visible = false + errorPopup.buttonNo.visible = false + errorPopup.buttonOkay.visible = false + + errorPopup.buttonRetry.visible = true + errorPopup.buttonSkip.visible = true + errorPopup.buttonCancel.visible = true + + errorPopup.checkbox.text = root.msgDontShowAgain + + errorPopup.show( + qsTr( + "Cannot import message \"%1\"\n\n%2\nCancel will stop the entire import.", + "error message while importing: arg1 is message subject, arg2 is error message" + ).arg(subject).arg(errorMessage) + ) + } + + function sanitizePath(path) { + var pattern = "file://" + if (go.goos=="windows") pattern+="/" + root.inputPath = path.replace(pattern, "") + } + + Connections { + target: timer + onTriggered: { + switch (currentIndex) { + case DialogImport.Page.SelectSourceType: + case DialogImport.Page.ImapSource: + case DialogImport.Page.SourceToTarget: + globalDateRange.setRange() + break + case DialogImport.Page.LoadingStructure: + go.setupAndLoadForImport( + root.isFromIMAP, + root.inputPath, + inputEmail.text, inputPassword.text, inputServer.text, inputPort.text, + root.address + ) + break + case DialogImport.Page.Progress: + go.startImport(root.address) + break + } + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/DialogYesNo.qml b/internal/frontend/qml/ImportExportUI/DialogYesNo.qml new file mode 100644 index 00000000..94d6d0a1 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/DialogYesNo.qml @@ -0,0 +1,354 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Dialog with Yes/No buttons + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Dialog { + id: root + + title : "" + + property string input + + property alias question : msg.text + property alias note : noteText.text + property alias answer : answ.text + property alias buttonYes : buttonYes + property alias buttonNo : buttonNo + + isDialogBusy: currentIndex==1 + + signal confirmed() + + Column { + id: dialogMessage + property int heightInputs : msg.height+ + middleSep.height+ + buttonRow.height + + (checkboxSep.visible ? checkboxSep.height : 0 ) + + (noteSep.visible ? noteSep.height : 0 ) + + (checkBoxWrapper.visible ? checkBoxWrapper.height : 0 ) + + (root.note!="" ? noteText.height : 0 ) + + Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-dialogMessage.heightInputs)/2 } + + AccessibleText { + id:noteText + anchors.horizontalCenter: parent.horizontalCenter + color: Style.dialog.text + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: false + } + width: 2*root.width/3 + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + } + Rectangle { id: noteSep; visible: note!=""; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator} + + AccessibleText { + id: msg + anchors.horizontalCenter: parent.horizontalCenter + color: Style.dialog.text + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: true + } + width: 2*parent.width/3 + text : "" + horizontalAlignment: Text.AlignHCenter + wrapMode: Text.Wrap + } + + Rectangle { id: checkboxSep; visible: checkBoxWrapper.visible; color : "transparent"; width : Style.main.dummy; height : Style.dialog.heightSeparator} + Row { + id: checkBoxWrapper + property bool isChecked : false + visible: root.state=="deleteUser" + anchors.horizontalCenter: parent.horizontalCenter + spacing: Style.dialog.spacing + + function toggle() { + checkBoxWrapper.isChecked = !checkBoxWrapper.isChecked + } + + Text { + id: checkbox + font { + pointSize : Style.dialog.iconSize * Style.pt + family : Style.fontawesome.name + } + anchors.verticalCenter : parent.verticalCenter + text: checkBoxWrapper.isChecked ? Style.fa.check_square_o : Style.fa.square_o + color: checkBoxWrapper.isChecked ? Style.main.textBlue : Style.main.text + + MouseArea { + anchors.fill: parent + onPressed: checkBoxWrapper.toggle() + cursorShape: Qt.PointingHandCursor + } + } + Text { + id: checkBoxNote + anchors.verticalCenter : parent.verticalCenter + text: qsTr("Additionally delete all stored preferences and data", "when removing an account, this extra preference additionally deletes all cached data") + color: Style.main.text + font.pointSize: Style.dialog.fontSize * Style.pt + + MouseArea { + anchors.fill: parent + onPressed: checkBoxWrapper.toggle() + cursorShape: Qt.PointingHandCursor + + Accessible.role: Accessible.CheckBox + Accessible.checked: checkBoxWrapper.isChecked + Accessible.name: checkBoxNote.text + Accessible.description: checkBoxNote.text + Accessible.ignored: checkBoxNote.text == "" + Accessible.onToggleAction: checkBoxWrapper.toggle() + Accessible.onPressAction: checkBoxWrapper.toggle() + } + } + } + + Rectangle { id: middleSep; color : "transparent"; width : Style.main.dummy; height : 2*Style.dialog.heightSeparator } + + Row { + id: buttonRow + anchors.horizontalCenter: parent.horizontalCenter + spacing: Style.dialog.spacing + ButtonRounded { + id:buttonNo + color_main : Style.dialog.textBlue + fa_icon : Style.fa.times + text : qsTr("No") + onClicked : root.hide() + } + ButtonRounded { + id: buttonYes + color_main : Style.dialog.background + color_minor : Style.dialog.textBlue + isOpaque : true + fa_icon : Style.fa.check + text : qsTr("Yes") + onClicked : { + currentIndex=1 + root.confirmed() + } + } + } + } + + + Column { + Rectangle { color : "transparent"; width : Style.main.dummy; height : (root.height-answ.height)/2 } + AccessibleText { + id: answ + anchors.horizontalCenter: parent.horizontalCenter + color: Style.dialog.text + font { + pointSize : Style.dialog.fontSize * Style.pt + bold : true + } + width: 3*parent.width/4 + horizontalAlignment: Text.AlignHCenter + text : qsTr("Waiting...", "in general this displays between screens when processing data takes a long time") + wrapMode: Text.Wrap + } + } + + + states : [ + State { + name: "quit" + PropertyChanges { + target: root + currentIndex : 0 + title : qsTr("Close ImportExport", "quits the application") + question : qsTr("Are you sure you want to close the ImportExport?", "asked when user tries to quit the application") + note : "" + answer : qsTr("Closing ImportExport...", "displayed when user is quitting application") + } + }, + State { + name: "logout" + PropertyChanges { + target: root + currentIndex : 1 + title : qsTr("Logout", "title of page that displays during account logout") + question : "" + note : "" + answer : qsTr("Logging out...", "displays during account logout") + } + }, + State { + name: "deleteUser" + PropertyChanges { + target: root + currentIndex : 0 + title : qsTr("Delete account", "title of page that displays during account deletion") + question : qsTr("Are you sure you want to remove this account?", "displays during account deletion") + note : "" + answer : qsTr("Deleting ...", "displays during account deletion") + } + }, + State { + name: "clearChain" + PropertyChanges { + target : root + currentIndex : 0 + title : qsTr("Clear keychain", "title of page that displays during keychain clearing") + question : qsTr("Are you sure you want to clear your keychain?", "displays during keychain clearing") + note : qsTr("This will remove all accounts that you have added to the Import-Export tool.", "displays during keychain clearing") + answer : qsTr("Clearing the keychain ...", "displays during keychain clearing") + } + }, + State { + name: "clearCache" + PropertyChanges { + target: root + currentIndex : 0 + title : qsTr("Clear cache", "title of page that displays during cache clearing") + question : qsTr("Are you sure you want to clear your local cache?", "displays during cache clearing") + note : qsTr("This will delete all of your stored preferences.", "displays during cache clearing") + answer : qsTr("Clearing the cache ...", "displays during cache clearing") + } + }, + State { + name: "checkUpdates" + PropertyChanges { + target: root + currentIndex : 1 + title : "" + question : "" + note : "" + answer : qsTr("Checking for updates ...", "displays if user clicks the Check for Updates button in the Help tab") + } + }, + State { + name: "internetCheck" + PropertyChanges { + target: root + currentIndex : 1 + title : "" + question : "" + note : "" + answer : qsTr("Contacting server...", "displays if user clicks the Check for Updates button in the Help tab") + } + }, + State { + name: "addressmode" + PropertyChanges { + target: root + currentIndex : 0 + title : "" + question : qsTr("Do you want to continue?", "asked when the user changes between split and combined address mode") + note : qsTr("Changing between split and combined address mode will require you to delete your account(s) from your email client and begin the setup process from scratch.", "displayed when the user changes between split and combined address mode") + answer : qsTr("Configuring address mode for ", "displayed when the user changes between split and combined address mode") + root.input + } + }, + State { + name: "toggleAutoStart" + PropertyChanges { + target: root + currentIndex : 1 + question : "" + note : "" + title : "" + answer : { + var msgTurnOn = qsTr("Turning on automatic start of ImportExport...", "when the automatic start feature is selected") + var msgTurnOff = qsTr("Turning off automatic start of ImportExport...", "when the automatic start feature is deselected") + return go.isAutoStart==0 ? msgTurnOff : msgTurnOn + } + } + }, + State { + name: "undef"; + PropertyChanges { + target: root + currentIndex : 1 + question : "" + note : "" + title : "" + answer : "" + } + } + ] + + + Shortcut { + sequence: StandardKey.Cancel + onActivated: root.hide() + } + + Shortcut { + sequence: "Enter" + onActivated: root.confirmed() + } + + onHide: { + checkBoxWrapper.isChecked = false + state = "undef" + } + + onShow: { + // hide all other dialogs + winMain.dialogAddUser .visible = false + winMain.dialogCredits .visible = false + //winMain.dialogVersionInfo .visible = false + // dialogFirstStart should reappear again after closing global + root.visible = true + } + + + + onConfirmed : { + if (state == "quit" || state == "instance exists" ) { + timer.interval = 1000 + } else { + timer.interval = 300 + } + answ.forceActiveFocus() + timer.start() + } + + Connections { + target: timer + onTriggered: { + if ( state == "addressmode" ) { go.switchAddressMode (input) } + if ( state == "clearChain" ) { go.clearKeychain () } + if ( state == "clearCache" ) { go.clearCache () } + if ( state == "deleteUser" ) { go.deleteAccount (input, checkBoxWrapper.isChecked) } + if ( state == "logout" ) { go.logoutAccount (input) } + if ( state == "toggleAutoStart" ) { go.toggleAutoStart () } + if ( state == "quit" ) { Qt.quit () } + if ( state == "instance exists" ) { Qt.quit () } + if ( state == "checkUpdates" ) { go.runCheckVersion (true) } + } + } + + Keys.onPressed: { + if (event.key == Qt.Key_Enter) { + root.confirmed() + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/ExportStructure.qml b/internal/frontend/qml/ImportExportUI/ExportStructure.qml new file mode 100644 index 00000000..867f3ab5 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ExportStructure.qml @@ -0,0 +1,151 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of export folders / labels +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + color : Style.exporting.background + radius : Style.exporting.boxRadius + border { + color : Style.exporting.line + width : Style.dialog.borderInput + } + property bool hasItems: true + + + Text { // placeholder + visible: !root.hasItems + anchors.centerIn: parent + color: Style.main.textDisabled + font { + pointSize: Style.dialog.fontSize * Style.pt + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: qsTr("No emails found for this address.","todo") + } + + + property string title : "" + + TextMetrics { + id: titleMetrics + text: root.title + elide: Qt.ElideMiddle + elideWidth: root.width - 4*Style.exporting.leftMargin + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: true + } + } + + Rectangle { + id: header + anchors { + top: root.top + left: root.left + } + width : root.width + height : Style.dialog.fontSize*3 + color : Style.transparent + Rectangle { + anchors.bottom: parent.bottom + color : Style.exporting.line + height : Style.dialog.borderInput + width : parent.width + } + + Text { + anchors { + left : parent.left + leftMargin : 2*Style.exporting.leftMargin + verticalCenter : parent.verticalCenter + } + color: Style.dialog.text + font: titleMetrics.font + text: titleMetrics.elidedText + } + } + + + ListView { + id: listview + clip : true + orientation : ListView.Vertical + boundsBehavior : Flickable.StopAtBounds + model : structurePM + cacheBuffer : 10000 + + anchors { + left : root.left + right : root.right + bottom : root.bottom + top : header.bottom + margins : Style.dialog.borderInput + } + + ScrollBar.vertical: ScrollBar { + /* + policy: ScrollBar.AsNeeded + background : Rectangle { + color : Style.exporting.sliderBackground + radius : Style.exporting.boxRadius + } + contentItem : Rectangle { + color : Style.exporting.sliderForeground + radius : Style.exporting.boxRadius + implicitWidth : Style.main.rightMargin / 3 + } + */ + anchors { + right: parent.right + rightMargin: Style.main.rightMargin/4 + } + width: Style.main.rightMargin/3 + Accessible.ignored: true + } + + delegate: FolderRowButton { + width : root.width - 5*root.border.width + type : folderType + color : folderColor + title : folderName + isSelected : isFolderSelected + onClicked : { + //console.log("Clicked", folderId, isSelected) + structurePM.setFolderSelection(folderId,!isSelected) + } + } + + section.property: "folderType" + section.delegate: FolderRowButton { + isSection : true + width : root.width - 5*root.border.width + title : gui.folderTypeTitle(section) + isSelected : { + //console.log("section selected changed: ", section) + return section == gui.enums.folderTypeLabel ? structurePM.selectedLabels : structurePM.selectedFolders + } + onClicked : structurePM.selectType(section,!isSelected) + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/FilterStructure.qml b/internal/frontend/qml/ImportExportUI/FilterStructure.qml new file mode 100644 index 00000000..3d452152 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/FilterStructure.qml @@ -0,0 +1,55 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Filter only selected folders or labels +import QtQuick 2.8 +import QtQml.Models 2.2 + + +DelegateModel { + id: root + model : structurePM + //filterOnGroup : root.folderType + //delegate : root.delegate + groups : [ + DelegateModelGroup {name: gui.enums.folderTypeFolder ; includeByDefault: false}, + DelegateModelGroup {name: gui.enums.folderTypeLabel ; includeByDefault: false} + ] + + function updateFilter() { + //console.log("FilterModelDelegate::UpdateFilter") + // filter + var rowCount = root.items.count; + for (var iItem = 0; iItem < rowCount; iItem++) { + var entry = root.items.get(iItem); + entry.inLabel = ( + root.filterOnGroup == gui.enums.folderTypeLabel && + entry.model.folderType == gui.enums.folderTypeLabel + ) + entry.inFolder = ( + root.filterOnGroup == gui.enums.folderTypeFolder && + entry.model.folderType != gui.enums.folderTypeLabel + ) + /* + if (entry.inFolder && entry.model.folderId == selectedIDs) { + view.currentIndex = iItem + } + */ + //console.log("::::update filter:::::", iItem, entry.model.folderName, entry.inFolder, entry.inLabel) + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/FolderRowButton.qml b/internal/frontend/qml/ImportExportUI/FolderRowButton.qml new file mode 100644 index 00000000..43e85554 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/FolderRowButton.qml @@ -0,0 +1,99 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Checkbox row for folder selection +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +AccessibleButton { + id: root + + property bool isSection : false + property bool isSelected : false + property string title : "N/A" + property string type : "" + property color color : "black" + + height : Style.exporting.rowHeight + padding : 0.0 + anchors { + horizontalCenter: parent.horizontalCenter + } + + background: Rectangle { + color: isSection ? Style.exporting.background : Style.exporting.rowBackground + Rectangle { // line + anchors.bottom : parent.bottom + height : Style.dialog.borderInput + width : parent.width + color : Style.exporting.background + } + } + + contentItem: Rectangle { + color: "transparent" + id: content + Text { + id: checkbox + anchors { + verticalCenter : parent.verticalCenter + left : content.left + leftMargin : Style.exporting.leftMargin * (root.type == gui.enums.folderTypeSystem ? 1.0 : 2.0) + } + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + color : isSelected ? Style.main.text : Style.main.textInactive + text : (isSelected ? Style.fa.check_square_o : Style.fa.square_o ) + } + + Text { // icon + id: folderIcon + visible: !isSection + anchors { + verticalCenter : parent.verticalCenter + left : checkbox.left + leftMargin : Style.dialog.fontSize + Style.exporting.leftMargin + } + color : root.type==gui.enums.folderTypeSystem ? Style.main.textBlue : root.color + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + text : { + return gui.folderIcon(root.title.toLowerCase(), root.type) + } + } + + Text { + text: root.title + anchors { + verticalCenter : parent.verticalCenter + left : isSection ? checkbox.left : folderIcon.left + leftMargin : Style.dialog.fontSize + Style.exporting.leftMargin + } + font { + pointSize : Style.dialog.fontSize * Style.pt + bold: isSection + } + color: Style.exporting.text + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/HelpView.qml b/internal/frontend/qml/ImportExportUI/HelpView.qml new file mode 100644 index 00000000..22e9e39d --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/HelpView.qml @@ -0,0 +1,129 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List the settings + +import QtQuick 2.8 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Item { + id: root + + // must have wrapper + Rectangle { + id: wrapper + anchors.centerIn: parent + width: parent.width + height: parent.height + color: Style.main.background + + // content + Column { + anchors.horizontalCenter : parent.horizontalCenter + + + ButtonIconText { + id: manual + anchors.left: parent.left + text: qsTr("Setup Guide") + leftIcon.text : Style.fa.book + rightIcon.text : Style.fa.chevron_circle_right + rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt + onClicked: go.openManual() + } + + ButtonIconText { + id: updates + anchors.left: parent.left + text: qsTr("Check for Updates") + leftIcon.text : Style.fa.refresh + rightIcon.text : Style.fa.chevron_circle_right + rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt + onClicked: { + dialogGlobal.state="checkUpdates" + dialogGlobal.show() + dialogGlobal.confirmed() + } + } + + Rectangle { + anchors.horizontalCenter : parent.horizontalCenter + height: Math.max ( + aboutText.height + + Style.main.fontSize, + wrapper.height - ( + 2*manual.height + + creditsLink.height + + Style.main.fontSize + ) + ) + width: wrapper.width + color : Style.transparent + Text { + id: aboutText + anchors { + bottom: parent.bottom + horizontalCenter: parent.horizontalCenter + } + color: Style.main.textDisabled + horizontalAlignment: Qt.AlignHCenter + font.family : Style.fontawesome.name + text: "ProtonMail Import-Export Version "+go.getBackendVersion()+"\n"+Style.fa.copyright + " 2020 Proton Technologies AG" + } + } + + Row { + anchors.horizontalCenter : parent.horizontalCenter + spacing : Style.main.dummy + + Text { + id: creditsLink + text : qsTr("Credits", "link to click on to view list of credited libraries") + color : Style.main.textDisabled + font.pointSize: Style.main.fontSize * Style.pt + font.underline: true + MouseArea { + anchors.fill: parent + onClicked : { + winMain.dialogCredits.show() + } + cursorShape: Qt.PointingHandCursor + } + } + + + Text { + id: releaseNotes + text : qsTr("Release notes", "link to click on to view release notes for this version of the app") + color : Style.main.textDisabled + font.pointSize: Style.main.fontSize * Style.pt + font.underline: true + MouseArea { + anchors.fill: parent + onClicked : { + go.getLocalVersionInfo() + winMain.dialogVersionInfo.show() + } + cursorShape: Qt.PointingHandCursor + } + } + } + } + } +} + diff --git a/internal/frontend/qml/ImportExportUI/IEStyle.qml b/internal/frontend/qml/ImportExportUI/IEStyle.qml new file mode 100644 index 00000000..71578633 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/IEStyle.qml @@ -0,0 +1,106 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Adjust Bridge Style + +import QtQuick 2.8 +import ImportExportUI 1.0 +import ProtonUI 1.0 + +Item { + Component.onCompleted : { + //Style.refdpi = go.goos == "darwin" ? 86.0 : 96.0 + Style.pt = go.goos == "darwin" ? 93/Style.dpi : 80/Style.dpi + + Style.main.background = "#fff" + Style.main.text = "#505061" + Style.main.textInactive = "#686876" + Style.main.line = "#dddddd" + Style.main.width = 884 * Style.px + Style.main.height = 422 * Style.px + Style.main.leftMargin = 25 * Style.px + Style.main.rightMargin = 25 * Style.px + + Style.title.background = Style.main.text + Style.title.text = Style.main.background + + Style.tabbar.background = "#3D3A47" + Style.tabbar.rightButton = "add account" + Style.tabbar.spacingButton = 45*Style.px + + Style.accounts.backgroundExpanded = "#fafafa" + Style.accounts.backgroundAddrRow = "#fff" + Style.accounts.leftMargin2 = Style.main.width/2 + Style.accounts.leftMargin3 = 5.5*Style.main.width/8 + + + Style.dialog.background = "#fff" + Style.dialog.text = Style.main.text + Style.dialog.line = "#e2e2e2" + Style.dialog.fontSize = 12 * Style.px + Style.dialog.heightInput = 2.2*Style.dialog.fontSize + Style.dialog.heightButton = Style.dialog.heightInput + Style.dialog.borderInput = 1 * Style.px + + Style.bubble.background = "#595966" + Style.bubble.paneBackground = "#454553" + Style.bubble.text = "#fff" + Style.bubble.width = 310 * Style.px + Style.bubble.widthPane = 36 * Style.px + Style.bubble.iconSize = 14 * Style.px + + + // colors: + // text: #515061 + // tick: #686876 + // blue icon: #9396cc + // row bck: #f8f8f9 + // line: #ddddde or #e2e2e2 + // + // slider bg: #e6e6e6 + // slider fg: #515061 + // info icon: #c3c3c8 + // input border: #ebebeb + // + // bubble color: #595966 + // bubble pane: #454553 + // bubble text: #fff + // + // indent folder + // + // Dimensions: + // full width: 882px + // leftMargin: 25px + // rightMargin: 25px + // rightMargin: 25px + // middleSeparator: 69px + // width folders: 416px or (width - separators) /2 + // width output: 346px or (width - separators) /2 + // + // height from top to input begin: 78px + // heightSeparator: 27px + // height folder input: 26px + // + // buble width: 309px + // buble left pane icon: 14px + // buble left pane width: 36px or (2.5 icon width) + // buble height: 46px + // buble arrow height: 12px + // buble arrow width: 14px + // buble radius: 3-4px + } +} diff --git a/internal/frontend/qml/ImportExportUI/ImportDelegate.qml b/internal/frontend/qml/ImportExportUI/ImportDelegate.qml new file mode 100644 index 00000000..ebb72d35 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ImportDelegate.qml @@ -0,0 +1,164 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of import folder and their target +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + color: Style.importing.rowBackground + height: 40 + width: 300 + property real leftMargin1 : folderIcon.x - root.x + property real leftMargin2 : selectFolder.x - root.x + property real nameWidth : { + var available = root.width + available -= rowPlacement.children.length * rowPlacement.spacing // spacing between places + available -= 3*rowPlacement.leftPadding // left, and 2x right + available -= folderIcon.width + available -= arrowIcon.width + available -= dateRangeMenu.width + return available/3.3 // source folder label, target folder menu, target labels menu, and 0.3x label list + } + property real iconWidth : nameWidth*0.3 + + property bool isSourceSelected: targetFolderID!="" + property string lastTargetFolder: "6" // Archive + property string lastTargetLabels: "" // no flag by default + + + Rectangle { + id: line + anchors { + left : parent.left + right : parent.right + bottom : parent.bottom + } + height : Style.main.border * 2 + color : Style.importing.rowLine + } + + Row { + id: rowPlacement + spacing: Style.dialog.spacing + leftPadding: Style.dialog.spacing*2 + anchors.verticalCenter : parent.verticalCenter + + CheckBoxLabel { + id: checkBox + anchors.verticalCenter : parent.verticalCenter + checked: root.isSourceSelected + + onClicked: root.toggleImport() + } + + Text { + id: folderIcon + text : gui.folderIcon(folderName, gui.enums.folderTypeFolder) + anchors.verticalCenter : parent.verticalCenter + color: root.isSourceSelected ? Style.main.text : Style.main.textDisabled + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + } + + Text { + text : folderName + width: nameWidth + elide: Text.ElideRight + anchors.verticalCenter : parent.verticalCenter + color: folderIcon.color + font.pointSize : Style.dialog.fontSize * Style.pt + } + + Text { + id: arrowIcon + text : Style.fa.arrow_right + anchors.verticalCenter : parent.verticalCenter + color: Style.main.text + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + } + + SelectFolderMenu { + id: selectFolder + sourceID: folderId + selectedIDs: targetFolderID + width: nameWidth + anchors.verticalCenter : parent.verticalCenter + onDoNotImport: root.toggleImport() + onImportToFolder: root.importToFolder(newTargetID) + } + + SelectLabelsMenu { + sourceID: folderId + selectedIDs: targetLabelIDs + width: nameWidth + anchors.verticalCenter : parent.verticalCenter + enabled: root.isSourceSelected + } + + LabelIconList { + selectedIDs: targetLabelIDs + width: iconWidth + anchors.verticalCenter : parent.verticalCenter + enabled: root.isSourceSelected + } + + DateRangeMenu { + id: dateRangeMenu + sourceID: folderId + + enabled: root.isSourceSelected + anchors.verticalCenter : parent.verticalCenter + } + } + + + function importToFolder(newTargetID) { + if (root.isSourceSelected) { + structureExternal.setTargetFolderID(folderId,newTargetID) + } else { + lastTargetFolder = newTargetID + toggleImport() + } + } + + function toggleImport() { + if (root.isSourceSelected) { + lastTargetFolder = targetFolderID + lastTargetLabels = targetLabelIDs + structureExternal.setTargetFolderID(folderId,"") + return Qt.Unchecked + } else { + structureExternal.setTargetFolderID(folderId,lastTargetFolder) + var labelsSplit = lastTargetLabels.split(";") + for (var labelIndex in labelsSplit) { + var labelID = labelsSplit[labelIndex] + structureExternal.addTargetLabelID(folderId,labelID) + } + return Qt.Checked + } + } + +} diff --git a/internal/frontend/qml/ImportExportUI/ImportReport.qml b/internal/frontend/qml/ImportExportUI/ImportReport.qml new file mode 100644 index 00000000..eab56bef --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ImportReport.qml @@ -0,0 +1,216 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Import report modal +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + color: "#aa101021" + visible: false + + MouseArea { // disable bellow + anchors.fill: root + hoverEnabled: true + } + + Rectangle { + id:background + color: Style.main.background + anchors { + fill : root + topMargin : Style.main.rightMargin + leftMargin : 2*Style.main.rightMargin + rightMargin : 2*Style.main.rightMargin + bottomMargin : 2.5*Style.main.rightMargin + } + + ClickIconText { + anchors { + top : parent.top + right : parent.right + margins : .5* Style.main.rightMargin + } + iconText : Style.fa.times + text : "" + textColor : Style.main.textBlue + onClicked : root.hide() + Accessible.description : qsTr("Close dialog %1", "Click to exit modal.").arg(title.text) + } + + Text { + id: title + text : qsTr("List of errors") + font { + pointSize: Style.dialog.titleSize * Style.pt + } + anchors { + top : parent.top + topMargin : 0.5*Style.main.rightMargin + horizontalCenter : parent.horizontalCenter + } + } + + ListView { + id: errorView + anchors { + left : parent.left + right : parent.right + top : title.bottom + bottom : detailBtn.top + margins : Style.main.rightMargin + } + + clip : true + flickableDirection : Flickable.HorizontalAndVerticalFlick + contentWidth : errorView.rWall + boundsBehavior : Flickable.StopAtBounds + + ScrollBar.vertical: ScrollBar { + anchors { + right : parent.right + top : parent.top + rightMargin : Style.main.rightMargin/4 + topMargin : Style.main.rightMargin + } + width: Style.main.rightMargin/3 + Accessible.ignored: true + } + ScrollBar.horizontal: ScrollBar { + anchors { + bottom : parent.bottom + right : parent.right + bottomMargin : Style.main.rightMargin/4 + rightMargin : Style.main.rightMargin + } + height: Style.main.rightMargin/3 + Accessible.ignored: true + } + + + + property real rW1 : 150 *Style.px + property real rW2 : 150 *Style.px + property real rW3 : 100 *Style.px + property real rW4 : 150 *Style.px + property real rW5 : 550 *Style.px + property real rWall : errorView.rW1+errorView.rW2+errorView.rW3+errorView.rW4+errorView.rW5 + property real pH : .5*Style.main.rightMargin + + model : errorList + delegate : Rectangle { + width : Math.max(errorView.width, row.width) + height : row.height + + Row { + id: row + + spacing : errorView.pH + leftPadding : errorView.pH + rightPadding : errorView.pH + topPadding : errorView.pH + bottomPadding : errorView.pH + + ImportReportCell { width : errorView.rW1; text : mailSubject } + ImportReportCell { width : errorView.rW2; text : mailDate } + ImportReportCell { width : errorView.rW3; text : inputFolder } + ImportReportCell { width : errorView.rW4; text : mailFrom } + ImportReportCell { width : errorView.rW5; text : errorMessage } + } + + Rectangle { + color : Style.main.line + height : .8*Style.px + width : parent.width + anchors.left : parent.left + anchors.bottom : parent.bottom + } + } + + headerPositioning: ListView.OverlayHeader + header: Rectangle { + height : viewHeader.height + width : Math.max(errorView.width, viewHeader.width) + color : Style.accounts.backgroundExpanded + z : 2 + + Row { + id: viewHeader + + spacing : errorView.pH + leftPadding : errorView.pH + rightPadding : errorView.pH + topPadding : .5*errorView.pH + bottomPadding : .5*errorView.pH + + ImportReportCell { width : errorView.rW1 ; text : qsTr ( "SUBJECT" ); isHeader: true } + ImportReportCell { width : errorView.rW2 ; text : qsTr ( "DATE/TIME" ); isHeader: true } + ImportReportCell { width : errorView.rW3 ; text : qsTr ( "FOLDER" ); isHeader: true } + ImportReportCell { width : errorView.rW4 ; text : qsTr ( "FROM" ); isHeader: true } + ImportReportCell { width : errorView.rW5 ; text : qsTr ( "ERROR" ); isHeader: true } + } + + Rectangle { + color : Style.main.line + height : .8*Style.px + width : parent.width + anchors.left : parent.left + anchors.bottom : parent.bottom + } + } + } + + Rectangle { + anchors{ + fill : errorView + margins : -radius + } + radius : 2* Style.px + color : Style.transparent + border { + width : Style.px + color : Style.main.line + } + } + + ButtonRounded { + id: detailBtn + fa_icon : Style.fa.file_text + text : qsTr("Detailed file") + color_main : Style.dialog.textBlue + onClicked : go.importLogFileName == "" ? go.openLogs() : go.openReport() + + anchors { + bottom : parent.bottom + bottomMargin : 0.5*Style.main.rightMargin + horizontalCenter : parent.horizontalCenter + } + } + } + + + function show() { + root.visible = true + } + + function hide() { + root.visible = false + } +} diff --git a/internal/frontend/qml/ImportExportUI/ImportReportCell.qml b/internal/frontend/qml/ImportExportUI/ImportReportCell.qml new file mode 100644 index 00000000..4df5d5f3 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ImportReportCell.qml @@ -0,0 +1,67 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Import report modal +import QtQuick 2.11 +import QtQuick.Controls 2.4 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + +Rectangle { + id: root + + property alias text : cellText.text + property bool isHeader : false + property bool isHovered : false + property bool isWider : cellText.contentWidth > root.width + + width : 20*Style.px + height : cellText.height + z : root.isHovered ? 3 : 1 + color : Style.transparent + + Rectangle { + anchors { + fill : cellText + margins : -2*Style.px + } + color : root.isWider ? Style.main.background : Style.transparent + border { + color : root.isWider ? Style.main.textDisabled : Style.transparent + width : Style.px + } + } + + Text { + id: cellText + color : root.isHeader ? Style.main.textDisabled : Style.main.text + elide : root.isHovered ? Text.ElideNone : Text.ElideRight + width : root.isHovered ? cellText.contentWidth : root.width + font { + pointSize : Style.main.textSize * Style.pt + family : Style.fontawesome.name + } + } + + MouseArea { + anchors.fill : root + hoverEnabled : !root.isHeader + onEntered : { root.isHovered = true } + onExited : { root.isHovered = false } + } +} diff --git a/internal/frontend/qml/ImportExportUI/ImportSourceButton.qml b/internal/frontend/qml/ImportExportUI/ImportSourceButton.qml new file mode 100644 index 00000000..934c59cd --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ImportSourceButton.qml @@ -0,0 +1,89 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// Export dialog +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + + +Button { + id: root + + width : 200 + height : icon.height + 4*tag.height + scale : pressed ? 0.95 : 1.0 + + property string iconText : Style.fa.ban + + background: Rectangle { color: "transparent" } + + contentItem: Rectangle { + id: wrapper + color: "transparent" + + Image { + id: icon + anchors { + bottom : wrapper.bottom + bottomMargin : tag.height*2.5 + horizontalCenter : wrapper.horizontalCenter + } + fillMode : Image.PreserveAspectFit + width : Style.main.fontSize * 7 + mipmap : true + source : "images/"+iconText+".png" + + } + + Row { + spacing: Style.dialog.spacing + anchors { + bottom : wrapper.bottom + horizontalCenter : wrapper.horizontalCenter + } + + Text { + id: tag + + text : Style.fa.plus_circle + color : Qt.lighter( Style.dialog.textBlue, root.enabled ? 1.0 : 1.5) + + font { + family : Style.fontawesome.name + pointSize : Style.main.fontSize * Style.pt * 1.2 + } + } + + Text { + text : root.text + color: tag.color + + font { + family : tag.font.family + pointSize : tag.font.pointSize + weight : Font.DemiBold + underline : true + } + } + } + } +} + + diff --git a/internal/frontend/qml/ImportExportUI/ImportStructure.qml b/internal/frontend/qml/ImportExportUI/ImportStructure.qml new file mode 100644 index 00000000..da94e46f --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/ImportStructure.qml @@ -0,0 +1,149 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of import folder and their target +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + property string titleFrom + property string titleTo + property bool hasItems: true + + color : Style.transparent + + Rectangle { + anchors.fill: root + radius : Style.dialog.radiusButton + color : Style.transparent + border { + color : Style.main.line + width : 1.5*Style.dialog.borderInput + } + + + Text { // placeholder + visible: !root.hasItems + anchors.centerIn: parent + color: Style.main.textDisabled + font { + pointSize: Style.dialog.fontSize * Style.pt + } + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + text: qsTr("No emails found for this source.","todo") + } + + } + + anchors { + left : parent.left + right : parent.right + top : parent.top + bottom : parent.bottom + + leftMargin : Style.main.leftMargin + rightMargin : Style.main.leftMargin + topMargin : Style.main.topMargin + bottomMargin : Style.main.bottomMargin + } + + ListView { + id: listview + clip : true + orientation : ListView.Vertical + boundsBehavior : Flickable.StopAtBounds + model : structureExternal + cacheBuffer : 10000 + delegate : ImportDelegate { + width: root.width + } + + anchors { + top: titleBox.bottom + bottom: root.bottom + left: root.left + right: root.right + margins : Style.dialog.borderInput + bottomMargin: Style.dialog.radiusButton + } + + ScrollBar.vertical: ScrollBar { + anchors { + right: parent.right + rightMargin: Style.main.rightMargin/4 + } + width: Style.main.rightMargin/3 + Accessible.ignored: true + } + } + + Rectangle { + id: titleBox + anchors { + top: parent.top + left: parent.left + right: parent.right + } + height: Style.main.fontSize *2 + color : Style.transparent + + Text { + id: textTitleFrom + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: { + if (listview.currentIndex<0) return 0 + else return listview.currentItem.leftMargin1 + } + } + text: ""+qsTr("From:")+" " + root.titleFrom + color: Style.main.text + width: listview.currentItem === null ? 0 : (listview.currentItem.leftMargin2 - listview.currentItem.leftMargin1 - Style.dialog.spacing) + elide: Text.ElideMiddle + } + + Text { + id: textTitleTo + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + leftMargin: { + if (listview.currentIndex<0) return root.width/3 + else return listview.currentItem.leftMargin2 + } + } + text: ""+qsTr("To:")+" " + root.titleTo + color: Style.main.text + } + } + + Rectangle { + id: line + anchors { + left : titleBox.left + right : titleBox.right + top : titleBox.bottom + } + height: Style.dialog.borderInput + color: Style.main.line + } +} diff --git a/internal/frontend/qml/ImportExportUI/InlineDateRange.qml b/internal/frontend/qml/ImportExportUI/InlineDateRange.qml new file mode 100644 index 00000000..017b0eb4 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/InlineDateRange.qml @@ -0,0 +1,128 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for date range +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + +Row { + id: dateRange + + property var structure : structureExternal + property string sourceID : structureExternal.getID ( -1 ) + + property alias allDates : allDatesBox.checked + property alias inputDateFrom : inputDateFrom + property alias inputDateTo : inputDateTo + + property alias labelWidth: label.width + + function setRange() {common.setRange()} + function applyRange() {common.applyRange()} + + DateRangeFunctions {id:common} + + spacing: Style.dialog.spacing*2 + + Text { + id: label + anchors.verticalCenter: parent.verticalCenter + text : qsTr("Date range") + font { + bold: true + family: Style.fontawesome.name + pointSize: Style.main.fontSize * Style.pt + } + color: Style.main.text + } + + DateInput { + id: inputDateFrom + label: "" + anchors.verticalCenter: parent.verticalCenter + currentDate: new Date(0) // default epoch start + maxDate: inputDateTo.currentDate + } + + Text { + text : Style.fa.arrows_h + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + color: Style.main.text + font.family: Style.fontawesome.name + } + + DateInput { + id: inputDateTo + label: "" + anchors.verticalCenter: parent.verticalCenter + currentDate: new Date() // default now + minDate: inputDateFrom.currentDate + } + + CheckBoxLabel { + id: allDatesBox + text : qsTr("All dates") + anchors.verticalCenter : parent.verticalCenter + checkedSymbol : Style.fa.toggle_on + uncheckedSymbol : Style.fa.toggle_off + uncheckedColor : Style.main.textDisabled + symbolPointSize : Style.dialog.iconSize * Style.pt * 1.1 + spacing : Style.dialog.spacing*2 + + TextMetrics { + id: metrics + text: allDatesBox.checkedSymbol + font { + family: Style.fontawesome.name + pointSize: allDatesBox.symbolPointSize + } + } + + + Rectangle { + color: allDatesBox.checked ? dotBackground.color : Style.exporting.sliderBackground + width: metrics.width*0.9 + height: metrics.height*0.6 + radius: height/2 + z: -1 + + anchors { + left: allDatesBox.left + verticalCenter: allDatesBox.verticalCenter + leftMargin: 0.05 * metrics.width + } + + Rectangle { + id: dotBackground + color : Style.exporting.background + height : parent.height + width : height + radius : height/2 + anchors { + left : parent.left + verticalCenter : parent.verticalCenter + } + + } + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml b/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml new file mode 100644 index 00000000..e7ee4680 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/InlineLabelSelect.qml @@ -0,0 +1,227 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// input for date range +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + + +Row { + id: root + spacing: Style.dialog.spacing + + property alias labelWidth : label.width + + property string labelName : "" + property string labelColor : "" + property alias labelSelected : masterLabelCheckbox.checked + + Text { + id: label + text : qsTr("Add import label") + font { + bold: true + family: Style.fontawesome.name + pointSize: Style.main.fontSize * Style.pt + } + color: Style.main.text + anchors.verticalCenter: parent.verticalCenter + } + + InfoToolTip { + info: qsTr( "When master import lablel is selected then all imported email will have this label.", "Tooltip text for master import label") + anchors.verticalCenter: parent.verticalCenter + } + + CheckBoxLabel { + id: masterLabelCheckbox + text : "" + anchors.verticalCenter : parent.verticalCenter + checkedSymbol : Style.fa.toggle_on + uncheckedSymbol : Style.fa.toggle_off + uncheckedColor : Style.main.textDisabled + symbolPointSize : Style.dialog.iconSize * Style.pt * 1.1 + spacing : Style.dialog.spacing*2 + + TextMetrics { + id: metrics + text: masterLabelCheckbox.checkedSymbol + font { + family: Style.fontawesome.name + pointSize: masterLabelCheckbox.symbolPointSize + } + } + + + Rectangle { + color: parent.checked ? dotBackground.color : Style.exporting.sliderBackground + width: metrics.width*0.9 + height: metrics.height*0.6 + radius: height/2 + z: -1 + + anchors { + left: masterLabelCheckbox.left + verticalCenter: masterLabelCheckbox.verticalCenter + leftMargin: 0.05 * metrics.width + } + + Rectangle { + id: dotBackground + color : Style.exporting.background + height : parent.height + width : height + radius : height/2 + anchors { + left : parent.left + verticalCenter : parent.verticalCenter + } + + } + } + } + + Rectangle { + // label + color : Style.transparent + radius : Style.dialog.radiusButton + border { + color : Style.dialog.line + width : Style.dialog.borderInput + } + anchors.verticalCenter : parent.verticalCenter + + scale: area.pressed ? 0.95 : 1 + + width: content.width + height: content.height + + + Row { + id: content + + spacing : Style.dialog.spacing + padding : Style.dialog.spacing + + anchors.verticalCenter: parent.verticalCenter + + // label icon color + Text { + text: Style.fa.tag + color: root.labelSelected ? root.labelColor : Style.dialog.line + anchors.verticalCenter: parent.verticalCenter + font { + family: Style.fontawesome.name + pointSize: Style.main.fontSize * Style.pt + } + } + + TextMetrics { + id:labelMetrics + text: root.labelName + elide: Text.ElideRight + elideWidth:gui.winMain.width*0.303 + + font { + pointSize: Style.main.fontSize * Style.pt + family: Style.fontawesome.name + } + } + + // label text + Text { + text: labelMetrics.elidedText + color: root.labelSelected ? Style.dialog.text : Style.dialog.line + font: labelMetrics.font + anchors.verticalCenter: parent.verticalCenter + } + + // edit icon + Text { + text: Style.fa.edit + color: root.labelSelected ? Style.main.textBlue : Style.dialog.line + anchors.verticalCenter: parent.verticalCenter + font { + family: Style.fontawesome.name + pointSize: Style.main.fontSize * Style.pt + } + } + } + + MouseArea { + id: area + anchors.fill: parent + enabled: root.labelSelected + onClicked : { + if (!root.labelSelected) return + // NOTE: "createLater" is hack + winMain.popupFolderEdit.show(root.labelName, "createLater", root.labelColor, gui.enums.folderTypeLabel, "") + } + } + } + + function reset(){ + labelColor = go.leastUsedColor() + labelName = qsTr("Imported", "default name of global label followed by date") + " " + gui.niceDateTime() + labelSelected=true + } + + Connections { + target: winMain.popupFolderEdit + + onEdited : { + if (newName!="") root.labelName = newName + if (newColor!="") root.labelColor = newColor + } + } + + + /* + SelectLabelsMenu { + id: labelMenu + width : winMain.width/5 + sourceID : root.sourceID + selectedIDs : root.structure.getTargetLabelIDs(root.sourceID) + anchors.verticalCenter: parent.verticalCenter + } + + LabelIconList { + id: iconList + selectedIDs : root.structure.getTargetLabelIDs(root.sourceID) + anchors.verticalCenter: parent.verticalCenter + } + + + Connections { + target: structureExternal + onDataChanged: { + iconList.selectedIDs = root.structure.getTargetLabelIDs(root.sourceID) + labelMenu.selectedIDs = root.structure.getTargetLabelIDs(root.sourceID) + } + } + + Connections { + target: structurePM + onDataChanged:{ + iconList.selectedIDs = root.structure.getTargetLabelIDs(root.sourceID) + labelMenu.selectedIDs = root.structure.getTargetLabelIDs(root.sourceID) + } + } + */ +} diff --git a/internal/frontend/qml/ImportExportUI/LabelIconList.qml b/internal/frontend/qml/ImportExportUI/LabelIconList.qml new file mode 100644 index 00000000..bf6ab759 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/LabelIconList.qml @@ -0,0 +1,96 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of icons for selected folders +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import QtQml.Models 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Rectangle { + id: root + width: Style.main.fontSize * 2 + height: metrics.height + property string selectedIDs : "" + color: "transparent" + + + + DelegateModel { + id: selectedLabels + filterOnGroup: "selected" + groups: DelegateModelGroup { + id: selected + name: "selected" + includeByDefault: true + } + model : structurePM + delegate : Text { + text : metrics.text + font : metrics.font + color : folderColor===undefined ? "#000": folderColor + } + } + + function updateFilter() { + var selected = root.selectedIDs.split(";") + var rowCount = selectedLabels.items.count + //console.log(" log ::", root.selectedIDs, rowCount, selectedLabels.model) + // filter + for (var iItem = 0; iItem < rowCount; iItem++) { + var entry = selectedLabels.items.get(iItem); + //console.log(" log filter ", iItem, rowCount, entry.model.folderId, entry.model.folderType, selected[iSel], entry.inSelected ) + for (var iSel in selected) { + entry.inSelected = ( + entry.model.folderType == gui.enums.folderTypeLabel && + entry.model.folderId == selected[iSel] + ) + if (entry.inSelected) break // found match, skip rest + } + } + } + + TextMetrics { + id: metrics + text: Style.fa.tag + font { + pointSize: Style.main.fontSize * Style.pt + family: Style.fontawesome.name + } + } + + Row { + anchors.left : root.left + spacing : { + var n = Math.max(2,selectedLabels.count) + var tagWidth = Math.max(1.0,metrics.width) + var space = Math.min(1*Style.px, (root.width - n*tagWidth)/(n-1)) // not more than 1px + space = Math.max(space,-tagWidth) // not less than tag width + return space + } + + Repeater { + model: selectedLabels + } + } + + Component.onCompleted: root.updateFilter() + onSelectedIDsChanged: root.updateFilter() + Connections { target: structurePM; onDataChanged:root.updateFilter() } +} + diff --git a/internal/frontend/qml/ImportExportUI/MainWindow.qml b/internal/frontend/qml/ImportExportUI/MainWindow.qml new file mode 100644 index 00000000..2531c803 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/MainWindow.qml @@ -0,0 +1,473 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// This is main window + +import QtQuick 2.8 +import QtQuick.Window 2.2 +import QtQuick.Controls 2.1 +import QtQuick.Layouts 1.3 +import ImportExportUI 1.0 +import ProtonUI 1.0 + + +// Main Window +Window { + id : root + property alias tabbar : tabbar + property alias viewContent : viewContent + property alias viewAccount : viewAccount + property alias dialogAddUser : dialogAddUser + property alias dialogGlobal : dialogGlobal + property alias dialogCredits : dialogCredits + property alias dialogVersionInfo : dialogVersionInfo + property alias dialogUpdate : dialogUpdate + property alias popupMessage : popupMessage + property alias popupFolderEdit : popupFolderEdit + property alias updateState : infoBar.state + property alias dialogExport : dialogExport + property alias dialogImport : dialogImport + property alias addAccountTip : addAccountTip + property int heightContent : height-titleBar.height + + property real innerWindowBorder : go.goos=="darwin" ? 0 : Style.main.border + + // main window appearance + width : Style.main.width + height : Style.main.height + flags : go.goos=="darwin" ? Qt.Window : Qt.Window | Qt.FramelessWindowHint + color: go.goos=="windows" ? Style.main.background : Style.transparent + title: go.programTitle + + minimumWidth : Style.main.width + minimumHeight : Style.main.height + + property bool isOutdateVersion : root.updateState == "forceUpgrade" + + property bool activeContent : + !dialogAddUser .visible && + !dialogCredits .visible && + !dialogVersionInfo .visible && + !dialogGlobal .visible && + !dialogUpdate .visible && + !dialogImport .visible && + !dialogExport .visible && + !popupFolderEdit .visible && + !popupMessage .visible + + Accessible.role: Accessible.Grouping + Accessible.description: qsTr("Window %1").arg(title) + Accessible.name: Accessible.description + + WindowTitleBar { + id: titleBar + window: root + visible: go.goos!="darwin" + } + + Rectangle { + anchors { + top : titleBar.bottom + left : parent.left + right : parent.right + bottom : parent.bottom + } + color: Style.title.background + } + + InformationBar { + id: infoBar + anchors { + left : parent.left + right : parent.right + top : titleBar.bottom + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + } + + TabLabels { + id: tabbar + currentIndex : 0 + enabled: root.activeContent + anchors { + top : infoBar.bottom + right : parent.right + left : parent.left + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + model: [ + { "title" : qsTr("Import/Export" , "title of tab that shows account list" ), "iconText": Style.fa.home }, + { "title" : qsTr("Settings" , "title of tab that allows user to change settings" ), "iconText": Style.fa.cogs }, + { "title" : qsTr("Help" , "title of tab that shows the help menu" ), "iconText": Style.fa.life_ring } + ] + } + + // Content of tabs + StackLayout { + id: viewContent + enabled: root.activeContent + // dimensions + anchors { + left : parent.left + right : parent.right + top : tabbar.bottom + bottom : parent.bottom + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + bottomMargin: innerWindowBorder + } + // attributes + currentIndex : { return root.tabbar.currentIndex} + clip : true + // content + AccountView { + id : viewAccount + onAddAccount : dialogAddUser.show() + model : accountsModel + hasFooter : false + delegate : AccountDelegate { + row_width : viewContent.width + } + } + SettingsView { id: viewSettings; } + HelpView { id: viewHelp; } + } + + + // Bubble prevent action + Rectangle { + anchors { + left: parent.left + right: parent.right + top: titleBar.bottom + bottom: parent.bottom + } + visible: bubbleNote.visible + color: "#aa222222" + MouseArea { + anchors.fill: parent + hoverEnabled: true + } + } + BubbleNote { + id : bubbleNote + visible : false + Component.onCompleted : { + bubbleNote.place(0) + } + } + + BubbleNote { + id:addAccountTip + anchors.topMargin: viewAccount.separatorNoAccount - 2*Style.main.fontSize + text : qsTr("Click here to start", "on first launch, this is displayed above the Add Account button to tell the user what to do first") + state: (go.isFirstStart && viewAccount.numAccounts==0 && root.viewContent.currentIndex==0) ? "Visible" : "Invisible" + bubbleColor: Style.main.textBlue + + Component.onCompleted : { + addAccountTip.place(-1) + } + enabled: false + + states: [ + State { + name: "Visible" + // hack: opacity 100% makes buttons dialog windows quit wrong color + PropertyChanges{target: addAccountTip; opacity: 0.999; visible: true} + }, + State { + name: "Invisible" + PropertyChanges{target: addAccountTip; opacity: 0.0; visible: false} + } + ] + + transitions: [ + Transition { + from: "Visible" + to: "Invisible" + + SequentialAnimation{ + NumberAnimation { + target: addAccountTip + property: "opacity" + duration: 0 + easing.type: Easing.InOutQuad + } + NumberAnimation { + target: addAccountTip + property: "visible" + duration: 0 + } + } + }, + Transition { + from: "Invisible" + to: "Visible" + SequentialAnimation{ + NumberAnimation { + target: addAccountTip + property: "visible" + duration: 300 + } + NumberAnimation { + target: addAccountTip + property: "opacity" + duration: 500 + easing.type: Easing.InOutQuad + } + } + } + ] + } + + + // Dialogs + + DialogAddUser { + id: dialogAddUser + + anchors { + top : infoBar.bottom + bottomMargin: innerWindowBorder + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + + onCreateAccount: Qt.openUrlExternally("https://protonmail.com/signup") + } + + DialogUpdate { + id: dialogUpdate + + title: root.isOutdateVersion ? + qsTr("%1 is outdated", "title of outdate dialog").arg(go.programTitle): + qsTr("%1 update to %2", "title of update dialog").arg(go.programTitle).arg(go.newversion) + introductionText: { + if (root.isOutdateVersion) { + if (go.goos=="linux") { + return qsTr('You are using an outdated version of our software.
+ Please dowload and install the latest version to continue using %1.

+ %2', + "Message for force-update in Linux").arg(go.programTitle).arg(go.landingPage) + } else { + return qsTr('You are using an outdated version of our software.
+ Please dowload and install the latest version to continue using %1.

+ You can continue with update or download and install the new version manually from

+ %2', + "Message for force-update in Win/Mac").arg(go.programTitle).arg(go.landingPage) + } + } else { + if (go.goos=="linux") { + return qsTr('New version of %1 is available.
+ Check release notes to learn what is new in %3.
+ Use your package manager to update or download and install new version manually from

+ %4', + "Message for update in Linux").arg(go.programTitle).arg("releaseNotes").arg(go.newversion).arg(go.landingPage) + } else { + return qsTr('New version of %1 is available.
+ Check release notes to learn what is new in %3.
+ You can continue with update or download and install new version manually from

+ %4', + "Message for update in Win/Mac").arg(go.programTitle).arg("releaseNotes").arg(go.newversion).arg(go.landingPage) + } + } + } + } + + + DialogExport { + id: dialogExport + anchors { + top : infoBar.bottom + bottomMargin: innerWindowBorder + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + + } + + DialogImport { + id: dialogImport + anchors { + top : infoBar.bottom + bottomMargin: innerWindowBorder + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + + } + + Dialog { + id: dialogCredits + anchors { + top : infoBar.bottom + bottomMargin: innerWindowBorder + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + + title: qsTr("Credits", "title for list of credited libraries") + + Credits { } + } + + Dialog { + id: dialogVersionInfo + anchors { + top : infoBar.bottom + bottomMargin: innerWindowBorder + leftMargin: innerWindowBorder + rightMargin: innerWindowBorder + } + property bool checkVersion : false + title: qsTr("Information about", "title of release notes page") + " v" + go.newversion + VersionInfo { } + onShow : { + // Hide information bar with olde version + if ( infoBar.state=="oldVersion" ) { + infoBar.state="upToDate" + dialogVersionInfo.checkVersion = true + } + } + onHide : { + // Reload current version based on online status + if (dialogVersionInfo.checkVersion) go.runCheckVersion(false) + dialogVersionInfo.checkVersion = false + } + } + + DialogYesNo { + id: dialogGlobal + question : "" + answer : "" + z: 100 + } + + PopupEditFolder { + id: popupFolderEdit + anchors { + left: parent.left + right: parent.right + top: infoBar.bottom + bottom: parent.bottom + } + } + + // Popup + PopupMessage { + id: popupMessage + anchors { + left : parent.left + right : parent.right + top : infoBar.bottom + bottom : parent.bottom + } + + onClickedNo: popupMessage.hide() + onClickedOkay: popupMessage.hide() + onClickedYes: { + if (popupMessage.message == gui.areYouSureYouWantToQuit) Qt.quit() + } + } + + // resize + MouseArea { // bottom + id: resizeBottom + property int diff: 0 + anchors { + bottom : parent.bottom + left : parent.left + right : parent.right + } + cursorShape: Qt.SizeVerCursor + height: Style.main.fontSize + onPressed: { + var globPos = mapToGlobal(mouse.x, mouse.y) + resizeBottom.diff = root.height + resizeBottom.diff -= globPos.y + } + onMouseYChanged : { + var globPos = mapToGlobal(mouse.x, mouse.y) + root.height = Math.max(root.minimumHeight, globPos.y + resizeBottom.diff) + } + } + + MouseArea { // right + id: resizeRight + property int diff: 0 + anchors { + top : titleBar.bottom + bottom : parent.bottom + right : parent.right + } + cursorShape: Qt.SizeHorCursor + width: Style.main.fontSize/2 + onPressed: { + var globPos = mapToGlobal(mouse.x, mouse.y) + resizeRight.diff = root.width + resizeRight.diff -= globPos.x + } + onMouseXChanged : { + var globPos = mapToGlobal(mouse.x, mouse.y) + root.width = Math.max(root.minimumWidth, globPos.x + resizeRight.diff) + } + } + + function showAndRise(){ + go.loadAccounts() + root.show() + root.raise() + if (!root.active) { + root.requestActivate() + } + } + + // Toggle window + function toggle() { + go.loadAccounts() + if (root.visible) { + if (!root.active) { + root.raise() + root.requestActivate() + } else { + root.hide() + } + } else { + root.show() + root.raise() + } + } + + onClosing : { + close.accepted=false + if ( + (dialogImport.visible && dialogImport.currentIndex == 4 && go.progress!=1) || + (dialogExport.visible && dialogExport.currentIndex == 2 && go.progress!=1) + ) { + popupMessage.buttonOkay .visible = false + popupMessage.buttonNo .visible = true + popupMessage.buttonYes .visible = true + popupMessage.show ( gui.areYouSureYouWantToQuit ) + return + } + + close.accepted=true + go.processFinished() + } +} diff --git a/internal/frontend/qml/ImportExportUI/OutputFormat.qml b/internal/frontend/qml/ImportExportUI/OutputFormat.qml new file mode 100644 index 00000000..3a87ea27 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/OutputFormat.qml @@ -0,0 +1,84 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Column { + spacing: Style.dialog.spacing + property string checkedText : group.checkedButton.text + + Text { + id: formatLabel + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: true + } + color: Style.dialog.text + text: qsTr("Select format of exported email:") + + InfoToolTip { + info: qsTr("MBOX exports one file for each folder", "todo") + "\n" + qsTr("EML exports one file for each email", "todo") + anchors { + left: parent.right + leftMargin: Style.dialog.spacing + verticalCenter: parent.verticalCenter + } + } + } + + Row { + spacing : Style.main.leftMargin + ButtonGroup { + id: group + } + + Repeater { + model: [ "MBOX", "EML" ] + delegate : RadioButton { + id: radioDelegate + checked: modelData=="MBOX" + width: 5*Style.dialog.fontSize // hack due to bold + text: modelData + ButtonGroup.group: group + spacing: Style.main.spacing + indicator: Text { + text : radioDelegate.checked ? Style.fa.check_circle : Style.fa.circle_o + color : radioDelegate.checked ? Style.main.textBlue : Style.main.textInactive + font { + pointSize: Style.dialog.iconSize * Style.pt + family: Style.fontawesome.name + } + anchors.verticalCenter: parent.verticalCenter + } + contentItem: Text { + text: radioDelegate.text + color: Style.main.text + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: checked + } + horizontalAlignment : Text.AlignHCenter + verticalAlignment : Text.AlignVCenter + leftPadding: Style.dialog.iconSize + } + } + } + } +} diff --git a/internal/frontend/qml/ImportExportUI/PopupEditFolder.qml b/internal/frontend/qml/ImportExportUI/PopupEditFolder.qml new file mode 100644 index 00000000..7af711c4 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/PopupEditFolder.qml @@ -0,0 +1,311 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// popup to edit folders or labels +import QtQuick 2.8 +import QtQuick.Controls 2.1 +import ImportExportUI 1.0 +import ProtonUI 1.0 + + +Rectangle { + id: root + visible: false + color: "#aa223344" + + property string folderType : gui.enums.folderTypeFolder + property bool isFolder : folderType == gui.enums.folderTypeFolder + property bool isNew : currentId == "" + property bool isCreateLater : currentId == "createLater" // NOTE: "createLater" is hack because folder id should be base64 string + + property string currentName : "" + property string currentId : "" + property string currentColor : "" + + property string sourceID : "" + property string selectedColor : colorList[0] + + property color textColor : Style.main.background + property color backColor : Style.bubble.paneBackground + + signal edited(string newName, string newColor) + + + + + property var colorList : [ "#7272a7", "#8989ac", "#cf5858", "#cf7e7e", "#c26cc7", "#c793ca", "#7569d1", "#9b94d1", "#69a9d1", "#a8c4d5", "#5ec7b7", "#97c9c1", "#72bb75", "#9db99f", "#c3d261", "#c6cd97", "#e6c04c", "#e7d292", "#e6984c", "#dfb286" ] + + MouseArea { // prevent action below aka modal: true + anchors.fill: parent + hoverEnabled: true + } + + Rectangle { + id:background + + anchors { + fill: root + leftMargin: winMain.width/6 + topMargin: winMain.height/6 + rightMargin: anchors.leftMargin + bottomMargin: anchors.topMargin + } + + color: backColor + radius: Style.errorDialog.radius + } + + + Column { // content + anchors { + top : background.top + horizontalCenter : background.horizontalCenter + } + + topPadding : Style.main.topMargin + bottomPadding : topPadding + spacing : (background.height - title.height - inputField.height - view.height - buttonRow.height - topPadding - bottomPadding) / children.length + + Text { + id: title + + font.pointSize: Style.dialog.titleSize * Style.pt + color: textColor + + text: { + if ( root.isFolder && root.isNew ) return qsTr ( "Create new folder" ) + if ( !root.isFolder && root.isNew ) return qsTr ( "Create new label" ) + if ( root.isFolder && !root.isNew ) return qsTr ( "Edit folder %1" ) .arg( root.currentName ) + if ( !root.isFolder && !root.isNew ) return qsTr ( "Edit label %1" ) .arg( root.currentName ) + } + + width : parent.width + elide : Text.ElideRight + + horizontalAlignment : Text.AlignHCenter + + Rectangle { + anchors { + top: parent.bottom + topMargin: Style.dialog.spacing + horizontalCenter: parent.horizontalCenter + } + color: textColor + height: Style.main.borderInput + } + } + + TextField { + id: inputField + + anchors { + horizontalCenter: parent.horizontalCenter + } + + width : parent.width + height : Style.dialog.button + rightPadding : Style.dialog.spacing + leftPadding : height + rightPadding + bottomPadding : rightPadding + topPadding : rightPadding + selectByMouse : true + color : textColor + font.pointSize : Style.dialog.fontSize * Style.pt + + background: Rectangle { + color: backColor + border { + color: textColor + width: Style.dialog.borderInput + } + + radius : Style.dialog.radiusButton + + Text { + anchors { + left: parent.left + verticalCenter: parent.verticalCenter + } + + font { + family: Style.fontawesome.name + pointSize: Style.dialog.titleSize * Style.pt + } + + text : folderType == gui.enums.folderTypeFolder ? Style.fa.folder : Style.fa.tag + color : root.selectedColor + width : parent.height + horizontalAlignment: Text.AlignHCenter + } + + Rectangle { + anchors { + left: parent.left + top: parent.top + leftMargin: parent.height + } + width: parent.border.width/2 + height: parent.height + } + } + } + + + GridView { + id: view + + anchors { + horizontalCenter: parent.horizontalCenter + } + + model : colorList + cellWidth : 2*Style.dialog.titleSize + cellHeight : cellWidth + width : 10*cellWidth + height : 2*cellHeight + + delegate: Rectangle { + width: view.cellWidth*0.8 + height: width + radius: width/2 + color: modelData + + border { + color: indicator.visible ? textColor : modelData + width: 2*Style.px + } + + Text { + id: indicator + anchors.centerIn : parent + text: Style.fa.check + color: textColor + font { + family: Style.fontawesome.name + pointSize: Style.dialog.titleSize * Style.pt + } + visible: modelData == root.selectedColor + } + + MouseArea { + anchors.fill: parent + onClicked : { + root.selectedColor = modelData + } + } + } + } + + Row { + id: buttonRow + + anchors { + horizontalCenter: parent.horizontalCenter + } + + spacing: Style.main.leftMargin + + ButtonRounded { + text: "Cancel" + color_main : textColor + onClicked :{ + root.hide() + } + } + + ButtonRounded { + text: "Okay" + color_main: Style.dialog.background + color_minor: Style.dialog.textBlue + isOpaque: true + onClicked :{ + root.okay() + } + } + } + } + + function hide() { + root.visible=false + root.currentId = "" + root.currentName = "" + root.currentColor = "" + root.folderType = "" + root.sourceID = "" + inputField.text = "" + } + + function show(currentName, currentId, currentColor, folderType, sourceID) { + root.currentId = currentId + root.currentName = currentName + root.currentColor = currentColor=="" ? go.leastUsedColor() : currentColor + root.selectedColor = root.currentColor + root.folderType = folderType + root.sourceID = sourceID + + inputField.text = currentName + root.visible=true + //console.log(title.text , root.currentName, root.currentId, root.currentColor, root.folderType, root.sourceID) + } + + function okay() { + // check inpupts + if (inputField.text == "") { + go.notifyError(gui.enums.errFillFolderName) + return + } + if (colorList.indexOf(root.selectedColor)<0) { + go.notifyError(gui.enums.errSelectFolderColor) + return + } + var isLabel = root.folderType == gui.enums.folderTypeLabel + if (!isLabel && !root.isFolder){ + console.log("Unknown folder type: ", root.folderType) + go.notifyError(gui.enums.errUpdateLabelFailed) + root.hide() + return + } + + if (winMain.dialogImport.address == "") { + console.log("Unknown address", winMain.dialogImport.address) + go.onNotifyError(gui.enums.errUpdateLabelFailed) + root.hide() + } + + if (root.isCreateLater) { + root.edited(inputField.text, root.selectedColor) + root.hide() + return + } + + + // TODO send request (as timer) + if (root.isNew) { + var isOK = go.createLabelOrFolder(winMain.dialogImport.address, inputField.text, root.selectedColor, isLabel, root.sourceID) + if (isOK) { + root.hide() + } + } else { + // TODO: check there was some change + go.updateLabelOrFolder(winMain.dialogImport.address, root.currentId, inputField.text, root.selectedColor) + } + + // waiting for finish + // TODO: waiting wheel of doom + // TODO: on close add source to sourceID + } +} diff --git a/internal/frontend/qml/ImportExportUI/SelectFolderMenu.qml b/internal/frontend/qml/ImportExportUI/SelectFolderMenu.qml new file mode 100644 index 00000000..5476aef4 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/SelectFolderMenu.qml @@ -0,0 +1,362 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// This is global combo box which can be adjusted to choose folder target, folder label or global label +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +ComboBox { + id: root + //fixme rounded + height: Style.main.fontSize*2 //fixme + property string folderType: gui.enums.folderTypeFolder + property string selectedIDs + property string sourceID + property bool isFolderType: root.folderType == gui.enums.folderTypeFolder + property bool hasTarget: root.selectedIDs != "" + property bool below: true + + signal doNotImport() + signal importToFolder(string newTargetID) + + leftPadding: Style.dialog.spacing + + onDownChanged : { + if (root.down) view.model.updateFilter() + root.below = popup.y>0 + } + + contentItem : Text { + id: boxText + verticalAlignment: Text.AlignVCenter + font { + family: Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + bold: root.down + } + elide: Text.ElideRight + textFormat: Text.StyledText + + text : root.displayText + color: !root.enabled ? Style.main.textDisabled : ( root.down ? Style.main.background : Style.main.text ) + } + + displayText: { + //console.trace() + //console.log("updatebox", view.currentIndex, root.hasTarget, root.selectedIDs, root.sourceID, root.folderType) + if (!root.hasTarget) { + if (root.isFolderType) return qsTr("Do not import") + return qsTr("No labels selected") + } + if (!root.isFolderType) return Style.fa.tags + " " + qsTr("Add/Remove labels") + + // We know here that it has a target and this is folder dropdown so we must find the first folder + var selSplit = root.selectedIDs.split(";") + for (var selIndex in selSplit) { + var selectedID = selSplit[selIndex] + var selectedType = structurePM.getType(selectedID) + if (selectedType == gui.enums.folderTypeLabel) continue; // skip type::labele + var selectedName = structurePM.getName(selectedID) + if (selectedName == "") continue; // empty name seems like wrong ID + var icon = gui.folderIcon(selectedName, selectedType) + if (selectedType == gui.enums.folderTypeSystem) { + return icon + " " + selectedName + } + var iconColor = structurePM.getColor(selectedID) + return ''+ icon + " " + selectedName + } + return "" + } + + + background : RoundedRectangle { + fillColor : root.down ? Style.main.textBlue : Style.transparent + strokeColor : root.down ? fillColor : Style.main.line + radiusTopLeft : root.down && !root.below ? 0 : Style.dialog.radiusButton + radiusBottomLeft : root.down && root.below ? 0 : Style.dialog.radiusButton + radiusTopRight : radiusTopLeft + radiusBottomRight : radiusBottomLeft + + MouseArea { + anchors.fill: parent + onClicked : { + if (root.down) root.popup.close() + else root.popup.open() + } + } + } + + indicator : Text { + text: (root.down && root.below) || (!root.down && !root.below) ? Style.fa.chevron_up : Style.fa.chevron_down + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + rightMargin: Style.dialog.spacing + } + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + color: root.enabled && !root.down ? Style.main.textBlue : root.contentItem.color + } + + // Popup objects + delegate: Rectangle { + id: thisDelegate + + height : Style.main.fontSize * 2 + width : selectNone.width + + property bool isHovered: area.containsMouse + + color: isHovered ? root.popup.hoverColor : root.popup.backColor + + + property bool isSelected : { + var selected = root.selectedIDs.split(";") + for (var iSel in selected) { + var sel = selected[iSel] + if (folderId == sel){ + return true + } + } + return false + } + + Text { + id: targetIcon + text: gui.folderIcon(folderName,folderType) + color : folderType != gui.enums.folderTypeSystem ? folderColor : root.popup.textColor + anchors { + verticalCenter: parent.verticalCenter + left: parent.left + leftMargin: root.leftPadding + } + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + } + + Text { + id: targetName + + anchors { + verticalCenter: parent.verticalCenter + left: targetIcon.right + right: parent.right + leftMargin: Style.dialog.spacing + rightMargin: Style.dialog.spacing + } + + text: folderName + color : root.popup.textColor + elide: Text.ElideRight + + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + } + + Text { + id: targetIndicator + anchors { + right: parent.right + verticalCenter: parent.verticalCenter + } + + text : thisDelegate.isSelected ? Style.fa.check_square : Style.fa.square_o + visible : thisDelegate.isSelected || !root.isFolderType + color : root.popup.textColor + font { + family : Style.fontawesome.name + pointSize : Style.dialog.fontSize * Style.pt + } + } + + Rectangle { + id: line + anchors { + bottom : parent.bottom + left : parent.left + right : parent.right + } + height : Style.main.lineWidth + color : Style.main.line + } + + MouseArea { + id: area + anchors.fill: parent + + onClicked: { + //console.log(" click delegate") + if (root.isFolderType) { // don't update if selected + if (!thisDelegate.isSelected) { + root.importToFolder(folderId) + } + root.popup.close() + } + if (root.folderType==gui.enums.folderTypeLabel) { + if (thisDelegate.isSelected) { + structureExternal.removeTargetLabelID(sourceID,folderId) + } else { + structureExternal.addTargetLabelID(sourceID,folderId) + } + } + } + hoverEnabled: true + } + } + + popup : Popup { + y: root.height + width: root.width + modal: true + closePolicy: Popup.CloseOnPressOutside | Popup.CloseOnEscape + padding: Style.dialog.spacing + + property var textColor : Style.main.background + property var backColor : Style.main.text + property var hoverColor : Style.main.textBlue + + contentItem : Column { + // header + Rectangle { + id: selectNone + width: root.popup.width - 2*root.popup.padding + //height: root.isFolderType ? 2* Style.main.fontSize : 0 + height: 2*Style.main.fontSize + color: area.containsMouse ? root.popup.hoverColor : root.popup.backColor + visible : root.isFolderType + + Text { + anchors { + left : parent.left + leftMargin : Style.dialog.spacing + verticalCenter : parent.verticalCenter + } + text: root.isFolderType ? qsTr("Do not import") : "" + color: root.popup.textColor + font { + pointSize: Style.dialog.fontSize * Style.pt + bold: true + } + } + + Rectangle { + id: line + anchors { + bottom : parent.bottom + left : parent.left + right : parent.right + } + height : Style.dialog.borderInput + color : Style.main.line + } + + MouseArea { + id: area + anchors.fill: parent + onClicked: { + //console.log(" click no set") + root.doNotImport() + root.popup.close() + } + hoverEnabled: true + } + } + + // scroll area + Rectangle { + width: selectNone.width + height: winMain.height/4 + color: root.popup.backColor + + ListView { + id: view + + clip : true + anchors.fill : parent + + section.property : "sectionName" + section.delegate : Text{text: sectionName} + + model : FilterStructure { + filterOnGroup : root.folderType + delegate : root.delegate + } + } + } + + // footer + Rectangle { + id: addFolderOrLabel + width: selectNone.width + height: addButton.height + 3*Style.dialog.spacing + color: root.popup.backColor + + Rectangle { + anchors { + top : parent.top + left : parent.left + right : parent.right + } + height : Style.dialog.borderInput + color : Style.main.line + } + + ButtonRounded { + id: addButton + anchors.centerIn: addFolderOrLabel + width: parent.width * 0.681 + + fa_icon : Style.fa.plus_circle + text : root.isFolderType ? qsTr("Create new folder") : qsTr("Create new label") + color_main : root.popup.textColor + } + + MouseArea { + anchors.fill : parent + + onClicked : { + //console.log("click", addButton.text) + var newName = "" + if ( typeof folderName !== 'undefined' && !structurePM.hasFolderWithName (folderName) ) { + newName = folderName + } + winMain.popupFolderEdit.show(newName, "", "", root.folderType, sourceID) + root.popup.close() + } + } + } + } + + background : RoundedRectangle { + strokeColor : root.popup.backColor + fillColor : root.popup.backColor + radiusTopLeft : root.below ? 0 : Style.dialog.radiusButton + radiusBottomLeft : !root.below ? 0 : Style.dialog.radiusButton + radiusTopRight : radiusTopLeft + radiusBottomRight : radiusBottomLeft + } + } +} + diff --git a/internal/frontend/qml/ImportExportUI/SelectLabelsMenu.qml b/internal/frontend/qml/ImportExportUI/SelectLabelsMenu.qml new file mode 100644 index 00000000..dec1d2d7 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/SelectLabelsMenu.qml @@ -0,0 +1,29 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List of import folder and their target +import QtQuick 2.8 +import QtQuick.Controls 2.2 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +SelectFolderMenu { + id: root + folderType: gui.enums.folderTypeLabel +} + + diff --git a/internal/frontend/qml/ImportExportUI/SettingsView.qml b/internal/frontend/qml/ImportExportUI/SettingsView.qml new file mode 100644 index 00000000..ced1887e --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/SettingsView.qml @@ -0,0 +1,148 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// List the settings + +import QtQuick 2.8 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Item { + id: root + + // must have wrapper + Rectangle { + id: wrapper + anchors.centerIn: parent + width: parent.width + height: parent.height + color: Style.main.background + + // content + Column { + anchors.left : parent.left + + ButtonIconText { + id: cacheKeychain + text: qsTr("Clear Keychain") + leftIcon.text : Style.fa.chain_broken + rightIcon { + text : qsTr("Clear") + color: Style.main.text + font { + pointSize : Style.settings.fontSize * Style.pt + underline : true + } + } + onClicked: { + dialogGlobal.state="clearChain" + dialogGlobal.show() + } + } + + ButtonIconText { + id: logs + anchors.left: parent.left + text: qsTr("Logs") + leftIcon.text : Style.fa.align_justify + rightIcon.text : Style.fa.chevron_circle_right + rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt + onClicked: go.openLogs() + } + + ButtonIconText { + id: bugreport + anchors.left: parent.left + text: qsTr("Report Bug") + leftIcon.text : Style.fa.bug + rightIcon.text : Style.fa.chevron_circle_right + rightIcon.font.pointSize : Style.settings.toggleSize * Style.pt + onClicked: bugreportWin.show() + } + + /* + + ButtonIconText { + id: cacheClear + text: qsTr("Clear Cache") + leftIcon.text : Style.fa.times + rightIcon { + text : qsTr("Clear") + color: Style.main.text + font { + pointSize : Style.settings.fontSize * Style.pt + underline : true + } + } + onClicked: { + dialogGlobal.state="clearCache" + dialogGlobal.show() + } + } + + + ButtonIconText { + id: autoStart + text: qsTr("Automatically Start Bridge") + leftIcon.text : Style.fa.rocket + rightIcon { + font.pointSize : Style.settings.toggleSize * Style.pt + text : go.isAutoStart!=0 ? Style.fa.toggle_on : Style.fa.toggle_off + color : go.isAutoStart!=0 ? Style.main.textBlue : Style.main.textDisabled + } + onClicked: { + go.toggleAutoStart() + } + } + + ButtonIconText { + id: advancedSettings + property bool isAdvanced : !go.isDefaultPort + text: qsTr("Advanced settings") + leftIcon.text : Style.fa.cogs + rightIcon { + font.pointSize : Style.settings.toggleSize * Style.pt + text : isAdvanced!=0 ? Style.fa.chevron_circle_up : Style.fa.chevron_circle_right + color : isAdvanced!=0 ? Style.main.textDisabled : Style.main.textBlue + } + onClicked: { + isAdvanced = !isAdvanced + } + } + + ButtonIconText { + id: changePort + visible: advancedSettings.isAdvanced + text: qsTr("Change SMTP/IMAP Ports") + leftIcon.text : Style.fa.plug + rightIcon { + text : qsTr("Change") + color: Style.main.text + font { + pointSize : Style.settings.fontSize * Style.pt + underline : true + } + } + onClicked: { + dialogChangePort.show() + } + } + */ + } + } +} + diff --git a/internal/frontend/qml/ImportExportUI/VersionInfo.qml b/internal/frontend/qml/ImportExportUI/VersionInfo.qml new file mode 100644 index 00000000..ad90488d --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/VersionInfo.qml @@ -0,0 +1,115 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// credits + +import QtQuick 2.8 +import ProtonUI 1.0 +import ImportExportUI 1.0 + +Item { + id: root + Rectangle { + id: wrapper + anchors.centerIn: parent + width: 2*Style.main.width/3 + height: Style.main.height - 6*Style.dialog.titleSize + color: "transparent" + + Flickable { + anchors.fill : wrapper + contentWidth : wrapper.width + contentHeight : content.height + flickableDirection : Flickable.VerticalFlick + clip : true + + + Column { + id: content + anchors.top: parent.top + anchors.horizontalCenter: parent.horizontalCenter + width: wrapper.width + spacing: 5 + + Text { + visible: go.changelog != "" + anchors { + left: parent.left + } + font.bold: true + font.pointSize: Style.main.fontSize * Style.pt + color: Style.main.text + text: qsTr("Release notes:") + } + + Text { + anchors { + left: parent.left + leftMargin: Style.main.leftMargin + } + font.pointSize: Style.main.fontSize * Style.pt + width: wrapper.width - anchors.leftMargin + wrapMode: Text.Wrap + color: Style.main.text + text: go.changelog + } + + Text { + visible: go.bugfixes != "" + anchors { + left: parent.left + } + font.bold: true + font.pointSize: Style.main.fontSize * Style.pt + color: Style.main.text + text: qsTr("Fixed bugs:") + } + + Repeater { + anchors.fill: parent + model: go.bugfixes.split(";") + + Text { + visible: go.bugfixes!="" + anchors { + left: parent.left + leftMargin: Style.main.leftMargin + } + font.pointSize: Style.main.fontSize * Style.pt + width: wrapper.width - anchors.leftMargin + wrapMode: Text.Wrap + color: Style.main.text + text: modelData + } + } + + Rectangle{id:spacer; color:"transparent"; width:10; height: buttonClose.height} + + + ButtonRounded { + id: buttonClose + anchors.horizontalCenter: content.horizontalCenter + text: "Close" + onClicked: { + root.parent.hide() + } + } + } + } + } +} + diff --git a/internal/frontend/qml/ImportExportUI/qmldir b/internal/frontend/qml/ImportExportUI/qmldir new file mode 100644 index 00000000..752ffc16 --- /dev/null +++ b/internal/frontend/qml/ImportExportUI/qmldir @@ -0,0 +1,31 @@ +module ImportExportUI +AccountDelegate 1.0 AccountDelegate.qml +Credits 1.0 Credits.qml +DateBox 1.0 DateBox.qml +DateInput 1.0 DateInput.qml +DateRangeMenu 1.0 DateRangeMenu.qml +DateRange 1.0 DateRange.qml +DateRangeFunctions 1.0 DateRangeFunctions.qml +DialogExport 1.0 DialogExport.qml +DialogImport 1.0 DialogImport.qml +DialogYesNo 1.0 DialogYesNo.qml +ExportStructure 1.0 ExportStructure.qml +FilterStructure 1.0 FilterStructure.qml +FolderRowButton 1.0 FolderRowButton.qml +HelpView 1.0 HelpView.qml +IEStyle 1.0 IEStyle.qml +ImportDelegate 1.0 ImportDelegate.qml +ImportSourceButton 1.0 ImportSourceButton.qml +ImportStructure 1.0 ImportStructure.qml +ImportReport 1.0 ImportReport.qml +ImportReportCell 1.0 ImportReportCell.qml +InlineDateRange 1.0 InlineDateRange.qml +InlineLabelSelect 1.0 InlineLabelSelect.qml +LabelIconList 1.0 LabelIconList.qml +MainWindow 1.0 MainWindow.qml +OutputFormat 1.0 OutputFormat.qml +PopupEditFolder 1.0 PopupEditFolder.qml +SelectFolderMenu 1.0 SelectFolderMenu.qml +SelectLabelsMenu 1.0 SelectLabelsMenu.qml +SettingsView 1.0 SettingsView.qml +VersionInfo 1.0 VersionInfo.qml diff --git a/internal/frontend/qml/ProtonUI/AccountView.qml b/internal/frontend/qml/ProtonUI/AccountView.qml index 13a437a0..72094fe3 100644 --- a/internal/frontend/qml/ProtonUI/AccountView.qml +++ b/internal/frontend/qml/ProtonUI/AccountView.qml @@ -87,7 +87,7 @@ Item { Text { // Status anchors { left : parent.left - leftMargin : Style.accounts.leftMargin2 + leftMargin : viewContent.width/2 verticalCenter : parent.verticalCenter } visible: root.numAccounts!=0 @@ -99,7 +99,7 @@ Item { Text { // Actions anchors { left : parent.left - leftMargin : Style.accounts.leftMargin3 + leftMargin : 5.5*viewContent.width/8 verticalCenter : parent.verticalCenter } visible: root.numAccounts!=0 diff --git a/internal/frontend/qml/ProtonUI/BugReportWindow.qml b/internal/frontend/qml/ProtonUI/BugReportWindow.qml index e8857179..9a6ccfde 100644 --- a/internal/frontend/qml/ProtonUI/BugReportWindow.qml +++ b/internal/frontend/qml/ProtonUI/BugReportWindow.qml @@ -327,6 +327,7 @@ Window { function show() { prefill() + description.focus=true root.visible=true } diff --git a/internal/frontend/qml/ProtonUI/CheckBoxLabel.qml b/internal/frontend/qml/ProtonUI/CheckBoxLabel.qml index abfd800b..4746f9b8 100644 --- a/internal/frontend/qml/ProtonUI/CheckBoxLabel.qml +++ b/internal/frontend/qml/ProtonUI/CheckBoxLabel.qml @@ -30,10 +30,12 @@ CheckBox { property color uncheckedColor : Style.main.textInactive property string checkedSymbol : Style.fa.check_square_o property string uncheckedSymbol : Style.fa.square_o + property alias symbolPointSize : symbol.font.pointSize background: Rectangle { color: Style.transparent } indicator: Text { + id: symbol text : root.checked ? root.checkedSymbol : root.uncheckedSymbol color : root.checked ? root.checkedColor : root.uncheckedColor font { diff --git a/internal/frontend/qml/ProtonUI/DialogUpdate.qml b/internal/frontend/qml/ProtonUI/DialogUpdate.qml index 479de5f1..261c35f7 100644 --- a/internal/frontend/qml/ProtonUI/DialogUpdate.qml +++ b/internal/frontend/qml/ProtonUI/DialogUpdate.qml @@ -123,12 +123,12 @@ Dialog { wrapMode: Text.Wrap text: { switch (go.progressDescription) { - case 1: return qsTr("Checking the current version.") - case 2: return qsTr("Downloading the update files.") - case 3: return qsTr("Verifying the update files.") - case 4: return qsTr("Unpacking the update files.") - case 5: return qsTr("Starting the update.") - case 6: return qsTr("Quitting the application.") + case "1": return qsTr("Checking the current version.") + case "2": return qsTr("Downloading the update files.") + case "3": return qsTr("Verifying the update files.") + case "4": return qsTr("Unpacking the update files.") + case "5": return qsTr("Starting the update.") + case "6": return qsTr("Quitting the application.") default: return "" } } @@ -220,7 +220,7 @@ Dialog { function clear() { root.hasError = false go.progress = 0.0 - go.progressDescription = 0 + go.progressDescription = "0" } function finished(hasError) { diff --git a/internal/frontend/qml/ProtonUI/InputField.qml b/internal/frontend/qml/ProtonUI/InputField.qml index 525fad39..3c8db7ca 100644 --- a/internal/frontend/qml/ProtonUI/InputField.qml +++ b/internal/frontend/qml/ProtonUI/InputField.qml @@ -138,6 +138,11 @@ Column { } } + function clear() { + inputField.text = "" + rightIcon = "" + } + function checkNonEmpty() { if (inputField.text == "") { rightIcon = Style.fa.exclamation_triangle @@ -154,6 +159,17 @@ Column { if (root.isPassword) inputField.echoMode = TextInput.Password } + function checkIsANumber(){ + if (/^\d+$/.test(inputField.text)) { + rightIcon = Style.fa.check_circle + return true + } + rightIcon = Style.fa.exclamation_triangle + root.placeholderText = "" + inputField.focus = true + return false + } + function forceFocus() { inputField.forceActiveFocus() } diff --git a/internal/frontend/qml/ProtonUI/PopupMessage.qml b/internal/frontend/qml/ProtonUI/PopupMessage.qml index 67aa1b36..5c2f32ae 100644 --- a/internal/frontend/qml/ProtonUI/PopupMessage.qml +++ b/internal/frontend/qml/ProtonUI/PopupMessage.qml @@ -23,9 +23,25 @@ import ProtonUI 1.0 Rectangle { id: root color: Style.transparent - property alias text: message.text + property alias text : message.text + property alias checkbox : checkbox + property alias buttonOkay : buttonOkay + property alias buttonYes : buttonYes + property alias buttonNo : buttonNo + property alias buttonRetry : buttonRetry + property alias buttonSkip : buttonSkip + property alias buttonCancel : buttonCancel + property alias msgWidth : backgroundInp.width + property string msgID : "" visible: false + signal clickedOkay() + signal clickedYes() + signal clickedNo() + signal clickedRetry() + signal clickedSkip() + signal clickedCancel() + MouseArea { // prevent action below anchors.fill: parent hoverEnabled: true @@ -58,14 +74,29 @@ Rectangle { wrapMode: Text.Wrap } - ButtonRounded { - text : qsTr("Okay", "todo") - isOpaque : true - color_main : Style.dialog.background - color_minor : Style.dialog.textBlue - onClicked : root.hide() + CheckBoxLabel { + id: checkbox + text: "" + checked: false + visible: (text != "") + textColor : Style.errorDialog.text + checkedColor: Style.errorDialog.text + uncheckedColor: Style.errorDialog.text anchors.horizontalCenter : parent.horizontalCenter } + + Row { + spacing: Style.dialog.spacing + anchors.horizontalCenter : parent.horizontalCenter + + ButtonRounded { id : buttonNo ; text : qsTr ( "No" , "Button No" ) ; onClicked : root.clickedNo ( ) ; visible : false ; isOpaque : false ; color_main : Style.errorDialog.text ; color_minor : Style.transparent ; } + ButtonRounded { id : buttonYes ; text : qsTr ( "Yes" , "Button Yes" ) ; onClicked : root.clickedYes ( ) ; visible : false ; isOpaque : true ; color_main : Style.errorDialog.text ; color_minor : Style.dialog.textBlue ; } + ButtonRounded { id : buttonRetry ; text : qsTr ( "Retry" , "Button Retry" ) ; onClicked : root.clickedRetry ( ) ; visible : false ; isOpaque : false ; color_main : Style.errorDialog.text ; color_minor : Style.transparent ; } + ButtonRounded { id : buttonSkip ; text : qsTr ( "Skip" , "Button Skip" ) ; onClicked : root.clickedSkip ( ) ; visible : false ; isOpaque : false ; color_main : Style.errorDialog.text ; color_minor : Style.transparent ; } + ButtonRounded { id : buttonCancel ; text : qsTr ( "Cancel" , "Button Cancel" ) ; onClicked : root.clickedCancel ( ) ; visible : false ; isOpaque : true ; color_main : Style.errorDialog.text ; color_minor : Style.dialog.textBlue ; } + ButtonRounded { id : buttonOkay ; text : qsTr ( "Okay" , "Button Okay" ) ; onClicked : root.clickedOkay ( ) ; visible : true ; isOpaque : true ; color_main : Style.errorDialog.text ; color_minor : Style.dialog.textBlue ; } + + } } } @@ -75,7 +106,16 @@ Rectangle { } function hide() { - root.state = "Okay" root.visible=false + + root .text = "" + checkbox .text = "" + + buttonNo .visible = false + buttonYes .visible = false + buttonRetry .visible = false + buttonSkip .visible = false + buttonCancel .visible = false + buttonOkay .visible = true } } diff --git a/internal/frontend/qml/ProtonUI/RoundedRectangle.qml b/internal/frontend/qml/ProtonUI/RoundedRectangle.qml new file mode 100644 index 00000000..4b63de09 --- /dev/null +++ b/internal/frontend/qml/ProtonUI/RoundedRectangle.qml @@ -0,0 +1,115 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +import QtQuick 2.8 +import ProtonUI 1.0 + + +Rectangle { + id: root + + color: Style.transparent + + property color fillColor : Style.main.background + property color strokeColor : Style.main.line + property real strokeWidth : Style.dialog.borderInput + property real radiusTopLeft : Style.dialog.radiusButton + property real radiusBottomLeft : Style.dialog.radiusButton + property real radiusTopRight : Style.dialog.radiusButton + property real radiusBottomRight : Style.dialog.radiusButton + + function paint() { + canvas.requestPaint() + } + + onFillColorChanged : root.paint() + onStrokeColorChanged : root.paint() + onStrokeWidthChanged : root.paint() + onRadiusTopLeftChanged : root.paint() + onRadiusBottomLeftChanged : root.paint() + onRadiusTopRightChanged : root.paint() + onRadiusBottomRightChanged : root.paint() + + + Canvas { + id: canvas + anchors.fill: root + + onPaint: { + var ctx = getContext("2d") + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = root.fillColor + ctx.strokeStyle = root.strokeColor + ctx.lineWidth = root.strokeWidth + var dimensions = { + x: ctx.lineWidth, + y: ctx.lineWidth, + w: canvas.width-2*ctx.lineWidth, + h: canvas.height-2*ctx.lineWidth, + } + var radius = { + tl: root.radiusTopLeft, + tr: root.radiusTopRight, + bl: root.radiusBottomLeft, + br: root.radiusBottomRight, + } + + root.roundRect( + ctx, + dimensions, + radius, true, true + ) + } + } + + // adapted from: https://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas/3368118#3368118 + function roundRect(ctx, dim, radius, fill, stroke) { + if (typeof stroke == 'undefined') { + stroke = true; + } + if (typeof radius === 'undefined') { + radius = 5; + } + if (typeof radius === 'number') { + radius = {tl: radius, tr: radius, br: radius, bl: radius}; + } else { + var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0}; + for (var side in defaultRadius) { + radius[side] = radius[side] || defaultRadius[side]; + } + } + ctx.beginPath(); + ctx.moveTo(dim.x + radius.tl, dim.y); + ctx.lineTo(dim.x + dim.w - radius.tr, dim.y); + ctx.quadraticCurveTo(dim.x + dim.w, dim.y, dim.x + dim.w, dim.y + radius.tr); + ctx.lineTo(dim.x + dim.w, dim.y + dim.h - radius.br); + ctx.quadraticCurveTo(dim.x + dim.w, dim.y + dim.h, dim.x + dim.w - radius.br, dim.y + dim.h); + ctx.lineTo(dim.x + radius.bl, dim.y + dim.h); + ctx.quadraticCurveTo(dim.x, dim.y + dim.h, dim.x, dim.y + dim.h - radius.bl); + ctx.lineTo(dim.x, dim.y + radius.tl); + ctx.quadraticCurveTo(dim.x, dim.y, dim.x + radius.tl, dim.y); + ctx.closePath(); + if (fill) { + ctx.fill(); + } + if (stroke) { + ctx.stroke(); + } + } + + Component.onCompleted: root.paint() +} diff --git a/internal/frontend/qml/ProtonUI/Style.qml b/internal/frontend/qml/ProtonUI/Style.qml index 07655ba1..e7979854 100644 --- a/internal/frontend/qml/ProtonUI/Style.qml +++ b/internal/frontend/qml/ProtonUI/Style.qml @@ -221,6 +221,31 @@ QtObject { property real leftMargin3 : 30 * px } + property QtObject importing : QtObject { + property color rowBackground : dialog.background + property color rowLine : dialog.line + } + + property QtObject dropDownLight: QtObject { + property color background : dialog.background + property color text : dialog.text + property color inactive : dialog.line + property color highlight : dialog.textBlue + property color separator : dialog.line + property color line : dialog.line + property bool labelBold : true + } + + property QtObject dropDownDark : QtObject { + property color background : dialog.text + property color text : dialog.background + property color inactive : dialog.line + property color highlight : dialog.textBlue + property color separator : dialog.line + property color line : dialog.line + property bool labelBold : true + } + property int okInfoBar : 0 property int warnInfoBar : 1 property int warnBubbleMessage : 2 diff --git a/internal/frontend/qml/ProtonUI/WindowTitleBar.qml b/internal/frontend/qml/ProtonUI/WindowTitleBar.qml index c48efafc..8234a521 100644 --- a/internal/frontend/qml/ProtonUI/WindowTitleBar.qml +++ b/internal/frontend/qml/ProtonUI/WindowTitleBar.qml @@ -23,7 +23,9 @@ import ProtonUI 1.0 Rectangle { id: root - height: root.isDarwin ? Style.titleMacOS.height : Style.title.height + height: visible ? ( + root.isDarwin ? Style.titleMacOS.height : Style.title.height + ) : 0 color: "transparent" property bool isDarwin : (go.goos == "darwin") property QtObject window diff --git a/internal/frontend/qml/ProtonUI/qmldir b/internal/frontend/qml/ProtonUI/qmldir index afe0b94a..ade9e7aa 100644 --- a/internal/frontend/qml/ProtonUI/qmldir +++ b/internal/frontend/qml/ProtonUI/qmldir @@ -23,6 +23,7 @@ InputField 1.0 InputField.qml InstanceExistsWindow 1.0 InstanceExistsWindow.qml LogoHeader 1.0 LogoHeader.qml PopupMessage 1.0 PopupMessage.qml +RoundedRectangle 1.0 RoundedRectangle.qml TabButton 1.0 TabButton.qml TabLabels 1.0 TabLabels.qml TextLabel 1.0 TextLabel.qml diff --git a/internal/frontend/qml/tst_GuiIE.qml b/internal/frontend/qml/tst_GuiIE.qml new file mode 100644 index 00000000..625edda0 --- /dev/null +++ b/internal/frontend/qml/tst_GuiIE.qml @@ -0,0 +1,970 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +import QtQuick 2.8 +import ImportExportUI 1.0 +import ProtonUI 1.0 +import QtQuick.Controls 2.1 +import QtQuick.Window 2.2 + +Window { + id : testroot + width : 100 + height : 600 + flags : Qt.Window | Qt.Dialog | Qt.FramelessWindowHint + visible : true + title : "GUI test Window" + color : "transparent" + x : testgui.winMain.x - 120 + y : testgui.winMain.y + + property bool newVersion : true + + Accessible.name: testroot.title + Accessible.description: "Window with buttons testing the GUI events" + + + Rectangle { + id:test_systray + anchors{ + top: parent.top + horizontalCenter: parent.horizontalCenter + } + height: 40 + width: 100 + color: "yellow" + Image { + id: sysImg + anchors { + left : test_systray.left + top : test_systray.top + } + height: test_systray.height + mipmap: true + fillMode : Image.PreserveAspectFit + source: "" + } + Text { + id: systrText + anchors { + right : test_systray.right + verticalCenter: test_systray.verticalCenter + } + text: "unset" + } + + function normal() { + test_systray.color = "#22ee22" + systrText.text = "norm" + sysImg.source= "../share/icons/rounded-systray.png" + } + function highlight() { + test_systray.color = "#eeee22" + systrText.text = "highl" + sysImg.source= "../share/icons/rounded-syswarn.png" + } + + MouseArea { + property point diff: "0,0" + anchors.fill: parent + onPressed: { + diff = Qt.point(testroot.x, testroot.y) + var mousePos = mapToGlobal(mouse.x, mouse.y) + diff.x -= mousePos.x + diff.y -= mousePos.y + } + onPositionChanged: { + var currPos = mapToGlobal(mouse.x, mouse.y) + testroot.x = currPos.x + diff.x + testroot.y = currPos.y + diff.y + } + } + } + + ListModel { + id: buttons + + ListElement { title : "Show window" } + ListElement { title : "Logout cuthix" } + ListElement { title : "Internet on" } + ListElement { title : "Internet off" } + ListElement { title : "Macos" } + ListElement { title : "Windows" } + ListElement { title : "Linux" } + ListElement { title : "New Version" } + ListElement { title : "ForceUpgrade" } + ListElement { title : "ImportStructure" } + ListElement { title : "DraftImpFailed" } + ListElement { title : "NoInterImp" } + ListElement { title : "ReportImp" } + ListElement { title : "NewFolder" } + ListElement { title : "EditFolder" } + ListElement { title : "EditLabel" } + ListElement { title : "ExpProgErr" } + ListElement { title : "ImpProgErr" } + } + + ListView { + id: view + anchors { + top : test_systray.bottom + bottom : parent.bottom + left : parent.left + right : parent.right + } + + orientation : ListView.Vertical + model : buttons + focus : true + + delegate : ButtonRounded { + text : title + color_main : "orange" + color_minor : "#aa335588" + isOpaque : true + anchors.horizontalCenter: parent.horizontalCenter + onClicked : { + console.log("Clicked on ", title) + switch (title) { + case "Show window" : + go.showWindow(); + break; + case "Logout cuthix" : + go.checkLoggedOut("cuthix"); + break; + case "Internet on" : + go.setConnectionStatus(true); + break; + case "Internet off" : + go.setConnectionStatus(false); + break; + case "Macos" : + go.goos = "darwin"; + break; + case "Windows" : + go.goos = "windows"; + break; + case "Linux" : + go.goos = "linux"; + break; + case "New Version" : + testroot.newVersion = !testroot.newVersion + systrText.text = testroot.newVersion ? "new version" : "uptodate" + break + case "ForceUpgrade" : + go.notifyUpgrade() + break; + case "ImportStructure" : + testgui.winMain.dialogImport.address = "cuto@pm.com" + testgui.winMain.dialogImport.show() + testgui.winMain.dialogImport.currentIndex=DialogImport.Page.SourceToTarget + break + case "DraftImpFailed" : + testgui.notifyError(testgui.enums.errDraftImportFailed) + break + case "NoInterImp" : + testgui.notifyError(testgui.enums.errNoInternetWhileImport) + break + case "ReportImp" : + testgui.winMain.dialogImport.address = "cuto@pm.com" + testgui.winMain.dialogImport.show() + testgui.winMain.dialogImport.currentIndex=DialogImport.Page.Report + break + case "NewFolder" : + testgui.winMain.popupFolderEdit.show("currentName", "", "", testgui.enums.folderTypeFolder, "") + break + case "EditFolder" : + testgui.winMain.popupFolderEdit.show("currentName", "", "#7272a7", testgui.enums.folderTypeFolder, "") + break + case "EditFolder" : + testgui.winMain.popupFolderEdit.show("currentName", "", "", testgui.enums.folderTypeFolder, "") + break + case "ImpProgErr" : + go.animateProgressBar.pause() + testgui.notifyError(testgui.enums.errEmailImportFailed) + break + case "ExpProgErr" : + go.animateProgressBar.pause() + testgui.notifyError(testgui.enums.errEmailExportFailed) + break + default : + console.log("Not implemented " + title) + } + } + } + } + + + Component.onCompleted : { + testgui.winMain.x = 150 + testgui.winMain.y = 100 + } + + //InstanceExistsWindow { id: ie_test } + + GuiIE { + id: testgui + //checkLogTimer.interval: 3*1000 + winMain.visible: true + + ListModel{ + id: accountsModel + ListElement{ account : "cuthix" ; status : "connected"; isExpanded: false; isCombinedAddressMode: false; hostname : "127.0.0.1"; password : "ZI9tKp+ryaxmbpn2E12"; security : "StarTLS"; portSMTP : 1025; portIMAP : 1143; aliases : "cuto@pm.com;jaku@pm.com;DoYouKnowAboutAMovieCalledTheHorriblySlowMurderWithExtremelyInefficientWeapon@thatYouCanFindForExampleOnyoutube.com" } + ListElement{ account : "exteremelongnamewhichmustbeeladedinthemiddleoftheaddress@protonmail.com" ; status : "connected"; isExpanded: true; isCombinedAddressMode: true; hostname : "127.0.0.1"; password : "ZI9tKp+ryaxmbpn2E12"; security : "StarTLS"; portSMTP : 1025; portIMAP : 1143; aliases : "cuto@pm.com;jaku@pm.com;hu@hu.hu" } + ListElement{ account : "cuthix2@protonmail.com" ; status : "disconnected"; isExpanded: false; isCombinedAddressMode: false; hostname : "127.0.0.1"; password : "ZI9tKp+ryaxmbpn2E12"; security : "StarTLS"; portSMTP : 1025; portIMAP : 1143; aliases : "cuto@pm.com;jaku@pm.com;hu@hu.hu" } + ListElement{ account : "many@protonmail.com" ; status : "connected"; isExpanded: true; isCombinedAddressMode: true; hostname : "127.0.0.1"; password : "ZI9tKp+ryaxmbpn2E12"; security : "StarTLS"; portSMTP : 1025; portIMAP : 1143; aliases : "cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;cuto@pm.com;jaku@pm.com;hu@hu.hu;"} + } + + ListModel{ + id: structureExternal + + property var globalOptions: JSON.parse('{ "folderId" : "global--uniq" , "folderName" : "" , "folderColor" : "" , "folderType" : "" , "folderEntries" : 0, "fromDate": 0, "toDate": 0, "isFolderSelected" : false , "targetFolderID": "14" , "targetLabelIDs": ";20;29" }') + + ListElement{ folderId : "Inbox" ; folderName : "Inbox" ; folderColor : "black" ; folderType : "" ; folderEntries : 1 ; fromDate : 0 ; toDate : 0 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + ListElement{ folderId : "Sent" ; folderName : "Sent" ; folderColor : "black" ; folderType : "" ; folderEntries : 2 ; fromDate : 0 ; toDate : 0 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + ListElement{ folderId : "Spam" ; folderName : "Spam" ; folderColor : "black" ; folderType : "" ; folderEntries : 3 ; fromDate : 0 ; toDate : 0 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + ListElement{ folderId : "Draft" ; folderName : "Draft" ; folderColor : "black" ; folderType : "" ; folderEntries : 4 ; fromDate : 0 ; toDate : 0 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + + ListElement{ folderId : "Folder0" ; folderName : "Folder0" ; folderColor : "black" ; folderType : "folder" ; folderEntries : 10 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + ListElement{ folderId : "Folder1" ; folderName : "Folder1" ; folderColor : "black" ; folderType : "folder" ; folderEntries : 20 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + ListElement{ folderId : "Folder2" ; folderName : "Folder2" ; folderColor : "black" ; folderType : "folder" ; folderEntries : 30 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + ListElement{ folderId : "Folder3" ; folderName : "Folder3ToolongAndMustBeElidedSimilarToOnOfAccountsItJustNotNeedToBeThatLong" ; folderColor : "black" ; folderType : "folder" ; folderEntries : 40 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + + ListElement{ folderId : "Label0" ; folderName : "Label-" ; folderColor : "black" ; folderType : "label" ; folderEntries : 10 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + ListElement{ folderId : "Label1" ; folderName : "Label1" ; folderColor : "black" ; folderType : "label" ; folderEntries : 11 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + ListElement{ folderId : "Label2" ; folderName : "Label2" ; folderColor : "black" ; folderType : "label" ; folderEntries : 12 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" } + ListElement{ folderId : "Label3" ; folderName : "Label3" ; folderColor : "black" ; folderType : "label" ; folderEntries : 13 ; fromDate : 300000 ; toDate : 15000000 ; isFolderSelected : true ; targetFolderID : "14" ; targetLabelIDs : ";20;29" } + + function addTargetLabelID ( id , label ) { structureFuncs.addTargetLabelID ( structureExternal , id , label ) } + function removeTargetLabelID ( id , label ) { structureFuncs.removeTargetLabelID ( structureExternal , id , label ) } + function setTargetFolderID ( id , label ) { structureFuncs.setTargetFolderID ( structureExternal , id , label ) } + function setFromToDate ( id , from, to ) { structureFuncs.setFromToDate ( structureExternal , id , from, to ) } + + function getID ( row ) { return row == -1 ? structureExternal.globalOptions.folderId : structureExternal.get(row).folderId } + function getById ( folderId ) { return structureFuncs.getById ( structureExternal , folderId ) } + function getFrom ( folderId ) { return structureExternal.getById ( folderId ) .fromDate } + function getTo ( folderId ) { return structureExternal.getById ( folderId ) .toDate } + function getTargetLabelIDs ( folderId ) { return structureExternal.getById ( folderId ) .getTargetLabelIDs } + function hasFolderWithName ( folderName ) { return structureFuncs.hasFolderWithName ( structureExternal , folderName ) } + + function hasTarget () { return structureFuncs.hasTarget(structureExternal) } + } + + ListModel{ + id: structurePM + + // group selectors + property bool selectedLabels : false + property bool selectedFolders : false + property bool atLeastOneSelected : true + + property var globalOptions: JSON.parse('{ "folderId" : "global--uniq" , "folderName" : "global" , "folderColor" : "black" , "folderType" : "" , "folderEntries" : 0 , "fromDate": 300000 , "toDate": 15000000 , "isFolderSelected" : false , "targetFolderID": "14" , "targetLabelIDs": ";20;29" }') + + ListElement{ folderId : "0" ; folderName : "INBOX" ; folderColor : "blue" ; folderType : "" ; folderEntries : 1 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "1" ; folderName : "Sent" ; folderColor : "blue" ; folderType : "" ; folderEntries : 2 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "2" ; folderName : "Spam" ; folderColor : "blue" ; folderType : "" ; folderEntries : 3 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "3" ; folderName : "Draft" ; folderColor : "blue" ; folderType : "" ; folderEntries : 4 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "6" ; folderName : "Archive" ; folderColor : "blue" ; folderType : "" ; folderEntries : 5 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + + ListElement{ folderId : "14" ; folderName : "Folder0" ; folderColor : "blue" ; folderType : "folder" ; folderEntries : 10 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "15" ; folderName : "Folder1" ; folderColor : "green" ; folderType : "folder" ; folderEntries : 20 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "16" ; folderName : "Folder2" ; folderColor : "pink" ; folderType : "folder" ; folderEntries : 30 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "17" ; folderName : "Folder3ToolongAndMustBeElidedSimilarToOnOfAccountsItJustNotNeedToBeThatLong" ; folderColor : "orange" ; folderType : "folder" ; folderEntries : 40 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + + ListElement{ folderId : "28" ; folderName : "Label0" ; folderColor : "red" ; folderType : "label" ; folderEntries : 10 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "29" ; folderName : "Label1" ; folderColor : "blue" ; folderType : "label" ; folderEntries : 11 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "20" ; folderName : "Label2" ; folderColor : "green" ; folderType : "label" ; folderEntries : 12 ; isFolderSelected : false ; targetFolderID : "" ; targetLabelIDs : "" ; } + ListElement{ folderId : "21" ; folderName : "Label3ToolongAndMustBeElidedSimilarToOnOfAccountsItJustNotNeedToBeThatLong" ; folderColor : "orange" ; folderType : "label" ; folderEntries : 40 ; isFolderSelected : true ; targetFolderID : "" ; targetLabelIDs : "" ; } + + + function setFolderSelection ( folderId , toSelect ) { structureFuncs.setFolderSelection ( structurePM , folderId , toSelect ) } + function selectType ( folderType , toSelect ) { structureFuncs.setTypeSelected ( structurePM , folderType , toSelect ) } + function setFromToDate ( id , from, to ) { structureFuncs.setFromToDate ( structureExternal , id , from, to ) } + + function getID ( row ) { return row == -1 ? structurePM.globalOptions.folderId : structurePM.get(row) .folderId } + function getById ( folderId ) { return structureFuncs.getById ( structurePM , folderId ) } + function getName ( folderId ) { return structurePM.getById ( folderId ) .folderName } + function getType ( folderId ) { return structurePM.getById ( folderId ) .folderType } + function getColor ( folderId ) { return structurePM.getById ( folderId ) .folderColor } + function getFrom ( folderId ) { return structurePM.getById ( folderId ) .fromDate } + function getTo ( folderId ) { return structurePM.getById ( folderId ) .toDate } + function getTargetLabelIDs ( folderId ) { return structurePM.getById ( folderId ) .getTargetLabelIDs } + function hasFolderWithName ( folderName ) { return structureFuncs.hasFolderWithName ( structurePM , folderName ) } + + onDataChanged: { + structureFuncs.updateSelection(structurePM) + } + } + + QtObject { + id: structureFuncs + + function setFolderSelection (model, id , toSelect ) { + console.log(" set folde sel", id, toSelect) + for (var i= -1; i createLabelOrFolder", address, fname, fcolor, isFolder, sourceID) + return (fname!="fail") + } + + function checkInternet() { + // nothing to do + } + + function loadImportReports(fname) { + console.log("load import reports for ", fname) + } + + + onToggleAutoStart: { + workAndClose("toggleAutoStart") + isAutoStart = (isAutoStart!=0) ? 0 : 1 + console.log (" Test: toggleAutoStart "+isAutoStart) + } + + function openReport() { + Qt.openUrlExternally("file:///home/cuto/") + } + + function sendImportReport(address, fname) { + console.log("sending import report from ", address, " file ", fname) + return !fname.includes("fail") + } + } +} diff --git a/internal/frontend/qt-common/Makefile.local b/internal/frontend/qt-common/Makefile.local new file mode 100644 index 00000000..1ca530a4 --- /dev/null +++ b/internal/frontend/qt-common/Makefile.local @@ -0,0 +1,6 @@ +clean: + rm -f moc.cpp + rm -f moc.go + rm -f moc.h + rm -f moc_cgo*.go + rm -f moc_moc.h diff --git a/internal/frontend/qt-common/account_model.go b/internal/frontend/qt-common/account_model.go new file mode 100644 index 00000000..87d32746 --- /dev/null +++ b/internal/frontend/qt-common/account_model.go @@ -0,0 +1,236 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtcommon + +import ( + "fmt" + "github.com/therecipe/qt/core" +) + +// AccountInfo is an element of model. It contains all data for one account and +// it's aliases +type AccountInfo struct { + core.QObject + + _ string `property:"account"` + _ string `property:"userID"` + _ string `property:"status"` + _ string `property:"hostname"` + _ string `property:"password"` + _ string `property:"security"` + _ int `property:"portSMTP"` + _ int `property:"portIMAP"` + _ string `property:"aliases"` + _ bool `property:"isExpanded"` + _ bool `property:"isCombinedAddressMode"` +} + +// Constants for AccountsModel property map +const ( + Account = int(core.Qt__UserRole) + 1<= len(s.Accounts()) { + return NewAccountInfo(nil) + } + return s.Accounts()[index] +} + +// data return value for index and property +func (s *AccountsModel) data(index *core.QModelIndex, property int) *core.QVariant { + if !index.IsValid() { + return core.NewQVariant() + } + + if index.Row() >= len(s.Accounts()) { + return core.NewQVariant() + } + + var accountInfo = s.Accounts()[index.Row()] + + switch property { + case Account: + return NewQVariantString(accountInfo.Account()) + case UserID: + return NewQVariantString(accountInfo.UserID()) + case Status: + return NewQVariantString(accountInfo.Status()) + case Hostname: + return NewQVariantString(accountInfo.Hostname()) + case Password: + return NewQVariantString(accountInfo.Password()) + case Security: + return NewQVariantString(accountInfo.Security()) + case PortIMAP: + return NewQVariantInt(accountInfo.PortIMAP()) + case PortSMTP: + return NewQVariantInt(accountInfo.PortSMTP()) + case Aliases: + return NewQVariantString(accountInfo.Aliases()) + case IsExpanded: + return NewQVariantBool(accountInfo.IsExpanded()) + case IsCombinedAddressMode: + return NewQVariantBool(accountInfo.IsCombinedAddressMode()) + default: + return core.NewQVariant() + } +} + +// rowCount returns the dimension of model: number of rows is equivalent to number of items in list. +func (s *AccountsModel) rowCount(parent *core.QModelIndex) int { + return len(s.Accounts()) +} + +// columnCount returns the dimension of model: AccountsModel has only one column. +func (s *AccountsModel) columnCount(parent *core.QModelIndex) int { + return 1 +} + +// roleNames returns the names of available item properties. +func (s *AccountsModel) roleNames() map[int]*core.QByteArray { + return s.Roles() +} + +// addAccount is connected to the addAccount slot. +func (s *AccountsModel) addAccount(accountInfo *AccountInfo) { + s.BeginInsertRows(core.NewQModelIndex(), len(s.Accounts()), len(s.Accounts())) + s.SetAccounts(append(s.Accounts(), accountInfo)) + s.SetCount(len(s.Accounts())) + s.EndInsertRows() +} + +// toggleIsAvailable is connected to toggleIsAvailable slot. +func (s *AccountsModel) toggleIsAvailable(row int) { + var accountInfo = s.Accounts()[row] + currentStatus := accountInfo.Status() + if currentStatus == "active" { + accountInfo.SetStatus("disabled") + } else if currentStatus == "disabled" { + accountInfo.SetStatus("active") + } else { + accountInfo.SetStatus("error") + } + var pIndex = s.Index(row, 0, core.NewQModelIndex()) + s.DataChanged(pIndex, pIndex, []int{Status}) +} + +// removeAccount is connected to removeAccount slot. +func (s *AccountsModel) removeAccount(row int) { + s.BeginRemoveRows(core.NewQModelIndex(), row, row) + s.SetAccounts(append(s.Accounts()[:row], s.Accounts()[row+1:]...)) + s.SetCount(len(s.Accounts())) + s.EndRemoveRows() +} + +// Clear removes all items in model. +func (s *AccountsModel) Clear() { + s.BeginRemoveRows(core.NewQModelIndex(), 0, len(s.Accounts())) + s.SetAccounts(s.Accounts()[0:0]) + s.SetCount(len(s.Accounts())) + s.EndRemoveRows() +} + +// Dump prints the content of account models to console. +func (s *AccountsModel) Dump() { + fmt.Printf("Dimensions rows %d cols %d\n", s.rowCount(nil), s.columnCount(nil)) + for iAcc := 0; iAcc < s.rowCount(nil); iAcc++ { + var accountInfo = s.Accounts()[iAcc] + fmt.Printf(" %d. %s\n", iAcc, accountInfo.Account()) + } +} diff --git a/internal/frontend/qt-common/accounts.go b/internal/frontend/qt-common/accounts.go new file mode 100644 index 00000000..e731456f --- /dev/null +++ b/internal/frontend/qt-common/accounts.go @@ -0,0 +1,259 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtcommon + +import ( + "fmt" + "strings" + "sync" + + "github.com/ProtonMail/proton-bridge/internal/bridge" + "github.com/ProtonMail/proton-bridge/internal/events" + "github.com/ProtonMail/proton-bridge/internal/frontend/types" + "github.com/ProtonMail/proton-bridge/internal/preferences" + "github.com/ProtonMail/proton-bridge/pkg/config" + "github.com/ProtonMail/proton-bridge/pkg/keychain" + "github.com/ProtonMail/proton-bridge/pkg/pmapi" +) + +// QMLer sends signals to GUI +type QMLer interface { + ProcessFinished() + NotifyHasNoKeychain() + SetConnectionStatus(bool) + SetIsRestarting(bool) + SetAddAccountWarning(string, int) + NotifyBubble(int, string) + EmitEvent(string, string) + Quit() + + CanNotReachAPI() string + WrongMailboxPassword() string +} + +// Accounts holds functionality of users +type Accounts struct { + Model *AccountsModel + qml QMLer + um types.UserManager + prefs *config.Preferences + + authClient pmapi.Client + auth *pmapi.Auth + + LatestUserID string + accountMutex sync.Mutex +} + +// SetupAccounts will create Model and set QMLer and UserManager +func (a *Accounts) SetupAccounts(qml QMLer, um types.UserManager) { + a.Model = NewAccountsModel(nil) + a.qml = qml + a.um = um +} + +// LoadAccounts refreshes the current account list in GUI +func (a *Accounts) LoadAccounts() { + a.accountMutex.Lock() + defer a.accountMutex.Unlock() + + a.Model.Clear() + + users := a.um.GetUsers() + + // If there are no active accounts. + if len(users) == 0 { + log.Info("No active accounts") + return + } + for _, user := range users { + accInfo := NewAccountInfo(nil) + username := user.Username() + if username == "" { + username = user.ID() + } + accInfo.SetAccount(username) + + // Set status. + if user.IsConnected() { + accInfo.SetStatus("connected") + } else { + accInfo.SetStatus("disconnected") + } + + // Set login info. + accInfo.SetUserID(user.ID()) + accInfo.SetHostname(bridge.Host) + accInfo.SetPassword(user.GetBridgePassword()) + if a.prefs != nil { + accInfo.SetPortIMAP(a.prefs.GetInt(preferences.IMAPPortKey)) + accInfo.SetPortSMTP(a.prefs.GetInt(preferences.SMTPPortKey)) + } + + // Set aliases. + accInfo.SetAliases(strings.Join(user.GetAddresses(), ";")) + accInfo.SetIsExpanded(user.ID() == a.LatestUserID) + accInfo.SetIsCombinedAddressMode(user.IsCombinedAddressMode()) + + a.Model.addAccount(accInfo) + } + + // Updated can clear. + a.LatestUserID = "" +} + +// ClearCache signal to remove all DB files +func (a *Accounts) ClearCache() { + defer a.qml.ProcessFinished() + if err := a.um.ClearData(); err != nil { + log.Error("While clearing cache: ", err) + } + // Clearing data removes everything (db, preferences, ...) + // so everything has to be stopped and started again. + a.qml.SetIsRestarting(true) + a.qml.Quit() +} + +// ClearKeychain signal remove all accounts from keychains +func (a *Accounts) ClearKeychain() { + defer a.qml.ProcessFinished() + for _, user := range a.um.GetUsers() { + if err := a.um.DeleteUser(user.ID(), false); err != nil { + log.Error("While deleting user: ", err) + if err == keychain.ErrNoKeychainInstalled { // Probably not needed anymore. + a.qml.NotifyHasNoKeychain() + } + } + } +} + +// LogoutAccount signal to remove account +func (a *Accounts) LogoutAccount(iAccount int) { + defer a.qml.ProcessFinished() + userID := a.Model.get(iAccount).UserID() + user, err := a.um.GetUser(userID) + if err != nil { + log.Error("While logging out ", userID, ": ", err) + return + } + if err := user.Logout(); err != nil { + log.Error("While logging out ", userID, ": ", err) + } +} + +func (a *Accounts) showLoginError(err error, scope string) bool { + if err == nil { + a.qml.SetConnectionStatus(true) // If we are here connection is ok. + return false + } + log.Warnf("%s: %v", scope, err) + if err == pmapi.ErrAPINotReachable { + a.qml.SetConnectionStatus(false) + SendNotification(a.qml, TabAccount, a.qml.CanNotReachAPI()) + a.qml.ProcessFinished() + return true + } + a.qml.SetConnectionStatus(true) // If we are here connection is ok. + if err == pmapi.ErrUpgradeApplication { + a.qml.EmitEvent(events.UpgradeApplicationEvent, "") + return true + } + a.qml.SetAddAccountWarning(err.Error(), -1) + return true +} + +// Login signal returns: +// -1: when error occurred +// 0: when no 2FA and no MBOX +// 1: when has 2FA +// 2: when has no 2FA but have MBOX +func (a *Accounts) Login(login, password string) int { + var err error + a.authClient, a.auth, err = a.um.Login(login, password) + if a.showLoginError(err, "login") { + return -1 + } + if a.auth.HasTwoFactor() { + return 1 + } + if a.auth.HasMailboxPassword() { + return 2 + } + return 0 // No 2FA, no mailbox password. +} + +// Auth2FA returns: +// -1 : error (use SetAddAccountWarning to show message) +// 0 : single password mode +// 1 : two password mode +func (a *Accounts) Auth2FA(twoFacAuth string) int { + var err error + if a.auth == nil || a.authClient == nil { + err = fmt.Errorf("missing authentication in auth2FA %p %p", a.auth, a.authClient) + } else { + _, err = a.authClient.Auth2FA(twoFacAuth, a.auth) + } + + if a.showLoginError(err, "auth2FA") { + return -1 + } + + if a.auth.HasMailboxPassword() { + return 1 // Ask for mailbox password. + } + return 0 // One password. +} + +// AddAccount signal to add an account. It should close login modal +// ProcessFinished if ok. +func (a *Accounts) AddAccount(mailboxPassword string) int { + if a.auth == nil || a.authClient == nil { + log.Errorf("Missing authentication in addAccount %p %p", a.auth, a.authClient) + a.qml.SetAddAccountWarning(a.qml.WrongMailboxPassword(), -2) + return -1 + } + + user, err := a.um.FinishLogin(a.authClient, a.auth, mailboxPassword) + if err != nil { + log.WithError(err).Error("Login was unsuccessful") + a.qml.SetAddAccountWarning("Failure: "+err.Error(), -2) + return -1 + } + + a.LatestUserID = user.ID() + a.qml.EmitEvent(events.UserRefreshEvent, user.ID()) + a.qml.ProcessFinished() + return 0 +} + +// DeleteAccount by index in Model +func (a *Accounts) DeleteAccount(iAccount int, removePreferences bool) { + defer a.qml.ProcessFinished() + userID := a.Model.get(iAccount).UserID() + if err := a.um.DeleteUser(userID, removePreferences); err != nil { + log.Warn("deleteUser: cannot remove user: ", err) + if err == keychain.ErrNoKeychainInstalled { + a.qml.NotifyHasNoKeychain() + return + } + SendNotification(a.qml, TabSettings, err.Error()) + return + } +} diff --git a/internal/frontend/qt/logs.cpp b/internal/frontend/qt-common/common.cpp similarity index 58% rename from internal/frontend/qt/logs.cpp rename to internal/frontend/qt-common/common.cpp index e174e525..e29545a6 100644 --- a/internal/frontend/qt/logs.cpp +++ b/internal/frontend/qt-common/common.cpp @@ -1,23 +1,30 @@ // +build !nogui - -#include "logs.h" +#include "common.h" #include "_cgo_export.h" +#include #include #include +#include #include void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { - Q_UNUSED(type); - Q_UNUSED(context); - - QByteArray localMsg = msg.toUtf8().prepend("WHITESPACE"); + Q_UNUSED( type ) + Q_UNUSED( context ) + QByteArray localMsg = msg.toUtf8().prepend("WHITESPACE"); logMsgPacked( const_cast( (localMsg.constData()) +10 ), localMsg.size()-10 ); //printf("Handler: %s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); } -void InstallMessageHandler() { qInstallMessageHandler(messageHandler); } +void InstallMessageHandler() { + qInstallMessageHandler(messageHandler); +} + + +void RegisterTypes() { + qRegisterMetaType >(); +} diff --git a/internal/frontend/qt-common/common.go b/internal/frontend/qt-common/common.go new file mode 100644 index 00000000..644eefb6 --- /dev/null +++ b/internal/frontend/qt-common/common.go @@ -0,0 +1,130 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtcommon + +//#include "common.h" +import "C" + +import ( + "bufio" + "os" + "time" + + "github.com/sirupsen/logrus" + "github.com/therecipe/qt/core" +) + +var log = logrus.WithField("pkg", "frontend/qt-common") +var logQML = logrus.WithField("pkg", "frontend/qml") + +// RegisterTypes for vector of ints +func RegisterTypes() { // need to fix test message + C.RegisterTypes() +} + +func installMessageHandler() { + C.InstallMessageHandler() +} + +//export logMsgPacked +func logMsgPacked(data *C.char, len C.int) { + logQML.Warn(C.GoStringN(data, len)) +} + +// QtSetupCoreAndControls hanldes global setup of Qt. +// Should be called once per program. Probably once per thread is fine. +func QtSetupCoreAndControls(programName, programVersion string) { + installMessageHandler() + // Core setup. + core.QCoreApplication_SetApplicationName(programName) + core.QCoreApplication_SetApplicationVersion(programVersion) + // High DPI scaling for windows. + core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false) + // Software OpenGL: to avoid dedicated GPU. + core.QCoreApplication_SetAttribute(core.Qt__AA_UseSoftwareOpenGL, true) + // Basic style for QuickControls2 objects. + //quickcontrols2.QQuickStyle_SetStyle("material") +} + +// NewQByteArrayFromString is wrapper for new QByteArray from string +func NewQByteArrayFromString(name string) *core.QByteArray { + return core.NewQByteArray2(name, -1) +} + +// NewQVariantString is wrapper for QVariant alocator String +func NewQVariantString(data string) *core.QVariant { + return core.NewQVariant1(data) +} + +// NewQVariantStringArray is wrapper for QVariant alocator String Array +func NewQVariantStringArray(data []string) *core.QVariant { + return core.NewQVariant1(data) +} + +// NewQVariantBool is wrapper for QVariant alocator Bool +func NewQVariantBool(data bool) *core.QVariant { + return core.NewQVariant1(data) +} + +// NewQVariantInt is wrapper for QVariant alocator Int +func NewQVariantInt(data int) *core.QVariant { + return core.NewQVariant1(data) +} + +// NewQVariantLong is wrapper for QVariant alocator Int64 +func NewQVariantLong(data int64) *core.QVariant { + return core.NewQVariant1(data) +} + +// Pause used to show GUI tests +func Pause() { + time.Sleep(500 * time.Millisecond) +} + +// Longer pause used to diplay GUI tests +func PauseLong() { + time.Sleep(3 * time.Second) +} + +func ParsePMAPIError(err error, code int) error { + /* + if err == pmapi.ErrAPINotReachable { + code = ErrNoInternet + } + return errors.NewFromError(code, err) + */ + return nil +} + +// FIXME: Not working in test... +func WaitForEnter() { + log.Print("Press 'Enter' to continue...") + bufio.NewReader(os.Stdin).ReadBytes('\n') +} + +type Listener interface { + Add(string, chan<- string) +} + +func MakeAndRegisterEvent(eventListener Listener, event string) <-chan string { + ch := make(chan string) + eventListener.Add(event, ch) + return ch +} diff --git a/internal/frontend/qt/logs.h b/internal/frontend/qt-common/common.h similarity index 71% rename from internal/frontend/qt/logs.h rename to internal/frontend/qt-common/common.h index 48b392f5..699419fa 100644 --- a/internal/frontend/qt/logs.h +++ b/internal/frontend/qt-common/common.h @@ -10,8 +10,9 @@ extern "C" { #endif // C++ -void InstallMessageHandler(); -; + void InstallMessageHandler(); + void RegisterTypes(); + ; #ifdef __cplusplus } diff --git a/internal/frontend/qt-common/notification.go b/internal/frontend/qt-common/notification.go new file mode 100644 index 00000000..050545fc --- /dev/null +++ b/internal/frontend/qt-common/notification.go @@ -0,0 +1,40 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtcommon + +// Positions of notification bubble +const ( + TabAccount = 0 + TabSettings = 1 + TabHelp = 2 + TabQuit = 4 + TabUpdates = 100 + TabAddAccount = -1 +) + +// Notifier show bubble notification at postion marked by int +type Notifier interface { + NotifyBubble(int, string) +} + +// SendNotification unifies notification in GUI +func SendNotification(qml Notifier, tabIndex int, msg string) { + qml.NotifyBubble(tabIndex, msg) +} diff --git a/internal/frontend/qt-common/path_status.go b/internal/frontend/qt-common/path_status.go new file mode 100644 index 00000000..e6845f92 --- /dev/null +++ b/internal/frontend/qt-common/path_status.go @@ -0,0 +1,81 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +package qtcommon + +import ( + "io/ioutil" + "os" + "path/filepath" +) + +// PathStatus maps folder properties to flag +type PathStatus int + +// Definition of PathStatus flags +const ( + PathOK PathStatus = 1 << iota + PathEmptyPath + PathWrongPath + PathNotADir + PathWrongPermissions + PathDirEmpty +) + +// CheckPathStatus return PathStatus flag as int +func CheckPathStatus(path string) int { + stat := PathStatus(0) + // path is not empty + if path == "" { + stat |= PathEmptyPath + return int(stat) + } + // is dir + fi, err := os.Lstat(path) + if err != nil { + stat |= PathWrongPath + return int(stat) + } + if fi.IsDir() { + // can open + files, err := ioutil.ReadDir(path) + if err != nil { + stat |= PathWrongPermissions + return int(stat) + } + // empty folder + if len(files) == 0 { + stat |= PathDirEmpty + } + // can write + tmpFile := filepath.Join(path, "tmp") + for err == nil { + tmpFile += "tmp" + _, err = os.Lstat(tmpFile) + } + err = os.Mkdir(tmpFile, 0777) + if err != nil { + stat |= PathWrongPermissions + return int(stat) + } + os.Remove(tmpFile) + } else { + stat |= PathNotADir + } + stat |= PathOK + return int(stat) +} diff --git a/internal/frontend/qt-ie/Makefile.local b/internal/frontend/qt-ie/Makefile.local new file mode 100644 index 00000000..71a9058b --- /dev/null +++ b/internal/frontend/qt-ie/Makefile.local @@ -0,0 +1,60 @@ +QMLfiles=$(shell find ../qml/ -name "*.qml") $(shell find ../qml/ -name "qmldir") +FontAwesome=${CURDIR}/../share/fontawesome-webfont.ttf +ImageDir=${CURDIR}/../share/icons +Icons=$(shell find ${ImageDir} -name "*.png") +Icons+= share/images/folder_open.png share/images/envelope_open.png +MocDependencies= ./ui.go ./account_model.go ./folder_structure.go ./folder_functions.go +## EnumDependecies= ../backend/errors/errors.go ../backend/progress.go ../backend/source/enum.go ../frontend/enums.go + +all: ../qml/ImportExportUI/images moc.go ../qml/GuiIE.qml qmlcheck rcc.cpp + +## ./qml/GuiIE.qml: enums.sh ${EnumDependecies} +## ./enums.sh + +../qml/ProtonUI/fontawesome.ttf: + ln -sf ${FontAwesome} $@ +../qml/ProtonUI/images: + ln -sf ${ImageDir} $@ +../qml/ImportExportUI/images: + ln -sf ${ImageDir} $@ + +translate.ts: ${QMLfiles} + lupdate -recursive qml/ -ts $@ + +rcc.cpp: ${QMLfiles} ${Icons} resources.qrc + rm -f rcc.cpp rcc.qrc && qtrcc -o . + + +qmltest: + qmltestrunner -eventdelay 500 -import ../qml/ +qmlcheck: ../qml/ProtonUI/fontawesome.ttf ../qml/ImportExportUI/images ../qml/ProtonUI/images + qmlscene -verbose -I ../qml/ -f ../qml/tst_GuiIE.qml --quit +qmlpreview: ../qml/ProtonUI/fontawesome.ttf ../qml/ImportExportUI/images ../qml/ProtonUI/images + rm -f ../qml/*.qmlc ../qml/ProtonUI/*.qmlc ../qml/ImportExportUI/*.qmlc + qmlscene -verbose -I ../qml/ -f ../qml/tst_GuiIE.qml 2>&1 + +test: qmlcheck moc.go rcc.cpp + go test -v + +moc.go: ${MocDependencies} + qtmoc + +clean: + rm -rf linux/ + rm -rf darwin/ + rm -rf windows/ + rm -rf deploy/ + rm -f moc.cpp + rm -f moc.go + rm -f moc.h + rm -f moc_cgo*.go + rm -f moc_moc.h + rm -f rcc.cpp + rm -f rcc.qrc + rm -f rcc_cgo*.go + rm -f ../rcc.cpp + rm -f ../rcc.qrc + rm -f ../rcc_cgo*.go + rm -rf ../qml/ProtonUI/images + rm -f ../qml/ProtonUI/fontawesome.ttf + find ../qml -name *.qmlc -exec rm {} \; diff --git a/internal/frontend/qt-ie/README.md b/internal/frontend/qt-ie/README.md new file mode 100644 index 00000000..07dc9205 --- /dev/null +++ b/internal/frontend/qt-ie/README.md @@ -0,0 +1,55 @@ +# ProtonMail Import-Export Qt interface +Import-Export uses [Qt](https://www.qt.io) framework for creating appealing graphical +user interface. Package [therecipe/qt](https://github.com/therecipe/qt) is used +to implement Qt into [Go](https://www.goglang.com). + + +# For developers +The GUI is designed inside QML files. Communication with backend is done via +[frontend.go](./frontend.go). The API documentation is done via `go-doc`. + +## Setup +* if you don't have the system wide `go-1.8.1` download, install localy (e.g. + `~/build/go-1.8.1`) and setup: + + export GOROOT=~/build/go-1.8.1/go + export PATH=$GOROOT/bin:$PATH + +* go to your working directory and export `$GOPATH` + + export GOPATH=`Pwd` + mkdir -p $GOPATH/bin + export PATH=$PATH:$GOPATH/bin + + +* if you dont have system wide `Qt-5.8.0` + [download](https://download.qt.io/official_releases/qt/5.8/5.8.0/qt-opensource-linux-x64-5.8.0.run), + install locally (e.g. `~/build/qt/qt-5.8.0`) and setup: + + export QT_DIR=~/build/qt/qt-5.8.0 + export PATH=$QT_DIR/5.8/gcc_64/bin:$PATH + +* `Go-Qt` setup (installation is system dependent see + [therecipe/qt/README](https://github.com/therecipe/qt/blob/master/README.md) + for details) + + go get -u -v github.com/therecipe/qt/cmd/... + $GOPATH/bin/qtsetup + +## Compile +* it is necessary to compile the Qt-C++ with go for resources and meta-objects + + make -f Makefile.local + +* FIXME the rcc file is implicitly generated with `package main`. This needs to + be changed to `package qtie` manually +* check that user interface is working + + make -f Makefile.local test + +## Test + + make -f Makefile.local qmlpreview + +## Deploy +* before compilation of Import-Export it is necessary to run compilation of Qt-C++ part (done in makefile) diff --git a/internal/frontend/qt-ie/enums.go b/internal/frontend/qt-ie/enums.go new file mode 100644 index 00000000..4e21681b --- /dev/null +++ b/internal/frontend/qt-ie/enums.go @@ -0,0 +1,68 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "github.com/therecipe/qt/core" +) + +// Folder Type +const ( + FolderTypeSystem = "" + FolderTypeLabel = "label" + FolderTypeFolder = "folder" + FolderTypeExternal = "external" +) + +// Status +const ( + StatusNoInternet = "noInternet" + StatusCheckingInternet = "internetCheck" + StatusNewVersionAvailable = "oldVersion" + StatusUpToDate = "upToDate" + StatusForceUpdate = "forceupdate" +) + +// Constants for data map +const ( + // Account info + Account = int(core.Qt__UserRole) + 1<. + +// +build !nogui + +package qtie + +import ( + qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" + "github.com/therecipe/qt/core" +) + +// ErrorDetail stores information about email and error +type ErrorDetail struct { + MailSubject, MailDate, MailFrom, InputFolder, ErrorMessage string +} + +func init() { + ErrorListModel_QRegisterMetaType() +} + +// ErrorListModel to sending error details to Qt +type ErrorListModel struct { + core.QAbstractListModel + + // Qt list model + _ func() `constructor:"init"` + _ map[int]*core.QByteArray `property:"roles"` + _ int `property:"count"` + + Details []*ErrorDetail +} + +func (s *ErrorListModel) init() { + s.SetRoles(map[int]*core.QByteArray{ + MailSubject: qtcommon.NewQByteArrayFromString("mailSubject"), + MailDate: qtcommon.NewQByteArrayFromString("mailDate"), + MailFrom: qtcommon.NewQByteArrayFromString("mailFrom"), + InputFolder: qtcommon.NewQByteArrayFromString("inputFolder"), + ErrorMessage: qtcommon.NewQByteArrayFromString("errorMessage"), + }) + // basic QAbstractListModel mehods + s.ConnectData(s.data) + s.ConnectRowCount(s.rowCount) + s.ConnectColumnCount(s.columnCount) + s.ConnectRoleNames(s.roleNames) +} + +func (s *ErrorListModel) data(index *core.QModelIndex, role int) *core.QVariant { + if !index.IsValid() { + return core.NewQVariant() + } + + if index.Row() >= len(s.Details) { + return core.NewQVariant() + } + + var p = s.Details[index.Row()] + + switch role { + case MailSubject: + return qtcommon.NewQVariantString(p.MailSubject) + case MailDate: + return qtcommon.NewQVariantString(p.MailDate) + case MailFrom: + return qtcommon.NewQVariantString(p.MailFrom) + case InputFolder: + return qtcommon.NewQVariantString(p.InputFolder) + case ErrorMessage: + return qtcommon.NewQVariantString(p.ErrorMessage) + default: + return core.NewQVariant() + } +} + +func (s *ErrorListModel) rowCount(parent *core.QModelIndex) int { return len(s.Details) } +func (s *ErrorListModel) columnCount(parent *core.QModelIndex) int { return 1 } +func (s *ErrorListModel) roleNames() map[int]*core.QByteArray { return s.Roles() } + +// Add more errors to list +func (s *ErrorListModel) Add(more []*ErrorDetail) { + s.BeginInsertRows(core.NewQModelIndex(), len(s.Details), len(s.Details)) + s.Details = append(s.Details, more...) + s.SetCount(len(s.Details)) + s.EndInsertRows() +} + +// Clear removes all items in model +func (s *ErrorListModel) Clear() { + s.BeginRemoveRows(core.NewQModelIndex(), 0, len(s.Details)) + s.Details = s.Details[0:0] + s.SetCount(len(s.Details)) + s.EndRemoveRows() +} + +func (s *ErrorListModel) load(importLogFileName string) { + /* + err := backend.LoopDetailsInFile(importLogFileName, func(d *backend.MessageDetails) { + if d.MessageID != "" { // imported ok + return + } + ed := &ErrorDetail{ + MailSubject: d.Subject, + MailDate: d.Time, + MailFrom: d.From, + InputFolder: d.Folder, + ErrorMessage: d.Error, + } + s.Add([]*ErrorDetail{ed}) + }) + if err != nil { + log.Errorf("load import report from %q: %v", importLogFileName, err) + } + */ +} diff --git a/internal/frontend/qt-ie/export.go b/internal/frontend/qt-ie/export.go new file mode 100644 index 00000000..d85a3cbb --- /dev/null +++ b/internal/frontend/qt-ie/export.go @@ -0,0 +1,125 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "github.com/ProtonMail/proton-bridge/internal/transfer" +) + +const ( + TypeEML = "EML" + TypeMBOX = "MBOX" +) + +func (f *FrontendQt) LoadStructureForExport(addressOrID string) { + var err error + defer func() { + if err != nil { + f.showError(err) + f.Qml.ExportStructureLoadFinished(false) + } else { + f.Qml.ExportStructureLoadFinished(true) + } + }() + + if f.transfer, err = f.ie.GetEMLExporter(addressOrID, ""); err != nil { + return + } + + f.PMStructure.Clear() + sourceMailboxes, err := f.transfer.SourceMailboxes() + if err != nil { + return + } + for _, mbox := range sourceMailboxes { + rule := f.transfer.GetRule(mbox) + f.PMStructure.addEntry(newFolderInfo(mbox, rule)) + } + + f.PMStructure.transfer = f.transfer +} + +func (f *FrontendQt) StartExport(rootPath, login, fileType string, attachEncryptedBody bool) { + var target transfer.TargetProvider + if fileType == TypeEML { + target = transfer.NewEMLProvider(rootPath) + } else if fileType == TypeMBOX { + + target = transfer.NewMBOXProvider(rootPath) + } else { + log.Errorln("Wrong file format:", fileType) + return + } + f.transfer.ChangeTarget(target) + f.transfer.SetSkipEncryptedMessages(!attachEncryptedBody) + progress := f.transfer.Start() + f.setProgressManager(progress) + + /* + TODO + f.Qml.SetProgress(0.0) + f.Qml.SetProgressDescription(backend.ProgressInit) + f.Qml.SetTotal(0) + + settings := backend.ExportSettings{ + FilePath: fpath, + Login: login, + AttachEncryptedBody: attachEncryptedBody, + DateBegin: 0, + DateEnd: 0, + Labels: make(map[string]string), + } + + if fileType == "EML" { + settings.FileTypeID = backend.EMLFormat + } else if fileType == "MBOX" { + settings.FileTypeID = backend.MBOXFormat + } else { + log.Errorln("Wrong file format:", fileType) + return + } + + username, _, err := backend.ExtractUsername(login) + if err != nil { + log.Error("qtfrontend: cannot retrieve username from alias: ", err) + return + } + + settings.User, err = backend.ExtractCurrentUser(username) + if err != nil && !errors.IsCode(err, errors.ErrUnlockUser) { + return + } + + for _, entity := range f.PMStructure.entities { + if entity.IsFolderSelected { + settings.Labels[entity.FolderName] = entity.FolderId + } + } + + settings.DateBegin = f.PMStructure.GlobalOptions.FromDate + settings.DateEnd = f.PMStructure.GlobalOptions.ToDate + + settings.PM = backend.NewProcessManager() + f.setHandlers(settings.PM) + + log.Debugln("start export", settings.FilePath) + go backend.Export(f.panicHandler, settings) + */ +} diff --git a/internal/frontend/qt-ie/folder_functions.go b/internal/frontend/qt-ie/folder_functions.go new file mode 100644 index 00000000..7c156a72 --- /dev/null +++ b/internal/frontend/qt-ie/folder_functions.go @@ -0,0 +1,539 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "errors" + "strings" + + qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" + "github.com/ProtonMail/proton-bridge/internal/transfer" + "github.com/ProtonMail/proton-bridge/pkg/pmapi" + "github.com/therecipe/qt/core" +) + +const ( + GlobalOptionIndex = -1 +) + +var AllFolderInfoRoles = []int{ + FolderId, + FolderName, + FolderColor, + FolderType, + FolderEntries, + IsFolderSelected, + FolderFromDate, + FolderToDate, + TargetFolderID, + TargetLabelIDs, +} + +func getTargetHashes(mboxes []transfer.Mailbox) (targetFolderID, targetLabelIDs string) { + for _, targetMailbox := range mboxes { + if targetMailbox.IsExclusive { + targetFolderID = targetMailbox.Hash() + } else { + targetLabelIDs += targetMailbox.Hash() + ";" + } + } + + targetLabelIDs = strings.Trim(targetLabelIDs, ";") + return +} + +func isSystemMailbox(mbox transfer.Mailbox) bool { + return pmapi.IsSystemLabel(mbox.ID) +} + +func newFolderInfo(mbox transfer.Mailbox, rule *transfer.Rule) *FolderInfo { + targetFolderID, targetLabelIDs := getTargetHashes(rule.TargetMailboxes) + + entry := &FolderInfo{ + mailbox: mbox, + FolderEntries: 1, + FromDate: rule.FromTime, + ToDate: rule.ToTime, + IsFolderSelected: rule.Active, + TargetFolderID: targetFolderID, + TargetLabelIDs: targetLabelIDs, + } + + entry.FolderType = FolderTypeSystem + if !isSystemMailbox(mbox) { + if mbox.IsExclusive { + entry.FolderType = FolderTypeFolder + } else { + entry.FolderType = FolderTypeLabel + } + } + + return entry +} + +func (s *FolderStructure) saveRule(info *FolderInfo) error { + if s.transfer == nil { + return errors.New("missing transfer") + } + sourceMbox := info.mailbox + if !info.IsFolderSelected { + s.transfer.UnsetRule(sourceMbox) + return nil + } + allTargetMboxes, err := s.transfer.TargetMailboxes() + if err != nil { + return err + } + var targetMboxes []transfer.Mailbox + for _, target := range allTargetMboxes { + targetHash := target.Hash() + if info.TargetFolderID == targetHash || strings.Contains(info.TargetLabelIDs, targetHash) { + targetMboxes = append(targetMboxes, target) + } + } + + return s.transfer.SetRule(sourceMbox, targetMboxes, info.FromDate, info.ToDate) +} + +func (s *FolderInfo) updateTgtLblIDs(targetLabelsSet map[string]struct{}) { + targets := []string{} + for key := range targetLabelsSet { + targets = append(targets, key) + } + s.TargetLabelIDs = strings.Join(targets, ";") +} + +func (s *FolderInfo) clearTgtLblIDs() { + s.TargetLabelIDs = "" +} + +func (s *FolderInfo) AddTargetLabel(targetID string) { + if targetID == "" { + return + } + targetLabelsSet := s.getSetOfLabels() + targetLabelsSet[targetID] = struct{}{} + s.updateTgtLblIDs(targetLabelsSet) +} + +func (s *FolderInfo) RemoveTargetLabel(targetID string) { + if targetID == "" { + return + } + targetLabelsSet := s.getSetOfLabels() + delete(targetLabelsSet, targetID) + s.updateTgtLblIDs(targetLabelsSet) +} + +func (s *FolderInfo) IsType(askType string) bool { + return s.FolderType == askType +} + +func (s *FolderInfo) getSetOfLabels() (uniqSet map[string]struct{}) { + uniqSet = make(map[string]struct{}) + for _, label := range s.TargetLabelIDList() { + uniqSet[label] = struct{}{} + } + return +} + +func (s *FolderInfo) TargetLabelIDList() []string { + return strings.FieldsFunc( + s.TargetLabelIDs, + func(c rune) bool { return c == ';' }, + ) +} + +// Get data +func (s *FolderStructure) data(index *core.QModelIndex, role int) *core.QVariant { + row, isValid := index.Row(), index.IsValid() + if !isValid || row >= s.getCount() { + log.Warnln("Wrong index", isValid, row) + return core.NewQVariant() + } + + var f = s.get(row) + + switch role { + case FolderId: + return qtcommon.NewQVariantString(f.mailbox.Hash()) + case FolderName, int(core.Qt__DisplayRole): + return qtcommon.NewQVariantString(f.mailbox.Name) + case FolderColor: + return qtcommon.NewQVariantString(f.mailbox.Color) + case FolderType: + return qtcommon.NewQVariantString(f.FolderType) + case FolderEntries: + return qtcommon.NewQVariantInt(f.FolderEntries) + case FolderFromDate: + return qtcommon.NewQVariantLong(f.FromDate) + case FolderToDate: + return qtcommon.NewQVariantLong(f.ToDate) + case IsFolderSelected: + return qtcommon.NewQVariantBool(f.IsFolderSelected) + case TargetFolderID: + return qtcommon.NewQVariantString(f.TargetFolderID) + case TargetLabelIDs: + return qtcommon.NewQVariantString(f.TargetLabelIDs) + default: + log.Warnln("Wrong role", role) + return core.NewQVariant() + } +} + +// Get header data (table view, tree view) +func (s *FolderStructure) headerData(section int, orientation core.Qt__Orientation, role int) *core.QVariant { + if role != int(core.Qt__DisplayRole) { + return core.NewQVariant() + } + + if orientation == core.Qt__Horizontal { + return qtcommon.NewQVariantString("Column") + } + + return qtcommon.NewQVariantString("Row") +} + +// Flags is editable +func (s *FolderStructure) flags(index *core.QModelIndex) core.Qt__ItemFlag { + if !index.IsValid() { + return core.Qt__ItemIsEnabled + } + + // can do here also: core.NewQAbstractItemModelFromPointer(s.Pointer()).Flags(index) | core.Qt__ItemIsEditable + // or s.FlagsDefault(index) | core.Qt__ItemIsEditable + return core.Qt__ItemIsEnabled | core.Qt__ItemIsSelectable | core.Qt__ItemIsEditable +} + +// Set data +func (s *FolderStructure) setData(index *core.QModelIndex, value *core.QVariant, role int) bool { + log.Debugf("SET DATA %d", role) + if !index.IsValid() { + return false + } + if index.Row() < GlobalOptionIndex || index.Row() > s.getCount() || index.Column() != 1 { + return false + } + item := s.get(index.Row()) + t := true + switch role { + case FolderId, FolderType: + log. + WithField("structure", s). + WithField("row", index.Row()). + WithField("column", index.Column()). + WithField("role", role). + WithField("isEdit", role == int(core.Qt__EditRole)). + Warn("Set constant role forbiden") + case FolderName: + item.mailbox.Name = value.ToString() + case FolderColor: + item.mailbox.Color = value.ToString() + case FolderEntries: + item.FolderEntries = value.ToInt(&t) + case FolderFromDate: + item.FromDate = value.ToLongLong(&t) + case FolderToDate: + item.ToDate = value.ToLongLong(&t) + case IsFolderSelected: + item.IsFolderSelected = value.ToBool() + case TargetFolderID: + item.TargetFolderID = value.ToString() + case TargetLabelIDs: + item.TargetLabelIDs = value.ToString() + default: + log.Debugln("uknown role ", s, index.Row(), index.Column(), role, role == int(core.Qt__EditRole)) + return false + } + s.changedEntityRole(index.Row(), index.Row(), role) + return true +} + +// Dimension of model: number of rows is equivalent to number of items in list +func (s *FolderStructure) rowCount(parent *core.QModelIndex) int { + return s.getCount() +} + +func (s *FolderStructure) getCount() int { + return len(s.entities) +} + +// Returns names of available item properties +func (s *FolderStructure) roleNames() map[int]*core.QByteArray { + return s.Roles() +} + +// Clear removes all items in model +func (s *FolderStructure) Clear() { + s.BeginResetModel() + if s.getCount() != 0 { + s.entities = []*FolderInfo{} + } + + s.GlobalOptions = FolderInfo{ + mailbox: transfer.Mailbox{ + Name: "=", + }, + FromDate: 0, + ToDate: 0, + TargetFolderID: "", + TargetLabelIDs: "", + } + s.EndResetModel() +} + +// Method connected to addEntry slot +func (s *FolderStructure) addEntry(entry *FolderInfo) { + s.insertEntry(entry, s.getCount()) +} + +// NewUniqId which is not in map yet. +func (s *FolderStructure) newUniqId() (name string) { + name = s.GlobalOptions.mailbox.Name + mbox := transfer.Mailbox{Name: name} + for newVal := byte(name[0]); true; newVal++ { + mbox.Name = string([]byte{newVal}) + if s.getRowById(mbox.Hash()) < GlobalOptionIndex { + return + } + } + return +} + +// Method connected to addEntry slot +func (s *FolderStructure) insertEntry(entry *FolderInfo, i int) { + s.BeginInsertRows(core.NewQModelIndex(), i, i) + s.entities = append(s.entities[:i], append([]*FolderInfo{entry}, s.entities[i:]...)...) + s.EndInsertRows() + // update global if conflict + if entry.mailbox.Hash() == s.GlobalOptions.mailbox.Hash() { + globalName := s.newUniqId() + s.GlobalOptions.mailbox.Name = globalName + } +} + +func (s *FolderStructure) GetInfo(row int) FolderInfo { + return *s.get(row) +} + +func (s *FolderStructure) changedEntityRole(rowStart int, rowEnd int, roles ...int) { + if rowStart < GlobalOptionIndex || rowEnd < GlobalOptionIndex { + return + } + if rowStart < 0 || rowStart >= s.getCount() { + rowStart = 0 + } + if rowEnd < 0 || rowEnd >= s.getCount() { + rowEnd = s.getCount() + } + if rowStart > rowEnd { + tmp := rowStart + rowStart = rowEnd + rowEnd = tmp + } + indexStart := s.Index(rowStart, 0, core.NewQModelIndex()) + indexEnd := s.Index(rowEnd, 0, core.NewQModelIndex()) + s.updateSelection(indexStart, indexEnd, roles) + s.DataChanged(indexStart, indexEnd, roles) +} + +func (s *FolderStructure) setFolderSelection(id string, toSelect bool) { + log.Debugf("set folder selection %q %b", id, toSelect) + i := s.getRowById(id) + // + info := s.get(i) + before := info.IsFolderSelected + info.IsFolderSelected = toSelect + if err := s.saveRule(info); err != nil { + s.get(i).IsFolderSelected = before + log.WithError(err).WithField("id", id).WithField("toSelect", toSelect).Error("Cannot set selection") + return + } + // + s.changedEntityRole(i, i, IsFolderSelected) +} + +func (s *FolderStructure) setTargetFolderID(id, target string) { + log.Debugf("set targetFolderID %q %q", id, target) + i := s.getRowById(id) + // + info := s.get(i) + //s.get(i).TargetFolderID = target + before := info.TargetFolderID + info.TargetFolderID = target + if err := s.saveRule(info); err != nil { + info.TargetFolderID = before + log.WithError(err).WithField("id", id).WithField("target", target).Error("Cannot set target") + return + } + // + s.changedEntityRole(i, i, TargetFolderID) + if target == "" { // do not import + before := info.TargetLabelIDs + info.clearTgtLblIDs() + if err := s.saveRule(info); err != nil { + info.TargetLabelIDs = before + log.WithError(err).WithField("id", id).WithField("target", target).Error("Cannot set target") + return + } + s.changedEntityRole(i, i, TargetLabelIDs) + } +} + +func (s *FolderStructure) addTargetLabelID(id, label string) { + log.Debugf("add target label id %q %q", id, label) + if label == "" { + return + } + i := s.getRowById(id) + info := s.get(i) + before := info.TargetLabelIDs + info.AddTargetLabel(label) + if err := s.saveRule(info); err != nil { + info.TargetLabelIDs = before + log.WithError(err).WithField("id", id).WithField("label", label).Error("Cannot add label") + return + } + s.changedEntityRole(i, i, TargetLabelIDs) +} + +func (s *FolderStructure) removeTargetLabelID(id, label string) { + log.Debugf("remove label id %q %q", id, label) + if label == "" { + return + } + i := s.getRowById(id) + info := s.get(i) + before := info.TargetLabelIDs + info.RemoveTargetLabel(label) + if err := s.saveRule(info); err != nil { + info.TargetLabelIDs = before + log.WithError(err).WithField("id", id).WithField("label", label).Error("Cannot remove label") + return + } + s.changedEntityRole(i, i, TargetLabelIDs) +} + +func (s *FolderStructure) setFromToDate(id string, from, to int64) { + log.Debugf("set from to date %q %d %d", id, from, to) + i := s.getRowById(id) + info := s.get(i) + beforeFrom := info.FromDate + beforeTo := info.ToDate + info.FromDate = from + info.ToDate = to + if err := s.saveRule(info); err != nil { + info.FromDate = beforeFrom + info.ToDate = beforeTo + log.WithError(err).WithField("id", id).WithField("from", from).WithField("to", to).Error("Cannot set date") + return + } + s.changedEntityRole(i, i, FolderFromDate, FolderToDate) +} + +func (s *FolderStructure) selectType(folderType string, toSelect bool) { + log.Debugf("set type %q %b", folderType, toSelect) + iFirst, iLast := -1, -1 + for i, entity := range s.entities { + if entity.IsType(folderType) { + if iFirst == -1 { + iFirst = i + } + before := entity.IsFolderSelected + entity.IsFolderSelected = toSelect + if err := s.saveRule(entity); err != nil { + entity.IsFolderSelected = before + log.WithError(err).WithField("i", i).WithField("type", folderType).WithField("toSelect", toSelect).Error("Cannot select type") + } + iLast = i + } + } + if iFirst != -1 { + s.changedEntityRole(iFirst, iLast, IsFolderSelected) + } +} + +func (s *FolderStructure) updateSelection(topLeft *core.QModelIndex, bottomRight *core.QModelIndex, roles []int) { + for _, role := range roles { + switch role { + case IsFolderSelected: + s.SetSelectedFolders(true) + s.SetSelectedLabels(true) + s.SetAtLeastOneSelected(false) + for _, entity := range s.entities { + if entity.IsFolderSelected { + s.SetAtLeastOneSelected(true) + } else { + if entity.IsType(FolderTypeFolder) { + s.SetSelectedFolders(false) + } + if entity.IsType(FolderTypeLabel) { + s.SetSelectedLabels(false) + } + } + if !s.IsSelectedFolders() && !s.IsSelectedLabels() && s.IsAtLeastOneSelected() { + break + } + } + default: + } + } +} + +func (s *FolderStructure) hasFolderWithName(name string) bool { + for _, entity := range s.entities { + if entity.mailbox.Name == name { + return true + } + } + return false +} + +func (s *FolderStructure) getRowById(id string) (row int) { + for row = GlobalOptionIndex; row < s.getCount(); row++ { + if id == s.get(row).mailbox.Hash() { + return + } + } + row = GlobalOptionIndex - 1 + return +} + +func (s *FolderStructure) hasTarget() bool { + for row := 0; row < s.getCount(); row++ { + if s.get(row).TargetFolderID != "" { + return true + } + } + return false +} + +// Getter for account info pointer +// index out of array length returns empty folder info to avoid segfault +// index == GlobalOptionIndex is set to access global options +func (s *FolderStructure) get(index int) *FolderInfo { + if index < GlobalOptionIndex || index >= s.getCount() { + return &FolderInfo{} + } + if index == GlobalOptionIndex { + return &s.GlobalOptions + } + return s.entities[index] +} diff --git a/internal/frontend/qt-ie/folder_structure.go b/internal/frontend/qt-ie/folder_structure.go new file mode 100644 index 00000000..ed11cf36 --- /dev/null +++ b/internal/frontend/qt-ie/folder_structure.go @@ -0,0 +1,196 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +// TODO: +// Proposal for new structure +// It will be a bit more memory but much better performance +// * Rules: +// * rules []Rule /QAbstracItemModel/ +// * globalFromDate int64 +// * globalToDate int64 +// * globalLabel Mbox +// * targetPath string +// * filterEncryptedBodies bool +// * Rule +// * sourceMbox: Mbox +// * targetFolders: []Mbox /QAbstracItemModel/ (all available target folders) +// * targetLabels: []Mbox /QAbstracItemModel/ (all available target labels) +// * selectedLabelColors: QStringList (need reset context on change) (show label list) +// * fromDate int64 +// * toDate int64 +// * Mbox +// * IsActive bool (show checkox) +// * Name string (show name) +// * Type string (show icon) +// * Color string (show icon) +// +// Biggest update: add folder or label for all roles update target models + +import ( + qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" + "github.com/ProtonMail/proton-bridge/internal/transfer" + "github.com/therecipe/qt/core" +) + +// FolderStructure model providing container for items (folder info) to QML +// +// QML ListView connects the model from Go and it shows item (entities) +// information. +// +// Copied and edited from `github.com/therecipe/qt/internal/examples/sailfish/listview` +// +// NOTE: When implementing a model it is important to remember that QAbstractItemModel does not store any data itself !!!! +// see https://doc.qt.io/qt-5/model-view-programming.html#designing-a-model +type FolderStructure struct { + core.QAbstractListModel + + // QtObject Constructor + _ func() `constructor:"init"` + + // List of item properties + // + // All available item properties are inside the map + _ map[int]*core.QByteArray `property:"roles"` + + // The data storage + // + // The slice with all entities. It is not accessed directly but using + // `data(index,role)` + entities []*FolderInfo + GlobalOptions FolderInfo + + transfer *transfer.Transfer + + // Global Folders/Labels selection flag, use setter from QML + _ bool `property:"selectedLabels"` + _ bool `property:"selectedFolders"` + _ bool `property:"atLeastOneSelected"` + + // Getters (const) + _ func() int `slot:"getCount"` + _ func(index int) string `slot:"getID"` + _ func(id string) string `slot:"getName"` + _ func(id string) string `slot:"getType"` + _ func(id string) string `slot:"getColor"` + _ func(id string) int64 `slot:"getFrom"` + _ func(id string) int64 `slot:"getTo"` + _ func(id string) string `slot:"getTargetLabelIDs"` + _ func(name string) bool `slot:"hasFolderWithName"` + _ func() bool `slot:"hasTarget"` + + // TODO get folders + // TODO get labels + // TODO get selected labels + // TODO get selected folder + + // Setters (emits DataChanged) + _ func(fileType string, toSelect bool) `slot:"selectType"` + _ func(id string, toSelect bool) `slot:"setFolderSelection"` + _ func(id string, target string) `slot:"setTargetFolderID"` + _ func(id string, label string) `slot:"addTargetLabelID"` + _ func(id string, label string) `slot:"removeTargetLabelID"` + _ func(id string, from, to int64) `slot:"setFromToDate"` +} + +// FolderInfo is the element of model +// +// It contains all data for one structure entry +type FolderInfo struct { + /* + FolderId string + FolderFullPath string + FolderColor string + FolderFullName string + */ + mailbox transfer.Mailbox // TODO how to reference from qml source mailbox to go target mailbox + FolderType string + FolderEntries int // todo remove + IsFolderSelected bool + FromDate int64 // Unix seconds + ToDate int64 // Unix seconds + TargetFolderID string // target ID TODO: this will be hash + TargetLabelIDs string // semicolon separated list of label ID same here +} + +// Registration of new metatype before creating instance +// +// NOTE: check it is run once per program. write a log +func init() { + FolderStructure_QRegisterMetaType() +} + +// Constructor +// +// Creates the map for item properties and connects the methods +func (s *FolderStructure) init() { + s.SetRoles(map[int]*core.QByteArray{ + FolderId: qtcommon.NewQByteArrayFromString("folderId"), + FolderName: qtcommon.NewQByteArrayFromString("folderName"), + FolderColor: qtcommon.NewQByteArrayFromString("folderColor"), + FolderType: qtcommon.NewQByteArrayFromString("folderType"), + FolderEntries: qtcommon.NewQByteArrayFromString("folderEntries"), + IsFolderSelected: qtcommon.NewQByteArrayFromString("isFolderSelected"), + FolderFromDate: qtcommon.NewQByteArrayFromString("fromDate"), + FolderToDate: qtcommon.NewQByteArrayFromString("toDate"), + TargetFolderID: qtcommon.NewQByteArrayFromString("targetFolderID"), + TargetLabelIDs: qtcommon.NewQByteArrayFromString("targetLabelIDs"), + }) + + // basic QAbstractListModel mehods + s.ConnectGetCount(s.getCount) + s.ConnectRowCount(s.rowCount) + s.ConnectColumnCount(func(parent *core.QModelIndex) int { return 1 }) // for list it should be always 1 + s.ConnectData(s.data) + s.ConnectHeaderData(s.headerData) + s.ConnectRoleNames(s.roleNames) + // Editable QAbstractListModel needs: https://doc.qt.io/qt-5/model-view-programming.html#an-editable-model + s.ConnectSetData(s.setData) + s.ConnectFlags(s.flags) + + // Custom FolderStructure slots to export + + // Getters (const) + s.ConnectGetID(func(row int) string { return s.get(row).mailbox.Hash() }) + s.ConnectGetType(func(id string) string { row := s.getRowById(id); return s.get(row).FolderType }) + s.ConnectGetName(func(id string) string { row := s.getRowById(id); return s.get(row).mailbox.Name }) + s.ConnectGetColor(func(id string) string { row := s.getRowById(id); return s.get(row).mailbox.Color }) + s.ConnectGetFrom(func(id string) int64 { row := s.getRowById(id); return s.get(row).FromDate }) + s.ConnectGetTo(func(id string) int64 { row := s.getRowById(id); return s.get(row).ToDate }) + s.ConnectGetTargetLabelIDs(func(id string) string { row := s.getRowById(id); return s.get(row).TargetLabelIDs }) + s.ConnectHasFolderWithName(s.hasFolderWithName) + s.ConnectHasTarget(s.hasTarget) + + // Setters (emits DataChanged) + s.ConnectSelectType(s.selectType) + s.ConnectSetFolderSelection(s.setFolderSelection) + s.ConnectSetTargetFolderID(s.setTargetFolderID) + s.ConnectAddTargetLabelID(s.addTargetLabelID) + s.ConnectRemoveTargetLabelID(s.removeTargetLabelID) + s.ConnectSetFromToDate(s.setFromToDate) + + s.GlobalOptions = FolderInfo{ + mailbox: transfer.Mailbox{Name: "="}, + FromDate: 0, + ToDate: 0, + TargetFolderID: "", + TargetLabelIDs: "", + } +} diff --git a/internal/frontend/qt-ie/folder_structure_test.go b/internal/frontend/qt-ie/folder_structure_test.go new file mode 100644 index 00000000..50207f64 --- /dev/null +++ b/internal/frontend/qt-ie/folder_structure_test.go @@ -0,0 +1,65 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "testing" +) + +func hasNumberOfLabels(tb testing.TB, folder *FolderInfo, expected int) { + if current := len(folder.TargetLabelIDList()); current != expected { + tb.Error("Folder has wrong number of labels. Expected", expected, "has", current, " labels", folder.TargetLabelIDs) + } +} + +func labelStringEquals(tb testing.TB, folder *FolderInfo, expected string) { + if current := folder.TargetLabelIDs; current != expected { + tb.Error("Folder returned wrong labels. Expected", expected, "has", current, " labels", folder.TargetLabelIDs) + } +} + +func TestLabelInfoUniqSet(t *testing.T) { + folder := &FolderInfo{} + labelStringEquals(t, folder, "") + hasNumberOfLabels(t, folder, 0) + // add label + folder.AddTargetLabel("blah") + hasNumberOfLabels(t, folder, 1) + labelStringEquals(t, folder, "blah") + // + folder.AddTargetLabel("blah___") + hasNumberOfLabels(t, folder, 2) + labelStringEquals(t, folder, "blah;blah___") + // add same label + folder.AddTargetLabel("blah") + hasNumberOfLabels(t, folder, 2) + // remove label + folder.RemoveTargetLabel("blah") + hasNumberOfLabels(t, folder, 1) + // + folder.AddTargetLabel("blah___") + hasNumberOfLabels(t, folder, 1) + // remove same label + folder.RemoveTargetLabel("blah") + hasNumberOfLabels(t, folder, 1) + // add again label + folder.AddTargetLabel("blah") + hasNumberOfLabels(t, folder, 2) +} diff --git a/internal/frontend/qt-ie/frontend.go b/internal/frontend/qt-ie/frontend.go new file mode 100644 index 00000000..6b6305f5 --- /dev/null +++ b/internal/frontend/qt-ie/frontend.go @@ -0,0 +1,497 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "errors" + "os" + "strconv" + "time" + + "github.com/ProtonMail/proton-bridge/internal/events" + qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" + "github.com/ProtonMail/proton-bridge/internal/frontend/types" + "github.com/ProtonMail/proton-bridge/internal/transfer" + "github.com/ProtonMail/proton-bridge/pkg/config" + "github.com/ProtonMail/proton-bridge/pkg/listener" + "github.com/ProtonMail/proton-bridge/pkg/updates" + + "github.com/therecipe/qt/core" + "github.com/therecipe/qt/gui" + "github.com/therecipe/qt/qml" + "github.com/therecipe/qt/widgets" + + "github.com/sirupsen/logrus" + "github.com/skratchdot/open-golang/open" +) + +var log = logrus.WithField("pkg", "frontend-qt-ie") + +// FrontendQt is API between Import-Export and Qt +// +// With this interface it is possible to control Qt-Gui interface using pointers to +// Qt and QML objects. QML signals and slots are connected via methods of GoQMLInterface. +type FrontendQt struct { + panicHandler types.PanicHandler + config *config.Config + eventListener listener.Listener + updates types.Updater + ie types.ImportExporter + + App *widgets.QApplication // Main Application pointer + View *qml.QQmlApplicationEngine // QML engine pointer + MainWin *core.QObject // Pointer to main window inside QML + Qml *GoQMLInterface // Object accessible from both Go and QML for methods and signals + Accounts qtcommon.Accounts // Providing data for accounts ListView + + programName string // Program name + programVersion string // Program version + buildVersion string // Program build version + + PMStructure *FolderStructure // Providing data for account labels and folders for ProtonMail account + ExternalStructure *FolderStructure // Providing data for account labels and folders for MBOX, EML or external IMAP account + ErrorList *ErrorListModel // Providing data for error reporting + + transfer *transfer.Transfer + + notifyHasNoKeychain bool +} + +// New is constructor for Import-Export Qt-Go interface +func New( + version, buildVersion string, + panicHandler types.PanicHandler, + config *config.Config, + eventListener listener.Listener, + updates types.Updater, + ie types.ImportExporter, +) *FrontendQt { + f := &FrontendQt{ + panicHandler: panicHandler, + config: config, + programName: "ProtonMail Import-Export", + programVersion: "v" + version, + eventListener: eventListener, + buildVersion: buildVersion, + updates: updates, + ie: ie, + } + + // Nicer string for OS + currentOS := core.QSysInfo_PrettyProductName() + ie.SetCurrentOS(currentOS) + + log.Debugf("New Qt frontend: %p", f) + return f +} + +// IsAppRestarting for Import-Export is always false i.e never restarts +func (s *FrontendQt) IsAppRestarting() bool { + return false +} + +// Loop function for Import-Export interface. It runs QtExecute in main thread +// with no additional function. +func (s *FrontendQt) Loop(setupError error) (err error) { + if setupError != nil { + s.notifyHasNoKeychain = true + } + go func() { + defer s.panicHandler.HandlePanic() + s.watchEvents() + }() + err = s.QtExecute(func(s *FrontendQt) error { return nil }) + return err +} + +func (s *FrontendQt) watchEvents() { + internetOffCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.InternetOffEvent) + internetOnCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.InternetOnEvent) + restartBridgeCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.RestartBridgeEvent) + addressChangedCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.AddressChangedEvent) + addressChangedLogoutCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.AddressChangedLogoutEvent) + logoutCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.LogoutEvent) + updateApplicationCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.UpgradeApplicationEvent) + newUserCh := qtcommon.MakeAndRegisterEvent(s.eventListener, events.UserRefreshEvent) + for { + select { + case <-internetOffCh: + s.Qml.SetConnectionStatus(false) + case <-internetOnCh: + s.Qml.SetConnectionStatus(true) + case <-restartBridgeCh: + s.Qml.SetIsRestarting(true) + s.App.Quit() + case address := <-addressChangedCh: + s.Qml.NotifyAddressChanged(address) + case address := <-addressChangedLogoutCh: + s.Qml.NotifyAddressChangedLogout(address) + case userID := <-logoutCh: + user, err := s.ie.GetUser(userID) + if err != nil { + return + } + s.Qml.NotifyLogout(user.Username()) + case <-updateApplicationCh: + s.Qml.ProcessFinished() + s.Qml.NotifyUpdate() + case <-newUserCh: + s.Qml.LoadAccounts() + } + } +} + +func (s *FrontendQt) qtSetupQmlAndStructures() { + s.App = widgets.NewQApplication(len(os.Args), os.Args) + // view + s.View = qml.NewQQmlApplicationEngine(s.App) + // Add Go-QML Import-Export + s.Qml = NewGoQMLInterface(nil) + s.Qml.SetFrontend(s) // provides access + s.View.RootContext().SetContextProperty("go", s.Qml) + // Add AccountsModel + s.Accounts.SetupAccounts(s.Qml, s.ie) + s.View.RootContext().SetContextProperty("accountsModel", s.Accounts.Model) + + // Add ProtonMail FolderStructure + s.PMStructure = NewFolderStructure(nil) + s.View.RootContext().SetContextProperty("structurePM", s.PMStructure) + + // Add external FolderStructure + s.ExternalStructure = NewFolderStructure(nil) + s.View.RootContext().SetContextProperty("structureExternal", s.ExternalStructure) + + // Add error list modal + s.ErrorList = NewErrorListModel(nil) + s.View.RootContext().SetContextProperty("errorList", s.ErrorList) + s.Qml.ConnectLoadImportReports(s.ErrorList.load) + + // Import path and load QML files + s.View.AddImportPath("qrc:///") + s.View.Load(core.NewQUrl3("qrc:/uiie.qml", 0)) + + // TODO set the first start flag + log.Error("Get FirstStart: Not implemented") + //if prefs.Get(prefs.FirstStart) == "true" { + if false { + s.Qml.SetIsFirstStart(true) + } else { + s.Qml.SetIsFirstStart(false) + } + + // Notify user about error during initialization. + if s.notifyHasNoKeychain { + s.Qml.NotifyHasNoKeychain() + } +} + +// QtExecute in main for starting Qt application +// +// It is needed to have just one Qt application per program (at least per same +// thread). This functions reads the main user interface defined in QML files. +// The files are appended to library by Qt-QRC. +func (s *FrontendQt) QtExecute(Procedure func(*FrontendQt) error) error { + qtcommon.QtSetupCoreAndControls(s.programName, s.programVersion) + s.qtSetupQmlAndStructures() + // Check QML is loaded properly + if len(s.View.RootObjects()) == 0 { + //return errors.New(errors.ErrQApplication, "QML not loaded properly") + return errors.New("QML not loaded properly") + } + // Obtain main window (need for invoke method) + s.MainWin = s.View.RootObjects()[0] + // Injected procedure for out-of-main-thread applications + if err := Procedure(s); err != nil { + return err + } + // Loop + if ret := gui.QGuiApplication_Exec(); ret != 0 { + //err := errors.New(errors.ErrQApplication, "Event loop ended with return value: %v", string(ret)) + err := errors.New("Event loop ended with return value: " + string(ret)) + log.Warnln("QGuiApplication_Exec: ", err) + return err + } + log.Debug("Closing...") + log.Error("Set FirstStart: Not implemented") + //prefs.Set(prefs.FirstStart, "false") + return nil +} + +func (s *FrontendQt) openLogs() { + go open.Run(s.config.GetLogDir()) +} + +func (s *FrontendQt) openReport() { + go open.Run(s.Qml.ImportLogFileName()) +} + +func (s *FrontendQt) openDownloadLink() { + go open.Run(s.updates.GetDownloadLink()) +} + +func (s *FrontendQt) sendImportReport(address, reportFile string) (isOK bool) { + /* + accname := "[No account logged in]" + if s.Accounts.Count() > 0 { + accname = s.Accounts.get(0).Account() + } + + basename := filepath.Base(reportFile) + req := pmapi.ReportReq{ + OS: core.QSysInfo_ProductType(), + OSVersion: core.QSysInfo_PrettyProductName(), + Title: "[Import Export] Import report: " + basename, + Description: "Sending import report file in attachment.", + Username: accname, + Email: address, + } + + report, err := os.Open(reportFile) + if err != nil { + log.Errorln("report file open:", err) + isOK = false + } + req.AddAttachment("log", basename, report) + + c := pmapi.NewClient(backend.APIConfig, "import_reporter") + err = c.Report(req) + if err != nil { + log.Errorln("while sendReport:", err) + isOK = false + return + } + log.Infof("Report %q send successfully", basename) + isOK = true + */ + return false +} + +// sendBug is almost idetical to bridge +func (s *FrontendQt) sendBug(description, emailClient, address string) (isOK bool) { + isOK = true + var accname = "No account logged in" + if s.Accounts.Model.Count() > 0 { + accname = s.Accounts.Model.Get(0).Account() + } + if err := s.ie.ReportBug( + core.QSysInfo_ProductType(), + core.QSysInfo_PrettyProductName(), + description, + accname, + address, + emailClient, + ); err != nil { + log.Errorln("while sendBug:", err) + isOK = false + } + return +} + +// checkInternet is almost idetical to bridge +func (s *FrontendQt) checkInternet() { + s.Qml.SetConnectionStatus(s.ie.CheckConnection() == nil) +} + +func (s *FrontendQt) showError(err error) { + code := 0 // TODO err.Code() + s.Qml.SetErrorDescription(err.Error()) + log.WithField("code", code).Errorln(err.Error()) + s.Qml.NotifyError(code) +} + +func (s *FrontendQt) emitEvent(evType, msg string) { + s.eventListener.Emit(evType, msg) +} + +func (s *FrontendQt) setProgressManager(progress *transfer.Progress) { + s.Qml.ConnectPauseProcess(func() { progress.Pause("user") }) + s.Qml.ConnectResumeProcess(progress.Resume) + s.Qml.ConnectCancelProcess(func(clearUnfinished bool) { + // TODO clear unfinished + progress.Stop() + }) + + go func() { + defer func() { + s.Qml.DisconnectPauseProcess() + s.Qml.DisconnectResumeProcess() + s.Qml.DisconnectCancelProcess() + s.Qml.SetProgress(1) + }() + + //TODO get log file (in old code it was here, but this is ugly place probably somewhere else) + updates := progress.GetUpdateChannel() + for range updates { + if progress.IsStopped() { + break + } + failed, imported, _, _, total := progress.GetCounts() + if total != 0 { // udate total + s.Qml.SetTotal(int(total)) + } + s.Qml.SetProgressFails(int(failed)) + s.Qml.SetProgressDescription(progress.PauseReason()) // TODO add description when changing folders? + if total > 0 { + newProgress := float32(imported+failed) / float32(total) + if newProgress >= 0 && newProgress != s.Qml.Progress() { + s.Qml.SetProgress(newProgress) + s.Qml.ProgressChanged(newProgress) + } + } + } + + // TODO fatal error? + }() +} + +// StartUpdate is identical to bridge +func (s *FrontendQt) StartUpdate() { + progress := make(chan updates.Progress) + go func() { // Update progress in QML. + defer s.panicHandler.HandlePanic() + for current := range progress { + s.Qml.SetProgress(current.Processed) + s.Qml.SetProgressDescription(strconv.Itoa(current.Description)) + // Error happend + if current.Err != nil { + log.Error("update progress: ", current.Err) + s.Qml.UpdateFinished(true) + return + } + // Finished everything OK. + if current.Description >= updates.InfoQuitApp { + s.Qml.UpdateFinished(false) + time.Sleep(3 * time.Second) // Just notify. + s.Qml.SetIsRestarting(current.Description == updates.InfoRestartApp) + s.App.Quit() + return + } + } + }() + go func() { + defer s.panicHandler.HandlePanic() + s.updates.StartUpgrade(progress) + }() +} + +// isNewVersionAvailable is identical to bridge +// return 0 when local version is fine +// return 1 when new version is available +func (s *FrontendQt) isNewVersionAvailable(showMessage bool) { + go func() { + defer s.Qml.ProcessFinished() + isUpToDate, latestVersionInfo, err := s.updates.CheckIsUpToDate() + if err != nil { + log.Warnln("Cannot retrieve version info: ", err) + s.checkInternet() + return + } + s.Qml.SetConnectionStatus(true) // if we are here connection is ok + if isUpToDate { + s.Qml.SetUpdateState(StatusUpToDate) + if showMessage { + s.Qml.NotifyVersionIsTheLatest() + } + return + } + s.Qml.SetNewversion(latestVersionInfo.Version) + s.Qml.SetChangelog(latestVersionInfo.ReleaseNotes) + s.Qml.SetBugfixes(latestVersionInfo.ReleaseFixedBugs) + s.Qml.SetLandingPage(latestVersionInfo.LandingPage) + s.Qml.SetDownloadLink(latestVersionInfo.GetDownloadLink()) + s.Qml.SetUpdateState(StatusNewVersionAvailable) + }() +} + +func (s *FrontendQt) resetSource() { + if s.transfer != nil { + s.transfer.ResetRules() + if err := s.loadStructuresForImport(); err != nil { + log.WithError(err).Error("Cannot reload structures after reseting rules.") + } + } +} + +// getLocalVersionInfo is identical to bridge. +func (s *FrontendQt) getLocalVersionInfo() { + defer s.Qml.ProcessFinished() + localVersion := s.updates.GetLocalVersion() + s.Qml.SetNewversion(localVersion.Version) + s.Qml.SetChangelog(localVersion.ReleaseNotes) + s.Qml.SetBugfixes(localVersion.ReleaseFixedBugs) +} + +// LeastUsedColor is intended to return color for creating a new inbox or label. +func (s *FrontendQt) leastUsedColor() string { + if s.transfer == nil { + log.Errorln("Getting least used color before transfer exist.") + return "#7272a7" + } + + m, err := s.transfer.TargetMailboxes() + + if err != nil { + log.Errorln("Getting least used color:", err) + s.showError(err) + } + + return transfer.LeastUsedColor(m) +} + +// createLabelOrFolder performs an IE target mailbox creation. +func (s *FrontendQt) createLabelOrFolder(email, name, color string, isLabel bool, sourceID string) bool { + // Prepare new mailbox. + m := transfer.Mailbox{ + Name: name, + Color: color, + IsExclusive: !isLabel, + } + + // Select least used color if no color given. + if m.Color == "" { + m.Color = s.leastUsedColor() + } + + // Create mailbox. + newLabel, err := s.transfer.CreateTargetMailbox(m) + + if err != nil { + log.Errorln("Folder/Label creating:", err) + s.showError(err) + return false + } + + // TODO: notify UI of newly added folders/labels + /*errc := s.PMStructure.Load(email, false) + if errc != nil { + s.showError(errc) + return false + }*/ + + if sourceID != "" { + if isLabel { + s.ExternalStructure.addTargetLabelID(sourceID, newLabel.ID) + } else { + s.ExternalStructure.setTargetFolderID(sourceID, newLabel.ID) + } + } + + return true +} diff --git a/internal/frontend/qt-ie/frontend_nogui.go b/internal/frontend/qt-ie/frontend_nogui.go new file mode 100644 index 00000000..673800b8 --- /dev/null +++ b/internal/frontend/qt-ie/frontend_nogui.go @@ -0,0 +1,55 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build nogui + +package qtie + +import ( + "fmt" + "net/http" + + "github.com/ProtonMail/proton-bridge/internal/frontend/types" + "github.com/ProtonMail/proton-bridge/pkg/config" + "github.com/ProtonMail/proton-bridge/pkg/listener" + "github.com/sirupsen/logrus" +) + +var log = logrus.WithField("pkg", "frontend-nogui") //nolint[gochecknoglobals] + +type FrontendHeadless struct{} + +func (s *FrontendHeadless) Loop(credentialsError error) error { + log.Info("Check status on localhost:8081") + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "IE is running") + }) + return http.ListenAndServe(":8081", nil) +} + +func (s *FrontendHeadless) IsAppRestarting() bool { return false } + +func New( + version, buildVersion string, + panicHandler types.PanicHandler, + config *config.Config, + eventListener listener.Listener, + updates types.Updater, + ie types.ImportExporter, +) *FrontendHeadless { + return &FrontendHeadless{} +} diff --git a/internal/frontend/qt-ie/import.go b/internal/frontend/qt-ie/import.go new file mode 100644 index 00000000..058dff58 --- /dev/null +++ b/internal/frontend/qt-ie/import.go @@ -0,0 +1,89 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import "github.com/ProtonMail/proton-bridge/internal/transfer" + +// wrapper for QML +func (f *FrontendQt) setupAndLoadForImport(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServer, sourcePort, targetAddress string) { + var err error + defer func() { + if err != nil { + f.showError(err) + f.Qml.ImportStructuresLoadFinished(false) + } else { + f.Qml.ImportStructuresLoadFinished(true) + } + }() + + if isFromIMAP { + f.transfer, err = f.ie.GetRemoteImporter(targetAddress, sourceEmail, sourcePassword, sourceServer, sourcePort) + if err != nil { + return + } + } else { + f.transfer, err = f.ie.GetLocalImporter(targetAddress, sourcePath) + if err != nil { + return + } + } + + if err := f.loadStructuresForImport(); err != nil { + return + } +} + +func (f *FrontendQt) loadStructuresForImport() error { + f.PMStructure.Clear() + targetMboxes, err := f.transfer.TargetMailboxes() + if err != nil { + return err + } + for _, mbox := range targetMboxes { + rule := &transfer.Rule{} + f.PMStructure.addEntry(newFolderInfo(mbox, rule)) + } + + f.ExternalStructure.Clear() + sourceMboxes, err := f.transfer.SourceMailboxes() + if err != nil { + return err + } + for _, mbox := range sourceMboxes { + rule := f.transfer.GetRule(mbox) + f.ExternalStructure.addEntry(newFolderInfo(mbox, rule)) + } + + f.ExternalStructure.transfer = f.transfer + + return nil +} + +func (f *FrontendQt) StartImport(email string) { // TODO email not needed + f.Qml.SetProgressDescription("init") // TODO use const + f.Qml.SetProgressFails(0) + f.Qml.SetProgress(0.0) + f.Qml.SetTotal(1) + f.Qml.SetImportLogFileName("") + f.ErrorList.Clear() + + progress := f.transfer.Start() + f.setProgressManager(progress) +} diff --git a/internal/frontend/qt/logs.go b/internal/frontend/qt-ie/notification.go similarity index 72% rename from internal/frontend/qt/logs.go rename to internal/frontend/qt-ie/notification.go index 9b75950c..74d8f1de 100644 --- a/internal/frontend/qt/logs.go +++ b/internal/frontend/qt-ie/notification.go @@ -17,22 +17,16 @@ // +build !nogui -package qt +package qtie -//#include "logs.h" -import "C" - -import ( - "github.com/sirupsen/logrus" +const ( + TabGlobal = 0 + TabSettings = 1 + TabHelp = 2 + TabQuit = 4 + TabAddAccount = -1 ) -func installMessageHandler() { - C.InstallMessageHandler() -} - -//export logMsgPacked -func logMsgPacked(data *C.char, len C.int) { - log.WithFields(logrus.Fields{ - "pkg": "frontend-qml", - }).Warnln(C.GoStringN(data, len)) +func (s *FrontendQt) SendNotification(tabIndex int, msg string) { + s.Qml.NotifyBubble(tabIndex, msg) } diff --git a/internal/frontend/qt-ie/types.go b/internal/frontend/qt-ie/types.go new file mode 100644 index 00000000..8d759887 --- /dev/null +++ b/internal/frontend/qt-ie/types.go @@ -0,0 +1,25 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +type panicHandler interface { + HandlePanic() + SendReport(interface{}) +} diff --git a/internal/frontend/qt-ie/ui.go b/internal/frontend/qt-ie/ui.go new file mode 100644 index 00000000..e70663ae --- /dev/null +++ b/internal/frontend/qt-ie/ui.go @@ -0,0 +1,189 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +// +build !nogui + +package qtie + +import ( + "runtime" + + qtcommon "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" + "github.com/therecipe/qt/core" +) + +// GoQMLInterface between go and qml +// +// Here we implements all the signals / methods. +type GoQMLInterface struct { + core.QObject + + _ func() `constructor:"init"` + + _ string `property:"currentAddress"` + _ string `property:"goos"` + _ bool `property:"isFirstStart"` + _ bool `property:"isRestarting"` + _ bool `property:"isConnectionOK"` + + _ string `property:lastError` + _ float32 `property:progress` + _ string `property:progressDescription` + _ int `property:progressFails` + _ int `property:total` + _ string `property:importLogFileName` + + _ string `property:"programTitle"` + _ string `property:"newversion"` + _ string `property:"downloadLink"` + _ string `property:"landingPage"` + _ string `property:"changelog"` + _ string `property:"bugfixes"` + + // translations + _ string `property:"wrongCredentials"` + _ string `property:"wrongMailboxPassword"` + _ string `property:"canNotReachAPI"` + _ string `property:"credentialsNotRemoved"` + _ string `property:"versionCheckFailed"` + // + _ func(isAvailable bool) `signal:"setConnectionStatus"` + _ func(updateState string) `signal:"setUpdateState"` + _ func() `slot:"checkInternet"` + + _ func() `signal:"processFinished"` + _ func(okay bool) `signal:"exportStructureLoadFinished"` + _ func(okay bool) `signal:"importStructuresLoadFinished"` + _ func() `signal:"openManual"` + _ func(showMessage bool) `signal:"runCheckVersion"` + _ func() `slot:"getLocalVersionInfo"` + _ func(fname string) `slot:"loadImportReports"` + + _ func() `slot:"quit"` + _ func() `slot:"loadAccounts"` + _ func() `slot:"openLogs"` + _ func() `slot:"openDownloadLink"` + _ func() `slot:"openReport"` + _ func() `slot:"clearCache"` + _ func() `slot:"clearKeychain"` + _ func() `signal:"highlightSystray"` + _ func() `signal:"normalSystray"` + + _ func(showMessage bool) `slot:"isNewVersionAvailable"` + _ func() string `slot:"getBackendVersion"` + + _ func(description, client, address string) bool `slot:"sendBug"` + _ func(address, fname string) bool `slot:"sendImportReport"` + _ func(address string) `slot:"loadStructureForExport"` + _ func() string `slot:"leastUsedColor"` + _ func(username string, name string, color string, isLabel bool, sourceID string) bool `slot:"createLabelOrFolder"` + _ func(fpath, address, fileType string, attachEncryptedBody bool) `slot:"startExport"` + _ func(email string) `slot:"startImport"` + _ func() `slot:"resetSource"` + + _ func(isFromIMAP bool, sourcePath, sourceEmail, sourcePassword, sourceServe, sourcePort, targetAddress string) `slot:"setupAndLoadForImport"` + + _ string `property:"progressInit"` + + _ func(path string) int `slot:"checkPathStatus"` + + _ func(evType string, msg string) `signal:"emitEvent"` + _ func(tabIndex int, message string) `signal:"notifyBubble"` + + _ func() `signal:"bubbleClosed"` + _ func() `signal:"simpleErrorHappen"` + _ func() `signal:"askErrorHappen"` + _ func() `signal:"retryErrorHappen"` + _ func() `signal:"pauseProcess"` + _ func() `signal:"resumeProcess"` + _ func(clearUnfinished bool) `signal:"cancelProcess"` + + _ func(iAccount int, prefRem bool) `slot:"deleteAccount"` + _ func(iAccount int) `slot:"logoutAccount"` + _ func(login, password string) int `slot:"login"` + _ func(twoFacAuth string) int `slot:"auth2FA"` + _ func(mailboxPassword string) int `slot:"addAccount"` + _ func(message string, changeIndex int) `signal:"setAddAccountWarning"` + + _ func() `signal:"notifyVersionIsTheLatest"` + _ func() `signal:"notifyKeychainRebuild"` + _ func() `signal:"notifyHasNoKeychain"` + _ func() `signal:"notifyUpdate"` + _ func(accname string) `signal:"notifyLogout"` + _ func(accname string) `signal:"notifyAddressChanged"` + _ func(accname string) `signal:"notifyAddressChangedLogout"` + + _ func() `slot:"startUpdate"` + _ func(hasError bool) `signal:"updateFinished"` + + // errors + _ func() `signal:"answerRetry"` + _ func(all bool) `signal:"answerSkip"` + _ func(errCode int) `signal:"notifyError"` + _ string `property:"errorDescription"` +} + +// Constructor +func (s *GoQMLInterface) init() {} + +// SetFrontend connects all slots and signals from Go to QML +func (s *GoQMLInterface) SetFrontend(f *FrontendQt) { + s.ConnectQuit(f.App.Quit) + + s.ConnectLoadAccounts(f.Accounts.LoadAccounts) + s.ConnectOpenLogs(f.openLogs) + s.ConnectOpenDownloadLink(f.openDownloadLink) + s.ConnectOpenReport(f.openReport) + s.ConnectClearCache(f.Accounts.ClearCache) + s.ConnectClearKeychain(f.Accounts.ClearKeychain) + + s.ConnectSendBug(f.sendBug) + s.ConnectSendImportReport(f.sendImportReport) + + s.ConnectDeleteAccount(f.Accounts.DeleteAccount) + s.ConnectLogoutAccount(f.Accounts.LogoutAccount) + s.ConnectLogin(f.Accounts.Login) + s.ConnectAuth2FA(f.Accounts.Auth2FA) + s.ConnectAddAccount(f.Accounts.AddAccount) + + s.SetGoos(runtime.GOOS) + s.SetIsRestarting(false) + s.SetProgramTitle(f.programName) + + s.ConnectGetLocalVersionInfo(f.getLocalVersionInfo) + s.ConnectIsNewVersionAvailable(f.isNewVersionAvailable) + s.ConnectGetBackendVersion(func() string { + return f.programVersion + }) + + s.ConnectCheckInternet(f.checkInternet) + + s.ConnectLoadStructureForExport(f.LoadStructureForExport) + s.ConnectSetupAndLoadForImport(f.setupAndLoadForImport) + s.ConnectResetSource(f.resetSource) + s.ConnectLeastUsedColor(f.leastUsedColor) + s.ConnectCreateLabelOrFolder(f.createLabelOrFolder) + + s.ConnectStartExport(f.StartExport) + s.ConnectStartImport(f.StartImport) + + s.ConnectCheckPathStatus(qtcommon.CheckPathStatus) + + s.ConnectStartUpdate(f.StartUpdate) + + s.ConnectEmitEvent(f.emitEvent) +} diff --git a/internal/frontend/qt/Makefile.local b/internal/frontend/qt/Makefile.local index a50f35bd..826c5907 100644 --- a/internal/frontend/qt/Makefile.local +++ b/internal/frontend/qt/Makefile.local @@ -17,14 +17,14 @@ translate.ts: ${QMLfiles} lupdate -recursive qml/ -ts $@ rcc.cpp: ${QMLfiles} ${Icons} resources.qrc - rm -f rcc.cpp rcc.qrc && qtrcc -o . + rm -f rcc.cpp rcc.qrc && qtrcc -o . qmltest: - qmltestrunner -eventdelay 500 -import ./qml/ -qmlcheck : ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images + qmltestrunner -eventdelay 500 -import ../qml/ +qmlcheck: ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images qmlscene -I ../qml/ -f ../qml/tst_Gui.qml --quit -qmlpreview : ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images +qmlpreview: ../qml/ProtonUI/fontawesome.ttf ../qml/ProtonUI/images rm -f ../qml/*.qmlc ../qml/BridgeUI/*.qmlc qmlscene -verbose -I ../qml/ -f ../qml/tst_Gui.qml #qmlscene -qmljsdebugger=port:3768,block -verbose -I ../qml/ -f ../qml/tst_Gui.qml diff --git a/internal/frontend/qt/frontend.go b/internal/frontend/qt/frontend.go index 57b3a3c0..2748021e 100644 --- a/internal/frontend/qt/frontend.go +++ b/internal/frontend/qt/frontend.go @@ -40,6 +40,7 @@ import ( "github.com/ProtonMail/proton-bridge/internal/bridge" "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/frontend/autoconfig" + "github.com/ProtonMail/proton-bridge/internal/frontend/qt-common" "github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/ProtonMail/proton-bridge/internal/preferences" "github.com/ProtonMail/proton-bridge/pkg/config" @@ -151,7 +152,7 @@ func New( // InstanceExistAlert is a global warning window indicating an instance already exists. func (s *FrontendQt) InstanceExistAlert() { log.Warn("Instance already exists") - s.QtSetupCoreAndControls() + qtcommon.QtSetupCoreAndControls(s.programName, s.programVer) s.App = widgets.NewQApplication(len(os.Args), os.Args) s.View = qml.NewQQmlApplicationEngine(s.App) s.View.AddImportPath("qrc:///") @@ -283,28 +284,13 @@ func (s *FrontendQt) InvMethod(method string) error { return nil } -// QtSetupCoreAndControls hanldes global setup of Qt. -// Should be called once per program. Probably once per thread is fine. -func (s *FrontendQt) QtSetupCoreAndControls() { - installMessageHandler() - // Core setup. - core.QCoreApplication_SetApplicationName(s.programName) - core.QCoreApplication_SetApplicationVersion(s.programVer) - // High DPI scaling for windows. - core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, false) - // Software OpenGL: to avoid dedicated GPU. - core.QCoreApplication_SetAttribute(core.Qt__AA_UseSoftwareOpenGL, true) - // Basic style for QuickControls2 objects. - //quickcontrols2.QQuickStyle_SetStyle("material") -} - // qtExecute is the main function for starting the Qt application. // // It is better to have just one Qt application per program (at least per same // thread). This functions reads the main user interface defined in QML files. // The files are appended to library by Qt-QRC. func (s *FrontendQt) qtExecute(Procedure func(*FrontendQt) error) error { - s.QtSetupCoreAndControls() + qtcommon.QtSetupCoreAndControls(s.programName, s.programVer) s.App = widgets.NewQApplication(len(os.Args), os.Args) if runtime.GOOS == "linux" { // Fix default font. s.App.SetFont(gui.NewQFont2(FcMatchSans(), 12, int(gui.QFont__Normal), false), "") @@ -624,7 +610,7 @@ func (s *FrontendQt) StartUpdate() { defer s.panicHandler.HandlePanic() for current := range progress { s.Qml.SetProgress(current.Processed) - s.Qml.SetProgressDescription(current.Description) + s.Qml.SetProgressDescription(strconv.Itoa(current.Description)) // Error happend if current.Err != nil { log.Error("update progress: ", current.Err) diff --git a/internal/frontend/qt/resources.qrc b/internal/frontend/qt/resources.qrc deleted file mode 100644 index a7c05d71..00000000 --- a/internal/frontend/qt/resources.qrc +++ /dev/null @@ -1,77 +0,0 @@ - - - - - ../qml/ProtonUI/qmldir - ../qml/ProtonUI/AccessibleButton.qml - ../qml/ProtonUI/AccessibleText.qml - ../qml/ProtonUI/AccessibleSelectableText.qml - ../qml/ProtonUI/AccountView.qml - ../qml/ProtonUI/AddAccountBar.qml - ../qml/ProtonUI/BubbleNote.qml - ../qml/ProtonUI/BugReportWindow.qml - ../qml/ProtonUI/ButtonIconText.qml - ../qml/ProtonUI/ButtonRounded.qml - ../qml/ProtonUI/CheckBoxLabel.qml - ../qml/ProtonUI/ClickIconText.qml - ../qml/ProtonUI/Dialog.qml - ../qml/ProtonUI/DialogAddUser.qml - ../qml/ProtonUI/DialogUpdate.qml - ../qml/ProtonUI/DialogConnectionTroubleshoot.qml - ../qml/ProtonUI/FileAndFolderSelect.qml - ../qml/ProtonUI/InformationBar.qml - ../qml/ProtonUI/InputField.qml - ../qml/ProtonUI/InstanceExistsWindow.qml - ../qml/ProtonUI/LogoHeader.qml - ../qml/ProtonUI/PopupMessage.qml - ../qml/ProtonUI/Style.qml - ../qml/ProtonUI/TabButton.qml - ../qml/ProtonUI/TabLabels.qml - ../qml/ProtonUI/TextLabel.qml - ../qml/ProtonUI/TextValue.qml - ../qml/ProtonUI/TLSCertPinIssueBar.qml - ../qml/ProtonUI/WindowTitleBar.qml - ../share/fontawesome-webfont.ttf - - - ../share/icons/rounded-systray.png - ../share/icons/rounded-syswarn.png - ../share/icons/rounded-syswarn.png - ../share/icons/white-systray.png - ../share/icons/white-syswarn.png - ../share/icons/white-syserror.png - ../share/icons/rounded-app.png - ../share/icons/pm_logo.png - ../share/icons/win10_Dash.png - ../share/icons/win10_Times.png - ../share/icons/macos_gray.png - ../share/icons/macos_red.png - ../share/icons/macos_red_hl.png - ../share/icons/macos_red_dark.png - ../share/icons/macos_yellow.png - ../share/icons/macos_yellow_hl.png - ../share/icons/macos_yellow_dark.png - - - ../qml/BridgeUI/qmldir - ../qml/BridgeUI/AccountDelegate.qml - ../qml/BridgeUI/BubbleMenu.qml - ../qml/BridgeUI/Credits.qml - ../qml/BridgeUI/DialogFirstStart.qml - ../qml/BridgeUI/DialogPortChange.qml - ../qml/BridgeUI/DialogYesNo.qml - ../qml/BridgeUI/DialogTLSCertInfo.qml - ../qml/BridgeUI/HelpView.qml - ../qml/BridgeUI/InfoWindow.qml - ../qml/BridgeUI/MainWindow.qml - ../qml/BridgeUI/ManualWindow.qml - ../qml/BridgeUI/OutgoingNoEncPopup.qml - ../qml/BridgeUI/SettingsView.qml - ../qml/BridgeUI/StatusFooter.qml - ../qml/BridgeUI/VersionInfo.qml - - - ../qml/Gui.qml - - - diff --git a/internal/frontend/qt/ui.go b/internal/frontend/qt/ui.go index bd8a0b4e..30cffc26 100644 --- a/internal/frontend/qt/ui.go +++ b/internal/frontend/qt/ui.go @@ -64,7 +64,7 @@ type GoQMLInterface struct { _ string `property:"genericErrSeeLogs"` _ float32 `property:"progress"` - _ int `property:"progressDescription"` + _ string `property:"progressDescription"` _ func(isAvailable bool) `signal:"setConnectionStatus"` _ func(updateState string) `signal:"setUpdateState"` diff --git a/internal/frontend/resources.qrc b/internal/frontend/resources.qrc new file mode 100644 index 00000000..dff9f49f --- /dev/null +++ b/internal/frontend/resources.qrc @@ -0,0 +1,116 @@ + + + + + ./qml/ProtonUI/qmldir + ./qml/ProtonUI/AccessibleButton.qml + ./qml/ProtonUI/AccessibleText.qml + ./qml/ProtonUI/AccessibleSelectableText.qml + ./qml/ProtonUI/AccountView.qml + ./qml/ProtonUI/AddAccountBar.qml + ./qml/ProtonUI/BubbleNote.qml + ./qml/ProtonUI/BugReportWindow.qml + ./qml/ProtonUI/ButtonIconText.qml + ./qml/ProtonUI/ButtonRounded.qml + ./qml/ProtonUI/CheckBoxLabel.qml + ./qml/ProtonUI/ClickIconText.qml + ./qml/ProtonUI/Dialog.qml + ./qml/ProtonUI/DialogAddUser.qml + ./qml/ProtonUI/DialogUpdate.qml + ./qml/ProtonUI/DialogConnectionTroubleshoot.qml + ./qml/ProtonUI/FileAndFolderSelect.qml + ./qml/ProtonUI/InfoToolTip.qml + ./qml/ProtonUI/InformationBar.qml + ./qml/ProtonUI/InputBox.qml + ./qml/ProtonUI/InputField.qml + ./qml/ProtonUI/InstanceExistsWindow.qml + ./qml/ProtonUI/LogoHeader.qml + ./qml/ProtonUI/PopupMessage.qml + ./qml/ProtonUI/RoundedRectangle.qml + ./qml/ProtonUI/Style.qml + ./qml/ProtonUI/TabButton.qml + ./qml/ProtonUI/TabLabels.qml + ./qml/ProtonUI/TextLabel.qml + ./qml/ProtonUI/TextValue.qml + ./qml/ProtonUI/TLSCertPinIssueBar.qml + ./qml/ProtonUI/WindowTitleBar.qml + ./share/fontawesome-webfont.ttf + + + ./share/icons/rounded-systray.png + ./share/icons/rounded-syswarn.png + ./share/icons/rounded-syswarn.png + ./share/icons/white-systray.png + ./share/icons/white-syswarn.png + ./share/icons/white-syserror.png + ./share/icons/rounded-app.png + ./share/icons/pm_logo.png + ./share/icons/win10_Dash.png + ./share/icons/win10_Times.png + ./share/icons/macos_gray.png + ./share/icons/macos_red.png + ./share/icons/macos_red_hl.png + ./share/icons/macos_red_dark.png + ./share/icons/macos_yellow.png + ./share/icons/macos_yellow_hl.png + ./share/icons/macos_yellow_dark.png + + + ./qml/BridgeUI/qmldir + ./qml/BridgeUI/AccountDelegate.qml + ./qml/BridgeUI/BubbleMenu.qml + ./qml/BridgeUI/Credits.qml + ./qml/BridgeUI/DialogFirstStart.qml + ./qml/BridgeUI/DialogPortChange.qml + ./qml/BridgeUI/DialogYesNo.qml + ./qml/BridgeUI/DialogTLSCertInfo.qml + ./qml/BridgeUI/HelpView.qml + ./qml/BridgeUI/InfoWindow.qml + ./qml/BridgeUI/MainWindow.qml + ./qml/BridgeUI/ManualWindow.qml + ./qml/BridgeUI/OutgoingNoEncPopup.qml + ./qml/BridgeUI/SettingsView.qml + ./qml/BridgeUI/StatusFooter.qml + ./qml/BridgeUI/VersionInfo.qml + + + ./qml/ImportExportUI/qmldir + ./qml/ImportExportUI/AccountDelegate.qml + ./qml/ImportExportUI/Credits.qml + ./qml/ImportExportUI/DateBox.qml + ./qml/ImportExportUI/DateInput.qml + ./qml/ImportExportUI/DateRange.qml + ./qml/ImportExportUI/DateRangeMenu.qml + ./qml/ImportExportUI/DateRangeFunctions.qml + ./qml/ImportExportUI/DialogExport.qml + ./qml/ImportExportUI/DialogImport.qml + ./qml/ImportExportUI/DialogYesNo.qml + ./qml/ImportExportUI/ExportStructure.qml + ./qml/ImportExportUI/FilterStructure.qml + ./qml/ImportExportUI/FolderRowButton.qml + ./qml/ImportExportUI/HelpView.qml + ./qml/ImportExportUI/IEStyle.qml + ./qml/ImportExportUI/ImportDelegate.qml + ./qml/ImportExportUI/ImportReport.qml + ./qml/ImportExportUI/ImportReportCell.qml + ./qml/ImportExportUI/ImportSourceButton.qml + ./qml/ImportExportUI/ImportStructure.qml + ./qml/ImportExportUI/InlineDateRange.qml + ./qml/ImportExportUI/InlineLabelSelect.qml + ./qml/ImportExportUI/LabelIconList.qml + ./qml/ImportExportUI/MainWindow.qml + ./qml/ImportExportUI/OutputFormat.qml + ./qml/ImportExportUI/PopupEditFolder.qml + ./qml/ImportExportUI/SelectFolderMenu.qml + ./qml/ImportExportUI/SelectLabelsMenu.qml + ./qml/ImportExportUI/SettingsView.qml + ./qml/ImportExportUI/VersionInfo.qml + ./share/icons/folder_open.png + ./share/icons/envelope_open.png + + + ./qml/Gui.qml + ./qml/GuiIE.qml + + + diff --git a/internal/frontend/share/icons/envelope_open.png b/internal/frontend/share/icons/envelope_open.png new file mode 100644 index 0000000000000000000000000000000000000000..be8d245540d922e5551cf9a118f325318ba1ed12 GIT binary patch literal 22900 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 literal 0 HcmV?d00001 diff --git a/internal/frontend/share/icons/folder_open.png b/internal/frontend/share/icons/folder_open.png new file mode 100644 index 0000000000000000000000000000000000000000..4cc1694a170fb53938685e1878b946f241b2ef08 GIT binary patch literal 8875 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 ip!e-lGs1Q{=e- literal 0 HcmV?d00001 diff --git a/internal/frontend/share/icons/ie.icns b/internal/frontend/share/icons/ie.icns new file mode 100644 index 0000000000000000000000000000000000000000..6b67c5582191aaba2f73f077d614fed337fc6b4b GIT binary patch literal 272787 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%0Du4tBqj7j2QzU! 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 literal 0 HcmV?d00001 diff --git a/internal/frontend/share/icons/ie.ico b/internal/frontend/share/icons/ie.ico new file mode 100644 index 0000000000000000000000000000000000000000..375b1b49fb5ce1dbd924232ab6bbec24e65b5687 GIT binary patch literal 569990 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 + + + + + + + + + + + + + + + + + + + + + + diff --git a/internal/frontend/types/types.go b/internal/frontend/types/types.go index ee180249..f5781421 100644 --- a/internal/frontend/types/types.go +++ b/internal/frontend/types/types.go @@ -112,6 +112,8 @@ type ImportExporter interface { GetRemoteImporter(string, string, string, string, string) (*transfer.Transfer, error) GetEMLExporter(string, string) (*transfer.Transfer, error) GetMBOXExporter(string, string) (*transfer.Transfer, error) + SetCurrentOS(os string) + ReportBug(osType, osVersion, description, accountName, address, emailClient string) error } type importExportWrap struct { diff --git a/internal/importexport/credits.go b/internal/importexport/credits.go index b3a2a91d..87eed1d7 100644 --- a/internal/importexport/credits.go +++ b/internal/importexport/credits.go @@ -15,8 +15,8 @@ // You should have received a copy of the GNU General Public License // along with ProtonMail Bridge. If not, see . -// Code generated by ./credits.sh at Thu Jun 4 15:54:31 CEST 2020. DO NOT EDIT. +// Code generated by ./credits.sh at Thu 04 Jun 2020 04:19:16 PM CEST. DO NOT EDIT. package importexport -const Credits = "github.com/0xAX/notificator;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/ProtonMail/gopenpgp;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/go-resty/resty/v2;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" +const Credits = "github.com/0xAX/notificator;github.com/abiosoft/ishell;github.com/abiosoft/readline;github.com/allan-simon/go-singleinstance;github.com/andybalholm/cascadia;github.com/certifi/gocertifi;github.com/chzyer/logex;github.com/chzyer/test;github.com/cucumber/godog;github.com/docker/docker-credential-helpers;github.com/emersion/go-imap;github.com/emersion/go-imap-appendlimit;github.com/emersion/go-imap-idle;github.com/emersion/go-imap-move;github.com/emersion/go-imap-quota;github.com/emersion/go-imap-specialuse;github.com/emersion/go-imap-unselect;github.com/emersion/go-mbox;github.com/emersion/go-sasl;github.com/emersion/go-smtp;github.com/emersion/go-textwrapper;github.com/emersion/go-vcard;github.com/fatih/color;github.com/flynn-archive/go-shlex;github.com/getsentry/raven-go;github.com/golang/mock;github.com/google/go-cmp;github.com/gopherjs/gopherjs;github.com/go-resty/resty/v2;github.com/hashicorp/go-multierror;github.com/jameskeane/bcrypt;github.com/jaytaylor/html2text;github.com/jhillyerd/enmime;github.com/kardianos/osext;github.com/keybase/go-keychain;github.com/logrusorgru/aurora;github.com/miekg/dns;github.com/myesui/uuid;github.com/nsf/jsondiff;github.com/pkg/errors;github.com/ProtonMail/bcrypt;github.com/ProtonMail/crypto;github.com/ProtonMail/docker-credential-helpers;github.com/ProtonMail/go-appdir;github.com/ProtonMail/go-apple-mobileconfig;github.com/ProtonMail/go-autostart;github.com/ProtonMail/go-imap-id;github.com/ProtonMail/gopenpgp;github.com/ProtonMail/go-smtp;github.com/ProtonMail/go-vcard;github.com/sirupsen/logrus;github.com/skratchdot/open-golang;github.com/stretchr/testify;github.com/therecipe/qt;github.com/twinj/uuid;github.com/urfave/cli;go.etcd.io/bbolt;golang.org/x/crypto;golang.org/x/net;golang.org/x/text;gopkg.in/stretchr/testify.v1;;Font Awesome 4.7.0;;Qt 5.13 by Qt group;" diff --git a/internal/importexport/importexport.go b/internal/importexport/importexport.go index 569ce733..ceaf7176 100644 --- a/internal/importexport/importexport.go +++ b/internal/importexport/importexport.go @@ -55,6 +55,30 @@ func New( } } +// ReportBug reports a new bug from the user. +func (ie *ImportExport) ReportBug(osType, osVersion, description, accountName, address, emailClient string) error { + c := ie.clientManager.GetAnonymousClient() + defer c.Logout() + + title := "[Import-Export] Bug" + if err := c.ReportBugWithEmailClient( + osType, + osVersion, + title, + description, + accountName, + address, + emailClient, + ); err != nil { + log.Error("Reporting bug failed: ", err) + return err + } + + log.Info("Bug successfully reported") + + return nil +} + // GetLocalImporter returns transferrer from local EML or MBOX structure to ProtonMail account. func (ie *ImportExport) GetLocalImporter(address, path string) (*transfer.Transfer, error) { source := transfer.NewLocalProvider(path) @@ -111,3 +135,6 @@ func (ie *ImportExport) getPMAPIProvider(address string) (*transfer.PMAPIProvide return transfer.NewPMAPIProvider(ie.clientManager, user.ID(), addressID) } + +// SetCurrentOS TODO +func (ie *ImportExport) SetCurrentOS(os string) {} diff --git a/internal/store/user_mailbox.go b/internal/store/user_mailbox.go index db8e9b9b..6a0d3bde 100644 --- a/internal/store/user_mailbox.go +++ b/internal/store/user_mailbox.go @@ -110,22 +110,14 @@ func (store *Store) leastUsedColor() string { store.lock.RLock() defer store.lock.RUnlock() - usage := map[string]int{} + colors := []string{} for _, a := range store.addresses { for _, m := range a.mailboxes { - if m.color != "" { - usage[m.color]++ - } + colors = append(colors, m.color) } } - leastUsed := pmapi.LabelColors[0] - for _, color := range pmapi.LabelColors { - if usage[leastUsed] > usage[color] { - leastUsed = color - } - } - return leastUsed + return pmapi.LeastUsedColor(colors) } // updateMailbox updates the mailbox via the API. diff --git a/internal/transfer/mailbox.go b/internal/transfer/mailbox.go index 556c3218..b4fbaf46 100644 --- a/internal/transfer/mailbox.go +++ b/internal/transfer/mailbox.go @@ -21,6 +21,8 @@ import ( "crypto/sha256" "fmt" "strings" + + "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) // Mailbox is universal data holder of mailbox details for every provider. @@ -36,6 +38,19 @@ func (m Mailbox) Hash() string { return fmt.Sprintf("%x", sha256.Sum256([]byte(m.Name))) } +// LeastUsedColor is intended to return color for creating a new inbox or label +func LeastUsedColor(mailboxes []Mailbox) string { + usedColors := []string{} + + if mailboxes != nil { + for _, m := range mailboxes { + usedColors = append(usedColors, m.Color) + } + } + + return pmapi.LeastUsedColor(usedColors) +} + // findMatchingMailboxes returns all matching mailboxes from `mailboxes`. // Only one exclusive mailbox is returned. func (m Mailbox) findMatchingMailboxes(mailboxes []Mailbox) []Mailbox { diff --git a/internal/transfer/mailbox_test.go b/internal/transfer/mailbox_test.go index c59b4581..5865cfdc 100644 --- a/internal/transfer/mailbox_test.go +++ b/internal/transfer/mailbox_test.go @@ -23,6 +23,49 @@ import ( r "github.com/stretchr/testify/require" ) +func TestLeastUsedColor(t *testing.T) { + var mailboxes []Mailbox + // Unset mailboxes, should use first available color + mailboxes = nil + r.Equal(t, "#7272a7", LeastUsedColor(mailboxes)) + + // No mailboxes at all, should use first available color + mailboxes = []Mailbox{} + r.Equal(t, "#7272a7", LeastUsedColor(mailboxes)) + + // All colors have same frequency, should use first available color + mailboxes = []Mailbox{ + {Name: "Mbox1", Color: "#7272a7"}, + {Name: "Mbox2", Color: "#cf5858"}, + {Name: "Mbox3", Color: "#c26cc7"}, + {Name: "Mbox4", Color: "#7569d1"}, + {Name: "Mbox5", Color: "#69a9d1"}, + {Name: "Mbox6", Color: "#5ec7b7"}, + {Name: "Mbox7", Color: "#72bb75"}, + {Name: "Mbox8", Color: "#c3d261"}, + {Name: "Mbox9", Color: "#e6c04c"}, + {Name: "Mbox10", Color: "#e6984c"}, + {Name: "Mbox11", Color: "#8989ac"}, + {Name: "Mbox12", Color: "#cf7e7e"}, + {Name: "Mbox13", Color: "#c793ca"}, + {Name: "Mbox14", Color: "#9b94d1"}, + {Name: "Mbox15", Color: "#a8c4d5"}, + {Name: "Mbox16", Color: "#97c9c1"}, + {Name: "Mbox17", Color: "#9db99f"}, + {Name: "Mbox18", Color: "#c6cd97"}, + {Name: "Mbox19", Color: "#e7d292"}, + {Name: "Mbox20", Color: "#dfb286"}, + } + r.Equal(t, "#7272a7", LeastUsedColor(mailboxes)) + + // First three colors already used, but others wasn't. Should use first non-used one. + mailboxes = []Mailbox{ + {Name: "Mbox1", Color: "#7272a7"}, + {Name: "Mbox2", Color: "#cf5858"}, + {Name: "Mbox3", Color: "#c26cc7"}, + } + r.Equal(t, "#7569d1", LeastUsedColor(mailboxes)) +} func TestFindMatchingMailboxes(t *testing.T) { mailboxes := []Mailbox{ {Name: "Inbox", IsExclusive: true}, diff --git a/internal/transfer/transfer.go b/internal/transfer/transfer.go index bcd7290d..2fda8f7e 100644 --- a/internal/transfer/transfer.go +++ b/internal/transfer/transfer.go @@ -141,8 +141,8 @@ func (t *Transfer) CreateTargetMailbox(mailbox Mailbox) (Mailbox, error) { return t.target.CreateMailbox(mailbox) } -// ChangeTarget allows to change target. Ideally should not be used. -// Useful for situration after user changes mind where to export files and similar. +// ChangeTarget changes the target. It is safe to change target for export, +// must not be changed for import. Do not set after you started transfer. func (t *Transfer) ChangeTarget(target TargetProvider) { t.target = target } diff --git a/pkg/pmapi/labels.go b/pkg/pmapi/labels.go index eee5fc86..cc970740 100644 --- a/pkg/pmapi/labels.go +++ b/pkg/pmapi/labels.go @@ -175,3 +175,21 @@ func (c *client) DeleteLabel(id string) (err error) { err = res.Err() return } + +// LeastUsedColor is intended to return color for creating a new inbox or label +func LeastUsedColor(colors []string) (color string) { + color = LabelColors[0] + frequency := map[string]int{} + + for _, c := range colors { + frequency[c]++ + } + + for _, c := range LabelColors { + if frequency[color] > frequency[c] { + color = c + } + } + + return +} diff --git a/pkg/pmapi/labels_test.go b/pkg/pmapi/labels_test.go index 6479f501..36096a57 100644 --- a/pkg/pmapi/labels_test.go +++ b/pkg/pmapi/labels_test.go @@ -24,6 +24,8 @@ import ( "net/http" "reflect" "testing" + + r "github.com/stretchr/testify/require" ) const testLabelsBody = `{ @@ -184,3 +186,17 @@ func TestClient_DeleteLabel(t *testing.T) { t.Fatal("Expected no error while deleting label, got:", err) } } + +func TestLeastUsedColor(t *testing.T) { + // No colors at all, should use first available color + colors := []string{} + r.Equal(t, "#7272a7", LeastUsedColor(colors)) + + // All colors have same frequency, should use first available color + colors = []string{"#7272a7", "#cf5858", "#c26cc7", "#7569d1", "#69a9d1", "#5ec7b7", "#72bb75", "#c3d261", "#e6c04c", "#e6984c", "#8989ac", "#cf7e7e", "#c793ca", "#9b94d1", "#a8c4d5", "#97c9c1", "#9db99f", "#c6cd97", "#e7d292", "#dfb286"} + r.Equal(t, "#7272a7", LeastUsedColor(colors)) + + // First three colors already used, but others wasn't. Should use first non-used one. + colors = []string{"#7272a7", "#cf5858", "#c26cc7"} + r.Equal(t, "#7569d1", LeastUsedColor(colors)) +} diff --git a/utils/credits.sh b/utils/credits.sh index 6827c7b0..4c9ccf9f 100755 --- a/utils/credits.sh +++ b/utils/credits.sh @@ -23,14 +23,14 @@ PACKAGE=$1 # Vendor packages LOCKFILE=../go.mod -egrep $'^\t[^=>]*$' $LOCKFILE | sed -r 's/\t([^ ]*) v.*/\1/g' > tmp1 -egrep $'^\t.*=>.*v.*$' $LOCKFILE | sed -r 's/^.*=> ([^ ]*)( v.*)?/\1/g' >> tmp1 -cat tmp1 | egrep -v 'therecipe/qt/internal|therecipe/env_.*_512|protontech' | sort | uniq > tmp +egrep $'^\t[^=>]*$' $LOCKFILE | sed -r 's/\t([^ ]*) v.*/\1/g' > tmp1-$PACKAGE +egrep $'^\t.*=>.*v.*$' $LOCKFILE | sed -r 's/^.*=> ([^ ]*)( v.*)?/\1/g' >> tmp1-$PACKAGE +cat tmp1-$PACKAGE | egrep -v 'therecipe/qt/internal|therecipe/env_.*_512|protontech' | sort | uniq > tmp-$PACKAGE # Add non vendor credits -echo -e "\nFont Awesome 4.7.0\n\nQt 5.13 by Qt group\n" >> tmp +echo -e "\nFont Awesome 4.7.0\n\nQt 5.13 by Qt group\n" >> tmp-$PACKAGE # join lines -sed -i -e ':a' -e 'N' -e '$!ba' -e 's|\n|;|g' tmp +sed -i -e ':a' -e 'N' -e '$!ba' -e 's|\n|;|g' tmp-$PACKAGE cat ../utils/license_header.txt > ../internal/$PACKAGE/credits.go -echo -e '// Code generated by '`echo $0`' at '`date`'. DO NOT EDIT.\n\npackage '$PACKAGE'\n\nconst Credits = "'$(cat tmp)'"' >> ../internal/$PACKAGE/credits.go -rm tmp1 tmp +echo -e '// Code generated by '`echo $0`' at '`date`'. DO NOT EDIT.\n\npackage '$PACKAGE'\n\nconst Credits = "'$(cat tmp-$PACKAGE)'"' >> ../internal/$PACKAGE/credits.go +rm tmp1-$PACKAGE tmp-$PACKAGE diff --git a/utils/enums.sh b/utils/enums.sh new file mode 100644 index 00000000..b9431abd --- /dev/null +++ b/utils/enums.sh @@ -0,0 +1,149 @@ +// Copyright (c) 2020 Proton Technologies AG +// +// This file is part of ProtonMail Bridge. +// +// ProtonMail Bridge is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// ProtonMail Bridge is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with ProtonMail Bridge. If not, see . + +#!/bin/bash + +# create QML JSON object from list of golang constants +# run this script and output line stored in `out.qml` insert to `Gui.qml` + +list=" +qtfrontend.PathOK +qtfrontend.PathEmptyPath +qtfrontend.PathWrongPath +qtfrontend.PathNotADir +qtfrontend.PathWrongPermissions +qtfrontend.PathDirEmpty + +errors.ErrUnknownError +errors.ErrEventAPILogout +errors.ErrUpgradeAPI +errors.ErrUpgradeJSON +errors.ErrUserAuth +errors.ErrQApplication +errors.ErrEmailExportFailed +errors.ErrEmailExportMissing +errors.ErrNothingToImport +errors.ErrEmailImportFailed +errors.ErrDraftImportFailed +errors.ErrDraftLabelFailed +errors.ErrEncryptMessageAttachment +errors.ErrEncryptMessage +errors.ErrNoInternetWhileImport +errors.ErrUnlockUser +errors.ErrSourceMessageNotSelected + +source.ErrCannotParseMail +source.ErrWrongLoginOrPassword +source.ErrWrongServerPathOrPort +source.ErrWrongAuthMethod +source.ErrIMAPFetchFailed + +qtfrontend.ErrLocalSourceLoadFailed +qtfrontend.ErrPMLoadFailed +qtfrontend.ErrRemoteSourceLoadFailed +qtfrontend.ErrLoadAccountList +qtfrontend.ErrExit +qtfrontend.ErrRetry +qtfrontend.ErrAsk +qtfrontend.ErrImportFailed +qtfrontend.ErrCreateLabelFailed +qtfrontend.ErrCreateFolderFailed +qtfrontend.ErrUpdateLabelFailed +qtfrontend.ErrUpdateFolderFailed +qtfrontend.ErrFillFolderName +qtfrontend.ErrSelectFolderColor +qtfrontend.ErrNoInternet + +qtfrontend.FolderTypeSystem +qtfrontend.FolderTypeLabel +qtfrontend.FolderTypeFolder +qtfrontend.FolderTypeExternal + +backend.ProgressInit +backend.ProgressLooping +backend.ErrPMAPIMessageTooLarge + +qtfrontend.StatusNoInternet +qtfrontend.StatusCheckingInternet +qtfrontend.StatusNewVersionAvailable +qtfrontend.StatusUpToDate +qtfrontend.StatusForceUpgrade +" + +first=true + + +if true; then + echo '// +build ignore' + echo '' + echo 'package main' + echo '' + echo 'import (' + echo ' "github.com/ProtonMail/Import-Export/backend"' + echo ' "github.com/ProtonMail/Import-Export/backend/source"' + echo ' "github.com/ProtonMail/Import-Export/backend/errors"' + echo ' "github.com/ProtonMail/Import-Export/frontend"' + echo ' "fmt"' + echo ')' + echo '' + echo 'func main(){' + echo ' checkValues := map[int]string{}' + echo ' checkDuplicates := map[string]bool{}' + echo ' fmt.Print("{")' + for c in $list + do + if ! $first; then + echo 'fmt.Print(",")' + fi + + if [[ $c =~ .*Err ]]; then + ## Add check that all Err have different value + echo 'if enumName,ok := checkValues[int('$c')]; ok {' + echo ' panic("Enum '$c' and "+enumName+" has same value")' + echo '}' + echo 'checkValues[int('$c')]="'$c'"' + fi + + cname=`echo $c | cut -d. -f2` + lowCase=${cname,} + + ## Add check that all qml enums have different value + echo 'if checkDuplicates["'$lowCase'"]{' + echo ' panic("Enum with same lowcase name as '$c' has already been registered")' + echo '}' + echo 'checkDuplicates["'$lowCase'"]=true' + + ## add value in lowercase + echo 'fmt.Printf("\"'$lowCase'\":%#v",'$c')' + + first=false + done + echo ' fmt.Print("}")' + echo '}' +fi > main.go + + +if true; then +echo -n "property var enums : JSON.parse('" +go run main.go || exit 5 +echo -n "')" +fi > out.qml + +rm main.go +sed -i "s/property var enums : JSON.parse.*$/`cat out.qml`/" ./qml/Gui.qml +rm out.qml +