Shared GUI for Bridge and Import/Export
This commit is contained in:
parent
b598779c0f
commit
49316a935c
|
@ -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
|
23
BUILDS.md
23
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:
|
||||
|
|
42
Changelog.md
42
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.
|
||||
|
|
63
Makefile
63
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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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++
|
||||
|
|
|
@ -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++
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
8
go.sum
8
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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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 <a href=\"https://protonmail.com/bridge/faq#c15\">FAQ</a>.",
|
||||
"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()
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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<root.model.length; ++i){
|
||||
metrics.text = i<0 ? root.placeholderText : root.model[i]+"MM" // "M" for extra space
|
||||
max = Math.max(max, metrics.width)
|
||||
}
|
||||
root.width = root.spacing + max + root.spacing + indicatorIcon.width + root.spacing
|
||||
//console.log("width updated", root.placeholderText, root.width)
|
||||
}
|
||||
|
||||
TextMetrics {
|
||||
id: metrics
|
||||
font: root.font
|
||||
text: placeholderText
|
||||
}
|
||||
|
||||
|
||||
indicator: Text {
|
||||
id: indicatorIcon
|
||||
color: root.enabled ? dropDownStyle.highlight : dropDownStyle.inactive
|
||||
text: root.down ? Style.fa.chevron_up : Style.fa.chevron_down
|
||||
font.family: Style.fontawesome.name
|
||||
anchors {
|
||||
right: parent.right
|
||||
rightMargin: root.spacing
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
id: boxItem
|
||||
leftPadding: root.spacing
|
||||
rightPadding: root.spacing
|
||||
|
||||
text : enabled && root.currentIndex>=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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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)
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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 }
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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: "<b>"+qsTr("From:")+"</b> " + 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: "<b>"+qsTr("To:")+"</b> " + 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
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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() }
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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.<br>
|
||||
Please dowload and install the latest version to continue using %1.<br><br>
|
||||
<a href="%2">%2</a>',
|
||||
"Message for force-update in Linux").arg(go.programTitle).arg(go.landingPage)
|
||||
} else {
|
||||
return qsTr('You are using an outdated version of our software.<br>
|
||||
Please dowload and install the latest version to continue using %1.<br><br>
|
||||
You can continue with update or download and install the new version manually from<br><br>
|
||||
<a href="%2">%2</a>',
|
||||
"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.<br>
|
||||
Check <a href="%2">release notes</a> to learn what is new in %3.<br>
|
||||
Use your package manager to update or download and install new version manually from<br><br>
|
||||
<a href="%4">%4</a>',
|
||||
"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.<br>
|
||||
Check <a href="%2">release notes</a> to learn what is new in %3.<br>
|
||||
You can continue with update or download and install new version manually from<br><br>
|
||||
<a href="%4">%4</a>',
|
||||
"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()
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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 '<font color="'+iconColor+'">'+ icon + "</font> " + 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -327,6 +327,7 @@ Window {
|
|||
|
||||
function show() {
|
||||
prefill()
|
||||
description.focus=true
|
||||
root.visible=true
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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()
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
//console.log(" listing ",i, entry.folderId)
|
||||
if (entry.folderId == id) {
|
||||
entry.isFolderSelected = toSelect
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
console.log(" match & set", entry.toSelect)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTypeSelected (model, folderType , toSelect ) {
|
||||
console.log(" select type ", folderType, toSelect)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
console.log(" listing ",i, entry.folderType)
|
||||
if (entry.folderType == folderType) {
|
||||
entry.isFolderSelected = toSelect
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
console.log(" match & set", entry.isFolderSelected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setFromToDate (model, id , from, to ) {
|
||||
console.log(" set from to date id ", id, from, to)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
// console.log(" listing ",i, entry.targetFolderID)
|
||||
if (entry.folderId == id) {
|
||||
entry.fromDate = from
|
||||
entry.toDate = to
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
console.log(" match & set", entry.fromDate, entry.toDate)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setTargetFolderID (model, id , target ) {
|
||||
console.log(" set target folder id ", id, target)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
// console.log(" listing ",i, entry.targetFolderID)
|
||||
if (entry.folderId == id) {
|
||||
entry.targetFolderID=target
|
||||
if (target=="") entry.targetLabelIDs=target
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
console.log(" match & set", entry.targetFolderID)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getById ( model, folderId ) {
|
||||
console.log("called get object", folderId)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
//console.log(" listing ",i, entry.folderId)
|
||||
if (entry.folderId == folderId) return entry
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
|
||||
function addTargetLabelID (model, id , label ) {
|
||||
console.log(" add target label ", id, label)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
//console.log(" listing ",i, entry.targetLabelIDs)
|
||||
if (entry.folderId == id) {
|
||||
entry.targetLabelIDs += ";" + label
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
//console.log(" match & set", entry.targetLabelIDs)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function removeTargetLabelID (model, id , label ) {
|
||||
console.log(" remove target label ", id, label)
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
//console.log(" listing ",i, entry.targetLabelIDs)
|
||||
if (entry.folderId == id) {
|
||||
var update = entry.targetLabelIDs
|
||||
update = update.replace(new RegExp(';'+label,'gi'), "" )
|
||||
entry.targetLabelIDs = update
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
//console.log(" match & set", entry.targetLabelIDs)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateSelection(model) {
|
||||
console.log("Source folders changed", model)
|
||||
model.selectedLabels = true
|
||||
model.selectedFolders = true
|
||||
model.atLeastOneSelected = false
|
||||
for (var i= 0; i<model.count; i++) {
|
||||
var item = model.get(i)
|
||||
//console.log(" looping ", item.folderType)
|
||||
|
||||
if ( item.folderType == testgui.enums.folderTypeFolder ) model.selectedFolders = item.isFolderSelected && model.selectedFolders
|
||||
if ( item.folderType == testgui.enums.folderTypeLabel ) model.selectedLabels = item.isFolderSelected && model.selectedLabels
|
||||
if ( item.isFolderSelected ) atLeastOneSelected = true
|
||||
|
||||
if (!model.selectedLabels && !model.selectedFolders && model.atLeastOneSelected) break
|
||||
}
|
||||
}
|
||||
|
||||
function hasFolderWithName(model, folderName) {
|
||||
for (var i= 0; i<model.count; i++) {
|
||||
if (model.get(i).folderName == folderName) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
function hasTarget(model) {
|
||||
for (var i= 0; i<model.count; i++) {
|
||||
if (model.get(i).targetFolderID != "") return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
ListModel{
|
||||
id: errorList
|
||||
|
||||
ListElement{ mailSubject : "Want some soup" ; mailDate : "March 2 , 2019 12 : 00 : 22" ; inputFolder : "Archive" ; mailFrom : "me@me.me" ; errorMessage : "Something went wrong and import retry was not successful" ; }
|
||||
ListElement{ mailSubject : "RE: Office party" ; mailDate : "March 2 , 2019 12 : 00 : 22" ; inputFolder : "Archive" ; mailFrom : "me@me.me" ; errorMessage : "Something went wrong and import retry was not successful" ; }
|
||||
ListElement{ mailSubject : "Hello Andy" ; mailDate : "March 2 , 2019 12 : 00 : 22" ; inputFolder : "Archive" ; mailFrom : "me@me.me" ; errorMessage : "Something went wrong and import retry was not successful" ; }
|
||||
ListElement{ mailSubject : "Pop art is cool again" ; mailDate : "March 2 , 2019 12 : 00 : 22" ; inputFolder : "Archive" ; mailFrom : "me@me.me" ; errorMessage : "Something went wrong and import retry was not successful" ; }
|
||||
ListElement{ mailSubject : "Check this cute kittens play volleyball on Copacabanana beach" ; mailDate : "March 2 , 2019 12 : 00 : 22" ; inputFolder : "Archive" ; mailFrom : "me@me.me" ; errorMessage : "Something went wrong and import retry was not successful" ; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// moc go
|
||||
QtObject {
|
||||
id: go
|
||||
|
||||
property int isAutoStart : 1
|
||||
property bool isFirstStart : false
|
||||
property string currentAddress : "none"
|
||||
//property string goos : "windows"
|
||||
property string goos : "linux"
|
||||
//property string goos : "darwin"
|
||||
property bool isDefaultPort : false
|
||||
|
||||
property string wrongCredentials
|
||||
property string wrongMailboxPassword
|
||||
property string canNotReachAPI
|
||||
property string versionCheckFailed
|
||||
property string credentialsNotRemoved
|
||||
property string bugNotSent
|
||||
property string bugReportSent
|
||||
|
||||
property string programTitle : "ProtonMail Import/Export Tool"
|
||||
property string newversion : "q0.1.0"
|
||||
property string landingPage : "https://jakub.cuth.sk/bridge"
|
||||
property string changelog : "• Lorem ipsum dolor sit amet\n• consetetur sadipscing elitr,\n• sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat,\n• sed diam voluptua.\n• At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet."
|
||||
//property string changelog : ""
|
||||
property string bugfixes : "• lorem ipsum dolor sit amet;• consetetur sadipscing elitr;• sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat;• sed diam voluptua;• at vero eos et accusam et justo duo dolores et ea rebum;• stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet"
|
||||
//property string bugfixes : ""
|
||||
|
||||
property real progress: 0.0
|
||||
property int progressFails: 0
|
||||
property string progressDescription: "nothing"
|
||||
property string progressInit: "init"
|
||||
property int total: 42
|
||||
property string importLogFileName: "importLogFileName not set"
|
||||
|
||||
signal toggleMainWin(int systX, int systY, int systW, int systH)
|
||||
|
||||
signal showWindow()
|
||||
signal showHelp()
|
||||
signal showQuit()
|
||||
|
||||
signal notifyVersionIsTheLatest()
|
||||
signal setUpdateState(string updateState)
|
||||
|
||||
signal showMainWin()
|
||||
signal hideMainWin()
|
||||
signal simpleErrorHappen()
|
||||
signal importStructuresLoadFinished(bool okay)
|
||||
signal exportStructureLoadFinished(bool okay)
|
||||
signal folderUpdateFinished()
|
||||
signal loginFinished()
|
||||
|
||||
signal processFinished()
|
||||
signal toggleAutoStart()
|
||||
signal notifyBubble(int tabIndex, string message)
|
||||
signal runCheckVersion(bool showMessage)
|
||||
signal setAddAccountWarning(string message)
|
||||
signal notifyUpgrade()
|
||||
signal updateFinished(bool hasError)
|
||||
|
||||
signal notifyLogout(string accname)
|
||||
|
||||
signal notifyError(int errCode)
|
||||
property string errorDescription : ""
|
||||
|
||||
function delay(duration) {
|
||||
var timeStart = new Date().getTime();
|
||||
|
||||
while (new Date().getTime() - timeStart < duration) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function sendBug(desc,client,address){
|
||||
console.log("Test: sending ", desc, client, address)
|
||||
return desc.includes("fail")
|
||||
}
|
||||
|
||||
function deleteAccount(index,remove) {
|
||||
console.log ("Test: Delete account ",index," and remove prefences "+remove)
|
||||
workAndClose("deleteAccount")
|
||||
accountsModel.remove(index)
|
||||
}
|
||||
|
||||
function logoutAccount(index) {
|
||||
accountsModel.get(index).status="disconnected"
|
||||
workAndClose("logout")
|
||||
}
|
||||
|
||||
function login(username,password) {
|
||||
delay(700)
|
||||
if (password=="wrong") {
|
||||
setAddAccountWarning("Wrong password")
|
||||
return -1
|
||||
}
|
||||
if (username=="2fa") {
|
||||
return 1
|
||||
}
|
||||
if (username=="mbox") {
|
||||
return 2
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function auth2FA(twoFACode){
|
||||
delay(700)
|
||||
if (twoFACode=="wrong") {
|
||||
setAddAccountWarning("Wrong 2FA")
|
||||
return -1
|
||||
}
|
||||
if (twoFACode=="mbox") {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function addAccount(mailboxPass) {
|
||||
delay(700)
|
||||
if (mailboxPass=="wrong") {
|
||||
setAddAccountWarning("Wrong mailbox password")
|
||||
return -1
|
||||
}
|
||||
accountsModel.append({
|
||||
"account" : testgui.winMain.dialogAddUser.username,
|
||||
"status" : "connected",
|
||||
"isExpanded":true,
|
||||
"hostname" : "127.0.0.1",
|
||||
"password" : "ZI9tKp+ryaxmbpn2E12",
|
||||
"security" : "StarTLS",
|
||||
"portSMTP" : 1025,
|
||||
"portIMAP" : 1143,
|
||||
"aliases" : "cuto@pm.com;jaku@pm.com;theHorriblySlowMurderWithExtremelyInefficientWeapon@youtube.com",
|
||||
"isCombinedAddressMode": true
|
||||
})
|
||||
workAndClose("addAccount")
|
||||
}
|
||||
|
||||
property SequentialAnimation animateProgressBarUpgrade : SequentialAnimation {
|
||||
// version
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 1; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000; }
|
||||
|
||||
// download
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 2; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.01; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.1; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.3; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.5; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.8; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 1.0; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000; }
|
||||
|
||||
// verify
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.0; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 3; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000; }
|
||||
|
||||
// unzip
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 4; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.01; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.1; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.3; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.5; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.8; duration: 1; }
|
||||
PropertyAnimation{ duration: 500; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 1.0; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000; }
|
||||
|
||||
// update
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.0; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 5; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000; }
|
||||
|
||||
// quit
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: 6; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000; }
|
||||
}
|
||||
|
||||
property SequentialAnimation animateProgressBar : SequentialAnimation {
|
||||
id: apb
|
||||
property real speedup : 1.0;
|
||||
PropertyAnimation{ target: go; properties: "importLogFileName"; to: ""; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: go.progressInit; duration: 1; }
|
||||
PropertyAnimation{ duration: 2000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "importLogFileName"; to: "/home/cuto/.local/state/protonmail/import-export/c0/import_1554732302.log"; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "total"; to: 11; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "total"; to: 24; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "total"; to: 42; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: "/path/to/export/folder/"; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.01; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.1; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.3; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressFails"; to: 1; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: "/path/to/Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet/export/folder/"; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.5; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.8; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progressFails"; to: 13; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progressDescription"; to: "/path/to/export/lastfolder/"; duration: 1; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 0.9; duration: 1; }
|
||||
PropertyAnimation{ duration: 1000/apb.speedup; }
|
||||
PropertyAnimation{ target: go; properties: "progress"; to: 1.0; duration: 1; }
|
||||
}
|
||||
|
||||
function pauseProcess() {
|
||||
console.log("paused at ", go.progress)
|
||||
go.animateProgressBar.pause()
|
||||
}
|
||||
|
||||
function resumeProcess() {
|
||||
console.log("resumed at ", go.progress)
|
||||
go.animateProgressBar.resume()
|
||||
}
|
||||
|
||||
function cancelProcess(clearUnfinished) {
|
||||
console.log("stopped at ", go.progress, " clearing unfinished", clearUnfinished)
|
||||
go.animateProgressBar.stop()
|
||||
}
|
||||
|
||||
property Timer timer : Timer {
|
||||
id: timer
|
||||
interval : 1000
|
||||
repeat : false
|
||||
property string work
|
||||
onTriggered : {
|
||||
console.log("triggered "+timer.work)
|
||||
switch (timer.work) {
|
||||
case "isNewVersionAvailable" :
|
||||
case "clearCache" :
|
||||
case "clearKeychain" :
|
||||
case "logout" :
|
||||
go.processFinished()
|
||||
break;
|
||||
|
||||
case "addAccount" :
|
||||
case "login" :
|
||||
go.loginFinished()
|
||||
break;
|
||||
|
||||
case "loadStructureForExport" :
|
||||
go.exportStructureLoadFinished(true)
|
||||
break;
|
||||
|
||||
case "setupAndLoadForImport" :
|
||||
case "loadStructuresForImport" :
|
||||
go.importStructuresLoadFinished(true)
|
||||
break;
|
||||
|
||||
case "startExport" :
|
||||
case "startImport" :
|
||||
go.animateProgressBar.start()
|
||||
break;
|
||||
|
||||
case "startUpgrade":
|
||||
go.animateProgressBarUpgrade.start()
|
||||
go.updateFinished(true)
|
||||
|
||||
default:
|
||||
console.log("no action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function workAndClose(workDescription) {
|
||||
go.progress=0.0
|
||||
timer.work = workDescription
|
||||
timer.start()
|
||||
}
|
||||
|
||||
function startUpgrade() {
|
||||
timer.work="startUpgrade"
|
||||
timer.start()
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function checkPathStatus(path) {
|
||||
if ( path == "" ) return testgui.enums.pathEmptyPath
|
||||
if ( path == "wrong" ) return testgui.enums.pathWrongPath
|
||||
if ( path == "/root" ) return testgui.enums.pathWrongPermissions
|
||||
if ( path == "/home/cuto/file" ) return testgui.enums.pathOK | testgui.enums.pathNotADir
|
||||
if ( path == "/home/cuto/empty/" ) return testgui.enums.pathOK | testgui.enums.pathDirEmpty
|
||||
if ( path == "/home/cuto/Desktop" ) return testgui.enums.pathOK | testgui.enums.pathDirEmpty
|
||||
if ( path == "/home/cuto/nonEmpty/" ) return testgui.enums.pathOK
|
||||
if ( path == "/home/cuto/ok/" ) return testgui.enums.pathOK
|
||||
return testgui.enums.pathWrongPath
|
||||
}
|
||||
|
||||
|
||||
function strategies() {
|
||||
return ["strategy1", "strategy2"]
|
||||
}
|
||||
|
||||
function notPresentStrategy() {
|
||||
return ["notStrategy1", "notStrategy2"]
|
||||
}
|
||||
|
||||
function loadAccounts() {
|
||||
console.log("Test: Account loaded")
|
||||
}
|
||||
|
||||
function openDownloadLink(){
|
||||
}
|
||||
|
||||
function loadStructureForExport(address) {
|
||||
workAndClose("loadStructureForExport")
|
||||
}
|
||||
|
||||
function loadStructuresForImport(address) {
|
||||
workAndClose("loadStructuresForImport")
|
||||
}
|
||||
|
||||
function setupAndLoadForImport(address) {
|
||||
workAndClose("setupAndLoadForImport")
|
||||
}
|
||||
|
||||
function buildStructuresMapping() {
|
||||
var model = structureExternal
|
||||
console.log(" buildStructuresMapping aka reset all")
|
||||
for (var i= -1; i<model.count; i++) {
|
||||
console.log(" get ", i)
|
||||
var entry = i<0 ? model.globalOptions : model.get(i)
|
||||
console.log(" ", entry.folderId, entry.targetFolderID, entry.targetLabelIDs)
|
||||
if (entry.folderType == testgui.enums.folderTypeSystem) {
|
||||
entry.targetLabelIDs = ";20;29"
|
||||
entry.targetFolderID = entry.folderId=="global--uniq" ? "" : (
|
||||
i%2==0 ? "14" : "16"
|
||||
)
|
||||
entry.fromDate = 0
|
||||
entry.toDate = 0
|
||||
} else {
|
||||
entry.targetLabelIDs = ""
|
||||
entry.targetFolderID = ""
|
||||
entry.fromDate = 300000
|
||||
entry.toDate = 15000000
|
||||
}
|
||||
entry.isFolderSelected = false
|
||||
console.log(" set ", i, entry.targetFolderID, entry.targetLabelIDs)
|
||||
if (i<0) model.globalOptionsChanged()
|
||||
else model.set(i,entry)
|
||||
}
|
||||
}
|
||||
|
||||
function startExport(path,address,format,dateRange,encryptedBodies) {
|
||||
console.log ("Starting export: ",path, address, format, dateRange, encryptedBodies)
|
||||
workAndClose("startExport")
|
||||
}
|
||||
|
||||
function startImport(address) {
|
||||
workAndClose("startImport")
|
||||
}
|
||||
|
||||
function resetSource() {
|
||||
}
|
||||
|
||||
function setupRemoteSource(username, password, host, port) {
|
||||
console.log("setup remote source", username, password, host, port)
|
||||
}
|
||||
|
||||
function setupLocalSource(path) {
|
||||
console.log("setup local source", path)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
function switchAddressMode(username){
|
||||
for (var iAcc=0; iAcc < accountsModel.count; iAcc++) {
|
||||
if (accountsModel.get(iAcc).account == username ) {
|
||||
accountsModel.get(iAcc).isCombinedAddressMode = !accountsModel.get(iAcc).isCombinedAddressMode
|
||||
break
|
||||
}
|
||||
}
|
||||
workAndClose("switchAddressMode")
|
||||
}
|
||||
|
||||
function isNewVersionAvailable(showMessage){
|
||||
if (testroot.newVersion) {
|
||||
setUpdateState("oldVersion")
|
||||
} else {
|
||||
setUpdateState("upToDate")
|
||||
if(showMessage) {
|
||||
notifyVersionIsTheLatest()
|
||||
}
|
||||
}
|
||||
workAndClose("isNewVersionAvailable")
|
||||
//notifyBubble(2,go.versionCheckFailed)
|
||||
return 0
|
||||
}
|
||||
|
||||
function getLocalVersionInfo(){}
|
||||
|
||||
function getBackendVersion() {
|
||||
return "PIMP 1.0"
|
||||
}
|
||||
|
||||
property bool isConnectionOK : true
|
||||
signal setConnectionStatus(bool isAvailable)
|
||||
|
||||
function configureAppleMail(iAccount,iAddress) {
|
||||
console.log ("Test: autoconfig account ",iAccount," address ",iAddress)
|
||||
}
|
||||
|
||||
function openLogs() {
|
||||
Qt.openUrlExternally("file:///home/cuto/")
|
||||
}
|
||||
|
||||
function highlightSystray() {
|
||||
test_systray.highlight()
|
||||
}
|
||||
|
||||
function normalSystray() {
|
||||
test_systray.normal()
|
||||
}
|
||||
|
||||
signal bubbleClosed()
|
||||
|
||||
function getIMAPPort() {
|
||||
return 1143
|
||||
}
|
||||
function getSMTPPort() {
|
||||
return 1025
|
||||
}
|
||||
|
||||
function isPortOpen(portstring){
|
||||
if (isNaN(portstring)) {
|
||||
return 1
|
||||
}
|
||||
var portnum = parseInt(portstring,10)
|
||||
if (portnum < 3333) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
signal openManual()
|
||||
|
||||
function clearCache() {
|
||||
workAndClose("clearCache")
|
||||
}
|
||||
|
||||
function clearKeychain() {
|
||||
workAndClose("clearKeychain")
|
||||
}
|
||||
|
||||
function leastUsedColor() {
|
||||
return "#cf5858"
|
||||
}
|
||||
|
||||
|
||||
function answerSkip(skipAll) {
|
||||
go.animateProgressBar.resume()
|
||||
}
|
||||
|
||||
function answerRetry(){
|
||||
go.animateProgressBar.resume()
|
||||
}
|
||||
|
||||
function createLabelOrFolder(address,fname,fcolor,isFolder,sourceID){
|
||||
console.log("-> 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")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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<<iota
|
||||
UserID
|
||||
Status
|
||||
Hostname
|
||||
Password
|
||||
Security
|
||||
PortIMAP
|
||||
PortSMTP
|
||||
Aliases
|
||||
IsExpanded
|
||||
IsCombinedAddressMode
|
||||
)
|
||||
|
||||
// Registration of new metatype before creating instance
|
||||
// NOTE: check it is run once per program. write a log
|
||||
func init() {
|
||||
AccountInfo_QRegisterMetaType()
|
||||
}
|
||||
|
||||
// AccountModel for providing container of accounts information to QML.
|
||||
// QML ListView connects the model from Go and it shows item (accounts) information.
|
||||
// Copied and edited from `github.com/therecipe/qt/internal/examples/sailfish/listview`.
|
||||
type AccountsModel 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 accounts. It is not accessed directly but using `data(index,role)`.
|
||||
_ []*AccountInfo `property:"accounts"`
|
||||
|
||||
// Method for adding account.
|
||||
_ func(*AccountInfo) `slot:"addAccount"`
|
||||
|
||||
// Method for retrieving account.
|
||||
_ func(row int) *AccountInfo `slot:"get"`
|
||||
|
||||
// Method for login/logout the account.
|
||||
_ func(row int) `slot:"toggleIsAvailable"`
|
||||
|
||||
// Method for removing account from list.
|
||||
_ func(row int) `slot:"removeAccount"`
|
||||
|
||||
_ int `property:"count"`
|
||||
}
|
||||
|
||||
// init is called by C constructor. It creates the map for item properties and
|
||||
// connects the methods.
|
||||
func (s *AccountsModel) init() {
|
||||
s.SetRoles(map[int]*core.QByteArray{
|
||||
Account: NewQByteArrayFromString("account"),
|
||||
UserID: NewQByteArrayFromString("userID"),
|
||||
Status: NewQByteArrayFromString("status"),
|
||||
Hostname: NewQByteArrayFromString("hostname"),
|
||||
Password: NewQByteArrayFromString("password"),
|
||||
Security: NewQByteArrayFromString("security"),
|
||||
PortIMAP: NewQByteArrayFromString("portIMAP"),
|
||||
PortSMTP: NewQByteArrayFromString("portSMTP"),
|
||||
Aliases: NewQByteArrayFromString("aliases"),
|
||||
IsExpanded: NewQByteArrayFromString("isExpanded"),
|
||||
IsCombinedAddressMode: NewQByteArrayFromString("isCombinedAddressMode"),
|
||||
})
|
||||
// Basic QAbstractListModel methods.
|
||||
s.ConnectData(s.data)
|
||||
s.ConnectRowCount(s.rowCount)
|
||||
s.ConnectColumnCount(s.columnCount)
|
||||
s.ConnectRoleNames(s.roleNames)
|
||||
// Custom AccountModel methods.
|
||||
s.ConnectGet(s.get)
|
||||
s.ConnectAddAccount(s.addAccount)
|
||||
s.ConnectToggleIsAvailable(s.toggleIsAvailable)
|
||||
s.ConnectRemoveAccount(s.removeAccount)
|
||||
}
|
||||
|
||||
// get returns account info pointer or create new empy if index is out of
|
||||
// range.
|
||||
func (s *AccountsModel) get(index int) *AccountInfo {
|
||||
if index < 0 || index >= 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())
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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
|
||||
}
|
||||
}
|
|
@ -1,23 +1,30 @@
|
|||
// +build !nogui
|
||||
|
||||
|
||||
#include "logs.h"
|
||||
#include "common.h"
|
||||
#include "_cgo_export.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include <QtGlobal>
|
||||
|
||||
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<char*>( (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<QVector<int> >();
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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
|
||||
}
|
|
@ -10,8 +10,9 @@
|
|||
extern "C" {
|
||||
#endif // C++
|
||||
|
||||
void InstallMessageHandler();
|
||||
;
|
||||
void InstallMessageHandler();
|
||||
void RegisterTypes();
|
||||
;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
|
@ -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 {} \;
|
|
@ -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)
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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<<iota
|
||||
Status
|
||||
Password
|
||||
Aliases
|
||||
IsExpanded
|
||||
// Folder info
|
||||
FolderId
|
||||
FolderName
|
||||
FolderColor
|
||||
FolderType
|
||||
FolderEntries
|
||||
IsFolderSelected
|
||||
FolderFromDate
|
||||
FolderToDate
|
||||
TargetFolderID
|
||||
TargetLabelIDs
|
||||
// Error list
|
||||
MailSubject
|
||||
MailDate
|
||||
MailFrom
|
||||
InputFolder
|
||||
ErrorMessage
|
||||
)
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
}
|
||||
*/
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
*/
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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]
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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: "",
|
||||
}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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{}
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +build !nogui
|
||||
|
||||
package qtie
|
||||
|
||||
type panicHandler interface {
|
||||
HandlePanic()
|
||||
SendReport(interface{})
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
// +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)
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
<!--This file is process during qtdeploy and resources are added to executable.-->
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="ProtonUI">
|
||||
<file alias="qmldir" >../qml/ProtonUI/qmldir</file>
|
||||
<file alias="AccessibleButton.qml" >../qml/ProtonUI/AccessibleButton.qml</file>
|
||||
<file alias="AccessibleText.qml" >../qml/ProtonUI/AccessibleText.qml</file>
|
||||
<file alias="AccessibleSelectableText.qml" >../qml/ProtonUI/AccessibleSelectableText.qml</file>
|
||||
<file alias="AccountView.qml" >../qml/ProtonUI/AccountView.qml</file>
|
||||
<file alias="AddAccountBar.qml" >../qml/ProtonUI/AddAccountBar.qml</file>
|
||||
<file alias="BubbleNote.qml" >../qml/ProtonUI/BubbleNote.qml</file>
|
||||
<file alias="BugReportWindow.qml" >../qml/ProtonUI/BugReportWindow.qml</file>
|
||||
<file alias="ButtonIconText.qml" >../qml/ProtonUI/ButtonIconText.qml</file>
|
||||
<file alias="ButtonRounded.qml" >../qml/ProtonUI/ButtonRounded.qml</file>
|
||||
<file alias="CheckBoxLabel.qml" >../qml/ProtonUI/CheckBoxLabel.qml</file>
|
||||
<file alias="ClickIconText.qml" >../qml/ProtonUI/ClickIconText.qml</file>
|
||||
<file alias="Dialog.qml" >../qml/ProtonUI/Dialog.qml</file>
|
||||
<file alias="DialogAddUser.qml" >../qml/ProtonUI/DialogAddUser.qml</file>
|
||||
<file alias="DialogUpdate.qml" >../qml/ProtonUI/DialogUpdate.qml</file>
|
||||
<file alias="DialogConnectionTroubleshoot.qml" >../qml/ProtonUI/DialogConnectionTroubleshoot.qml</file>
|
||||
<file alias="FileAndFolderSelect.qml" >../qml/ProtonUI/FileAndFolderSelect.qml</file>
|
||||
<file alias="InformationBar.qml" >../qml/ProtonUI/InformationBar.qml</file>
|
||||
<file alias="InputField.qml" >../qml/ProtonUI/InputField.qml</file>
|
||||
<file alias="InstanceExistsWindow.qml" >../qml/ProtonUI/InstanceExistsWindow.qml</file>
|
||||
<file alias="LogoHeader.qml" >../qml/ProtonUI/LogoHeader.qml</file>
|
||||
<file alias="PopupMessage.qml" >../qml/ProtonUI/PopupMessage.qml</file>
|
||||
<file alias="Style.qml" >../qml/ProtonUI/Style.qml</file>
|
||||
<file alias="TabButton.qml" >../qml/ProtonUI/TabButton.qml</file>
|
||||
<file alias="TabLabels.qml" >../qml/ProtonUI/TabLabels.qml</file>
|
||||
<file alias="TextLabel.qml" >../qml/ProtonUI/TextLabel.qml</file>
|
||||
<file alias="TextValue.qml" >../qml/ProtonUI/TextValue.qml</file>
|
||||
<file alias="TLSCertPinIssueBar.qml" >../qml/ProtonUI/TLSCertPinIssueBar.qml</file>
|
||||
<file alias="WindowTitleBar.qml" >../qml/ProtonUI/WindowTitleBar.qml</file>
|
||||
<file alias="fontawesome.ttf" >../share/fontawesome-webfont.ttf</file>
|
||||
</qresource>
|
||||
<qresource prefix="ProtonUI/images">
|
||||
<file alias="systray.png" >../share/icons/rounded-systray.png</file>
|
||||
<file alias="systray-warn.png" >../share/icons/rounded-syswarn.png</file>
|
||||
<file alias="systray-error.png" >../share/icons/rounded-syswarn.png</file>
|
||||
<file alias="systray-mono.png" >../share/icons/white-systray.png</file>
|
||||
<file alias="systray-warn-mono.png" >../share/icons/white-syswarn.png</file>
|
||||
<file alias="systray-error-mono.png">../share/icons/white-syserror.png</file>
|
||||
<file alias="icon.png" >../share/icons/rounded-app.png</file>
|
||||
<file alias="pm_logo.png" >../share/icons/pm_logo.png</file>
|
||||
<file alias="win10_Dash.png" >../share/icons/win10_Dash.png</file>
|
||||
<file alias="win10_Times.png" >../share/icons/win10_Times.png</file>
|
||||
<file alias="macos_gray.png" >../share/icons/macos_gray.png</file>
|
||||
<file alias="macos_red.png" >../share/icons/macos_red.png</file>
|
||||
<file alias="macos_red_hl.png" >../share/icons/macos_red_hl.png</file>
|
||||
<file alias="macos_red_dark.png" >../share/icons/macos_red_dark.png</file>
|
||||
<file alias="macos_yellow.png" >../share/icons/macos_yellow.png</file>
|
||||
<file alias="macos_yellow_hl.png" >../share/icons/macos_yellow_hl.png</file>
|
||||
<file alias="macos_yellow_dark.png" >../share/icons/macos_yellow_dark.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="BridgeUI">
|
||||
<file alias="qmldir" >../qml/BridgeUI/qmldir</file>
|
||||
<file alias="AccountDelegate.qml" >../qml/BridgeUI/AccountDelegate.qml</file>
|
||||
<file alias="BubbleMenu.qml" >../qml/BridgeUI/BubbleMenu.qml</file>
|
||||
<file alias="Credits.qml" >../qml/BridgeUI/Credits.qml</file>
|
||||
<file alias="DialogFirstStart.qml" >../qml/BridgeUI/DialogFirstStart.qml</file>
|
||||
<file alias="DialogPortChange.qml" >../qml/BridgeUI/DialogPortChange.qml</file>
|
||||
<file alias="DialogYesNo.qml" >../qml/BridgeUI/DialogYesNo.qml</file>
|
||||
<file alias="DialogTLSCertInfo.qml" >../qml/BridgeUI/DialogTLSCertInfo.qml</file>
|
||||
<file alias="HelpView.qml" >../qml/BridgeUI/HelpView.qml</file>
|
||||
<file alias="InfoWindow.qml" >../qml/BridgeUI/InfoWindow.qml</file>
|
||||
<file alias="MainWindow.qml" >../qml/BridgeUI/MainWindow.qml</file>
|
||||
<file alias="ManualWindow.qml" >../qml/BridgeUI/ManualWindow.qml</file>
|
||||
<file alias="OutgoingNoEncPopup.qml" >../qml/BridgeUI/OutgoingNoEncPopup.qml</file>
|
||||
<file alias="SettingsView.qml" >../qml/BridgeUI/SettingsView.qml</file>
|
||||
<file alias="StatusFooter.qml" >../qml/BridgeUI/StatusFooter.qml</file>
|
||||
<file alias="VersionInfo.qml" >../qml/BridgeUI/VersionInfo.qml</file>
|
||||
</qresource>
|
||||
<qresource>
|
||||
<file alias="ui.qml" >../qml/Gui.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
@ -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"`
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
<!--This file is process during qtdeploy and resources are added to executable.-->
|
||||
<!DOCTYPE RCC>
|
||||
<RCC version="1.0">
|
||||
<qresource prefix="ProtonUI">
|
||||
<file alias="qmldir" >./qml/ProtonUI/qmldir</file>
|
||||
<file alias="AccessibleButton.qml" >./qml/ProtonUI/AccessibleButton.qml</file>
|
||||
<file alias="AccessibleText.qml" >./qml/ProtonUI/AccessibleText.qml</file>
|
||||
<file alias="AccessibleSelectableText.qml" >./qml/ProtonUI/AccessibleSelectableText.qml</file>
|
||||
<file alias="AccountView.qml" >./qml/ProtonUI/AccountView.qml</file>
|
||||
<file alias="AddAccountBar.qml" >./qml/ProtonUI/AddAccountBar.qml</file>
|
||||
<file alias="BubbleNote.qml" >./qml/ProtonUI/BubbleNote.qml</file>
|
||||
<file alias="BugReportWindow.qml" >./qml/ProtonUI/BugReportWindow.qml</file>
|
||||
<file alias="ButtonIconText.qml" >./qml/ProtonUI/ButtonIconText.qml</file>
|
||||
<file alias="ButtonRounded.qml" >./qml/ProtonUI/ButtonRounded.qml</file>
|
||||
<file alias="CheckBoxLabel.qml" >./qml/ProtonUI/CheckBoxLabel.qml</file>
|
||||
<file alias="ClickIconText.qml" >./qml/ProtonUI/ClickIconText.qml</file>
|
||||
<file alias="Dialog.qml" >./qml/ProtonUI/Dialog.qml</file>
|
||||
<file alias="DialogAddUser.qml" >./qml/ProtonUI/DialogAddUser.qml</file>
|
||||
<file alias="DialogUpdate.qml" >./qml/ProtonUI/DialogUpdate.qml</file>
|
||||
<file alias="DialogConnectionTroubleshoot.qml" >./qml/ProtonUI/DialogConnectionTroubleshoot.qml</file>
|
||||
<file alias="FileAndFolderSelect.qml" >./qml/ProtonUI/FileAndFolderSelect.qml</file>
|
||||
<file alias="InfoToolTip.qml" >./qml/ProtonUI/InfoToolTip.qml</file>
|
||||
<file alias="InformationBar.qml" >./qml/ProtonUI/InformationBar.qml</file>
|
||||
<file alias="InputBox.qml" >./qml/ProtonUI/InputBox.qml</file>
|
||||
<file alias="InputField.qml" >./qml/ProtonUI/InputField.qml</file>
|
||||
<file alias="InstanceExistsWindow.qml" >./qml/ProtonUI/InstanceExistsWindow.qml</file>
|
||||
<file alias="LogoHeader.qml" >./qml/ProtonUI/LogoHeader.qml</file>
|
||||
<file alias="PopupMessage.qml" >./qml/ProtonUI/PopupMessage.qml</file>
|
||||
<file alias="RoundedRectangle.qml" >./qml/ProtonUI/RoundedRectangle.qml</file>
|
||||
<file alias="Style.qml" >./qml/ProtonUI/Style.qml</file>
|
||||
<file alias="TabButton.qml" >./qml/ProtonUI/TabButton.qml</file>
|
||||
<file alias="TabLabels.qml" >./qml/ProtonUI/TabLabels.qml</file>
|
||||
<file alias="TextLabel.qml" >./qml/ProtonUI/TextLabel.qml</file>
|
||||
<file alias="TextValue.qml" >./qml/ProtonUI/TextValue.qml</file>
|
||||
<file alias="TLSCertPinIssueBar.qml" >./qml/ProtonUI/TLSCertPinIssueBar.qml</file>
|
||||
<file alias="WindowTitleBar.qml" >./qml/ProtonUI/WindowTitleBar.qml</file>
|
||||
<file alias="fontawesome.ttf" >./share/fontawesome-webfont.ttf</file>
|
||||
</qresource>
|
||||
<qresource prefix="ProtonUI/images">
|
||||
<file alias="systray.png" >./share/icons/rounded-systray.png</file>
|
||||
<file alias="systray-warn.png" >./share/icons/rounded-syswarn.png</file>
|
||||
<file alias="systray-error.png" >./share/icons/rounded-syswarn.png</file>
|
||||
<file alias="systray-mono.png" >./share/icons/white-systray.png</file>
|
||||
<file alias="systray-warn-mono.png" >./share/icons/white-syswarn.png</file>
|
||||
<file alias="systray-error-mono.png">./share/icons/white-syserror.png</file>
|
||||
<file alias="icon.png" >./share/icons/rounded-app.png</file>
|
||||
<file alias="pm_logo.png" >./share/icons/pm_logo.png</file>
|
||||
<file alias="win10_Dash.png" >./share/icons/win10_Dash.png</file>
|
||||
<file alias="win10_Times.png" >./share/icons/win10_Times.png</file>
|
||||
<file alias="macos_gray.png" >./share/icons/macos_gray.png</file>
|
||||
<file alias="macos_red.png" >./share/icons/macos_red.png</file>
|
||||
<file alias="macos_red_hl.png" >./share/icons/macos_red_hl.png</file>
|
||||
<file alias="macos_red_dark.png" >./share/icons/macos_red_dark.png</file>
|
||||
<file alias="macos_yellow.png" >./share/icons/macos_yellow.png</file>
|
||||
<file alias="macos_yellow_hl.png" >./share/icons/macos_yellow_hl.png</file>
|
||||
<file alias="macos_yellow_dark.png" >./share/icons/macos_yellow_dark.png</file>
|
||||
</qresource>
|
||||
<qresource prefix="BridgeUI">
|
||||
<file alias="qmldir" >./qml/BridgeUI/qmldir</file>
|
||||
<file alias="AccountDelegate.qml" >./qml/BridgeUI/AccountDelegate.qml</file>
|
||||
<file alias="BubbleMenu.qml" >./qml/BridgeUI/BubbleMenu.qml</file>
|
||||
<file alias="Credits.qml" >./qml/BridgeUI/Credits.qml</file>
|
||||
<file alias="DialogFirstStart.qml" >./qml/BridgeUI/DialogFirstStart.qml</file>
|
||||
<file alias="DialogPortChange.qml" >./qml/BridgeUI/DialogPortChange.qml</file>
|
||||
<file alias="DialogYesNo.qml" >./qml/BridgeUI/DialogYesNo.qml</file>
|
||||
<file alias="DialogTLSCertInfo.qml" >./qml/BridgeUI/DialogTLSCertInfo.qml</file>
|
||||
<file alias="HelpView.qml" >./qml/BridgeUI/HelpView.qml</file>
|
||||
<file alias="InfoWindow.qml" >./qml/BridgeUI/InfoWindow.qml</file>
|
||||
<file alias="MainWindow.qml" >./qml/BridgeUI/MainWindow.qml</file>
|
||||
<file alias="ManualWindow.qml" >./qml/BridgeUI/ManualWindow.qml</file>
|
||||
<file alias="OutgoingNoEncPopup.qml" >./qml/BridgeUI/OutgoingNoEncPopup.qml</file>
|
||||
<file alias="SettingsView.qml" >./qml/BridgeUI/SettingsView.qml</file>
|
||||
<file alias="StatusFooter.qml" >./qml/BridgeUI/StatusFooter.qml</file>
|
||||
<file alias="VersionInfo.qml" >./qml/BridgeUI/VersionInfo.qml</file>
|
||||
</qresource>
|
||||
<qresource prefix="ImportExportUI">
|
||||
<file alias="qmldir" >./qml/ImportExportUI/qmldir</file>
|
||||
<file alias="AccountDelegate.qml" >./qml/ImportExportUI/AccountDelegate.qml</file>
|
||||
<file alias="Credits.qml" >./qml/ImportExportUI/Credits.qml</file>
|
||||
<file alias="DateBox.qml" >./qml/ImportExportUI/DateBox.qml</file>
|
||||
<file alias="DateInput.qml" >./qml/ImportExportUI/DateInput.qml</file>
|
||||
<file alias="DateRange.qml" >./qml/ImportExportUI/DateRange.qml</file>
|
||||
<file alias="DateRangeMenu.qml" >./qml/ImportExportUI/DateRangeMenu.qml</file>
|
||||
<file alias="DateRangeFunctions.qml" >./qml/ImportExportUI/DateRangeFunctions.qml</file>
|
||||
<file alias="DialogExport.qml" >./qml/ImportExportUI/DialogExport.qml</file>
|
||||
<file alias="DialogImport.qml" >./qml/ImportExportUI/DialogImport.qml</file>
|
||||
<file alias="DialogYesNo.qml" >./qml/ImportExportUI/DialogYesNo.qml</file>
|
||||
<file alias="ExportStructure.qml" >./qml/ImportExportUI/ExportStructure.qml</file>
|
||||
<file alias="FilterStructure.qml" >./qml/ImportExportUI/FilterStructure.qml</file>
|
||||
<file alias="FolderRowButton.qml" >./qml/ImportExportUI/FolderRowButton.qml</file>
|
||||
<file alias="HelpView.qml" >./qml/ImportExportUI/HelpView.qml</file>
|
||||
<file alias="IEStyle.qml" >./qml/ImportExportUI/IEStyle.qml</file>
|
||||
<file alias="ImportDelegate.qml" >./qml/ImportExportUI/ImportDelegate.qml</file>
|
||||
<file alias="ImportReport.qml" >./qml/ImportExportUI/ImportReport.qml</file>
|
||||
<file alias="ImportReportCell.qml" >./qml/ImportExportUI/ImportReportCell.qml</file>
|
||||
<file alias="ImportSourceButton.qml" >./qml/ImportExportUI/ImportSourceButton.qml</file>
|
||||
<file alias="ImportStructure.qml" >./qml/ImportExportUI/ImportStructure.qml</file>
|
||||
<file alias="InlineDateRange.qml" >./qml/ImportExportUI/InlineDateRange.qml</file>
|
||||
<file alias="InlineLabelSelect.qml" >./qml/ImportExportUI/InlineLabelSelect.qml</file>
|
||||
<file alias="LabelIconList.qml" >./qml/ImportExportUI/LabelIconList.qml</file>
|
||||
<file alias="MainWindow.qml" >./qml/ImportExportUI/MainWindow.qml</file>
|
||||
<file alias="OutputFormat.qml" >./qml/ImportExportUI/OutputFormat.qml</file>
|
||||
<file alias="PopupEditFolder.qml" >./qml/ImportExportUI/PopupEditFolder.qml</file>
|
||||
<file alias="SelectFolderMenu.qml" >./qml/ImportExportUI/SelectFolderMenu.qml</file>
|
||||
<file alias="SelectLabelsMenu.qml" >./qml/ImportExportUI/SelectLabelsMenu.qml</file>
|
||||
<file alias="SettingsView.qml" >./qml/ImportExportUI/SettingsView.qml</file>
|
||||
<file alias="VersionInfo.qml" >./qml/ImportExportUI/VersionInfo.qml</file>
|
||||
<file alias="images/folder_open.png" >./share/icons/folder_open.png</file>
|
||||
<file alias="images/envelope_open.png" >./share/icons/envelope_open.png</file>
|
||||
</qresource>
|
||||
<qresource>
|
||||
<file alias="ui.qml" >./qml/Gui.qml</file>
|
||||
<file alias="uiie.qml" >./qml/GuiIE.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 8.7 KiB |
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 557 KiB |
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1024 1024" style="enable-background:new 0 0 1024 1024;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#9397CD;}
|
||||
.st1{fill:#262A33;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<circle class="st0" cx="512.2" cy="512.1" r="512"/>
|
||||
</g>
|
||||
<g>
|
||||
<circle class="st1" cx="850" cy="850" r="174"/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="797" y="774" class="st2" width="34" height="128"/>
|
||||
<polygon class="st2" points="814,717 751.6,775 876.4,775 "/>
|
||||
</g>
|
||||
<g>
|
||||
<rect x="865" y="798" class="st2" width="34" height="128"/>
|
||||
<polygon class="st2" points="882,983 944.4,925 819.6,925 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M511,263c0,0-136.3-4.5-164.4,146.7v103c0,0,1.2,11,32.2,33.4c31,22.4,111.2,85.4,132.3,85.4
|
||||
c21,0,101.3-63,132.3-85.4c31-22.4,32.2-33.4,32.2-33.4v-103C647.3,258.5,511,263,511,263z M604.3,465.9H511h-93.3v-56.1
|
||||
c18.9-75.1,93.3-76.1,93.3-76.1s74.4,1,93.3,76.1V465.9z"/>
|
||||
<path class="st2" d="M511,654.7c0,0-21.1-2.1-37.7-13.5C456.8,629.7,346.6,551,346.6,551v155.9c0,0,0.9,18.1,20.9,18.1
|
||||
s143.5,0,143.5,0s123.5,0,143.5,0s20.9-18.1,20.9-18.1V551c0,0-110.2,78.8-126.8,90.2C532.1,652.7,511,654.7,511,654.7z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -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 {
|
||||
|
|
|
@ -15,8 +15,8 @@
|
|||
// You should have received a copy of the GNU General Public License
|
||||
// along with ProtonMail Bridge. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
// 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;"
|
||||
|
|
|
@ -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) {}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
|
||||
#!/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
|
||||
|
Loading…
Reference in New Issue