Showing preview only (1,533K chars total). Download the full file or copy to clipboard to get everything.
Repository: flowkeeper-org/fk-desktop
Branch: main
Commit: 600fd33b9377
Files: 219
Total size: 1.4 MB
Directory structure:
gitextract_0tdm8aod/
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── build.yml
│ ├── main.yml
│ ├── manual-all.yml
│ ├── manual-one.yml
│ └── release.yml
├── .gitignore
├── .idea/
│ ├── .gitignore
│ ├── dictionaries/
│ │ └── project.xml
│ ├── flowkeeper-python.iml
│ ├── inspectionProfiles/
│ │ └── profiles_settings.xml
│ ├── misc.xml
│ ├── modules.xml
│ ├── vcs.xml
│ └── watcherTasks.xml
├── LICENSE
├── README.md
├── TODO.md
├── doc/
│ ├── actions.md
│ ├── build-alpine.md
│ ├── data-model.md
│ ├── design.md
│ ├── events.md
│ ├── pipeline.md
│ ├── release.md
│ └── strategies.md
├── requirements-test.txt
├── requirements.txt
├── res/
│ ├── CHANGELOG.txt
│ ├── CREDITS.txt
│ ├── LICENSE.txt
│ ├── about.ui
│ ├── core.ui
│ ├── icons/
│ │ ├── dark/
│ │ │ └── index.theme
│ │ ├── light/
│ │ │ └── index.theme
│ │ └── mixed/
│ │ └── index.theme
│ ├── sound/
│ │ └── Madelene.m4a
│ ├── stats.ui
│ ├── style-beach.json
│ ├── style-dark.json
│ ├── style-desert.json
│ ├── style-highlight.json
│ ├── style-light.json
│ ├── style-lime.json
│ ├── style-mixed.json
│ ├── style-motel.json
│ ├── style-purple.json
│ ├── style-resort.json
│ ├── style-template.qss
│ ├── style-terra.json
│ └── summary.ui
├── run-tests.sh
├── run.sh
├── scripts/
│ ├── README.md
│ ├── bsd/
│ │ └── README.md
│ ├── common/
│ │ ├── generate-resources.sh
│ │ ├── get-version.sh
│ │ └── pyinstaller/
│ │ ├── entitlements.plist
│ │ ├── normal.spec
│ │ └── portable.spec
│ ├── linux/
│ │ ├── appimage/
│ │ │ ├── install-appimage.sh
│ │ │ └── package-appimage.sh
│ │ ├── common/
│ │ │ ├── flowkeeper
│ │ │ ├── org.flowkeeper.Flowkeeper.desktop
│ │ │ └── org.flowkeeper.Flowkeeper.metainfo.xml
│ │ ├── debian/
│ │ │ ├── debian-control
│ │ │ ├── debian-control-min
│ │ │ ├── package-deb-min.sh
│ │ │ └── package-deb.sh
│ │ ├── flatpak/
│ │ │ └── README.md
│ │ ├── obs/
│ │ │ ├── README.md
│ │ │ └── obs.spec
│ │ ├── package-nuitka.sh
│ │ └── rpm/
│ │ └── package-rpm-min.sh
│ ├── macos/
│ │ ├── create-dmg.sh
│ │ ├── create-icons.sh
│ │ ├── install-certificates.sh
│ │ ├── install-create-dmg.sh
│ │ ├── notarize-dmg.sh
│ │ ├── package-macos-pkg.sh
│ │ └── package-nuitka.sh
│ └── windows/
│ ├── generate-resources-windows.sh
│ ├── install-innosetup.sh
│ ├── package-installer.sh
│ └── windows-installer.iss
├── src/
│ └── fk/
│ ├── __init__.py
│ ├── core/
│ │ ├── __init__.py
│ │ ├── abstract_cryptograph.py
│ │ ├── abstract_data_container.py
│ │ ├── abstract_data_item.py
│ │ ├── abstract_event_emitter.py
│ │ ├── abstract_event_source.py
│ │ ├── abstract_filesystem_watcher.py
│ │ ├── abstract_serializer.py
│ │ ├── abstract_settings.py
│ │ ├── abstract_strategy.py
│ │ ├── abstract_timer.py
│ │ ├── abstract_timer_display.py
│ │ ├── backlog.py
│ │ ├── backlog_strategies.py
│ │ ├── ephemeral_event_source.py
│ │ ├── event_source_factory.py
│ │ ├── event_source_holder.py
│ │ ├── events.py
│ │ ├── fernet_cryptograph.py
│ │ ├── file_event_source.py
│ │ ├── import_export.py
│ │ ├── integration_executor.py
│ │ ├── interruption.py
│ │ ├── mock_settings.py
│ │ ├── no_cryptograph.py
│ │ ├── pomodoro.py
│ │ ├── pomodoro_strategies.py
│ │ ├── sandbox.py
│ │ ├── simple_serializer.py
│ │ ├── strategy_factory.py
│ │ ├── tag.py
│ │ ├── tags.py
│ │ ├── tenant.py
│ │ ├── timer.py
│ │ ├── timer_data.py
│ │ ├── timer_strategies.py
│ │ ├── user.py
│ │ ├── user_strategies.py
│ │ ├── workitem.py
│ │ └── workitem_strategies.py
│ ├── desktop/
│ │ ├── __init__.py
│ │ ├── application.py
│ │ ├── config_wizard.py
│ │ ├── desktop.py
│ │ ├── desktop_strategies.py
│ │ ├── export_wizard.py
│ │ ├── import_wizard.py
│ │ ├── interruption_dialog.py
│ │ ├── settings.py
│ │ ├── stats_window.py
│ │ ├── tutorial.py
│ │ └── work_summary_window.py
│ ├── e2e/
│ │ ├── __init__.py
│ │ ├── abstract_e2e_test.py
│ │ ├── all-tests.json
│ │ ├── backlog_e2e.py
│ │ ├── screenshot.py
│ │ └── screenshots_e2e.py
│ ├── qt/
│ │ ├── __init__.py
│ │ ├── about_window.py
│ │ ├── abstract_drop_model.py
│ │ ├── abstract_item_delegate.py
│ │ ├── abstract_tableview.py
│ │ ├── actions.py
│ │ ├── app_version.py
│ │ ├── audio_player.py
│ │ ├── backlog_model.py
│ │ ├── backlog_tableview.py
│ │ ├── backlog_widget.py
│ │ ├── configurable_toolbar.py
│ │ ├── connection_widget.py
│ │ ├── flow_layout.py
│ │ ├── focus_widget.py
│ │ ├── heartbeat.py
│ │ ├── info_overlay.py
│ │ ├── oauth.py
│ │ ├── pomodoro_delegate.py
│ │ ├── progress_widget.py
│ │ ├── qt_filesystem_watcher.py
│ │ ├── qt_invoker.py
│ │ ├── qt_settings.py
│ │ ├── qt_timer.py
│ │ ├── render/
│ │ │ ├── __init__.py
│ │ │ ├── abstract_timer_renderer.py
│ │ │ ├── classic_timer_renderer.py
│ │ │ └── minimal_timer_renderer.py
│ │ ├── resize_event_filter.py
│ │ ├── search_completer.py
│ │ ├── tags_widget.py
│ │ ├── theme_change_event_filter.py
│ │ ├── threaded_event_source.py
│ │ ├── timer_widget.py
│ │ ├── tray_icon.py
│ │ ├── user_model.py
│ │ ├── user_tableview.py
│ │ ├── websocket_event_source.py
│ │ ├── workitem_model.py
│ │ ├── workitem_state_delegate.py
│ │ ├── workitem_tableview.py
│ │ ├── workitem_text_delegate.py
│ │ └── workitem_widget.py
│ ├── tests/
│ │ ├── __init__.py
│ │ ├── abstract_test_case.py
│ │ ├── data_generator.py
│ │ ├── fixtures/
│ │ │ ├── random-dump.txt
│ │ │ ├── random.txt
│ │ │ └── test-tags.txt
│ │ ├── test_backlogs.py
│ │ ├── test_events.py
│ │ ├── test_file_event_source.py
│ │ ├── test_import_export.py
│ │ ├── test_pomodoros.py
│ │ ├── test_settings.py
│ │ ├── test_tags.py
│ │ ├── test_users.py
│ │ ├── test_utils.py
│ │ └── test_workitems.py
│ └── tools/
│ ├── __init__.py
│ ├── cli.py
│ ├── minimal_actions.py
│ ├── minimal_audio.py
│ ├── minimal_auth.py
│ ├── minimal_backlogs.py
│ ├── minimal_common.py
│ ├── minimal_focus.py
│ ├── minimal_settings.py
│ ├── minimal_timer_widget.py
│ ├── minimal_tray.py
│ ├── minimal_tutorial.py
│ ├── minimal_update.py
│ ├── minimal_users.py
│ └── minimal_workitems.py
└── ws-tests.md
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: flowkeeper-org
================================================
FILE: .github/workflows/build.yml
================================================
name: Build subflow
on:
workflow_call:
inputs:
os:
required: true
type: string
compiler:
required: true
type: string
secrets:
MAC_SIGN_CERT:
required: true
MAC_SIGN_PASSWORD:
required: true
MAC_KEYCHAIN_PASSWORD:
required: true
jobs:
build:
runs-on: ${{ inputs.os }}
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
env:
BUILD_CERTIFICATE_BASE64: ${{ secrets.MAC_SIGN_CERT }}
P12_PASSWORD: ${{ secrets.MAC_SIGN_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
shell: bash
run: |
export FK_VERSION=$(scripts/common/get-version.sh)
echo "FK_VERSION=$FK_VERSION" >> $GITHUB_ENV
if [[ "$OSTYPE" == "darwin"* ]]; then
scripts/macos/install-create-dmg.sh
scripts/macos/install-certificates.sh
export BINARY_EXTENSION=""
echo "BINARY_EXTENSION=" >> $GITHUB_ENV
elif [[ "$OSTYPE" == "msys" ]]; then
# Disable Defender for the workspace directory
echo "Will disable Defender"
echo "powershell -inputformat none -outputformat none -NonInteractive -Command Add-MpPreference -ExclusionPath $(cmd //c cd)"
powershell -inputformat none -outputformat none -NonInteractive -Command Add-MpPreference -ExclusionPath "$(cmd //c cd)"
scripts/windows/install-innosetup.sh
export BINARY_EXTENSION=".exe"
echo "BINARY_EXTENSION=.exe" >> $GITHUB_ENV
else
scripts/linux/appimage/install-appimage.sh
export BINARY_EXTENSION=""
echo "BINARY_EXTENSION=" >> $GITHUB_ENV
fi
pip install -r requirements.txt
if [[ "${{ inputs.compiler}}" == "nuitka" ]]; then
pip install nuitka
else
pip install pyinstaller
fi
- name: Prepare sources
shell: bash
run: |
scripts/common/generate-resources.sh
rm -rf build dist
mkdir -p build dist
- name: Package builds (PyInstaller)
if: ${{ inputs.compiler == 'pyinstaller' }}
shell: bash
run: |
if [[ "$OSTYPE" == "darwin"* ]]; then
pyinstaller scripts/common/pyinstaller/normal.spec --distpath=build -- --sign
echo "Built PyInstaller for macOS"
ls -al build/
else
pyinstaller scripts/common/pyinstaller/portable.spec --distpath=build
pyinstaller scripts/common/pyinstaller/normal.spec --distpath=build
echo "Built PyInstaller for Linux or Windows"
ls -al build/
fi
- name: Package builds (Nuitka)
if: ${{ inputs.compiler == 'nuitka' }}
uses: Nuitka/Nuitka-Action@main
with:
nuitka-version: "2.6.8"
script-name: src/fk/desktop/desktop.py
mode: ${{ startsWith(inputs.os, 'macOS') && 'app' || 'onefile' }}
enable-plugins: pyside6
include-qt-plugins: multimedia
clang: ${{ startsWith(inputs.os, 'windows') && 'on' || '' }}
windows-console-mode: disable
windows-icon-from-ico: res/flowkeeper.ico
macos-app-icon: flowkeeper.icns
macos-signed-app-name: org.flowkeeper.Flowkeeper
macos-app-name: Flowkeeper
macos-sign-identity: "Developer ID Application: Constantine Kulak (ELWZ9S676C)"
macos-sign-notarization: true
macos-app-protected-resource: "com.apple.security.cs.allow-unsigned-executable-memory:true"
macos-app-version: "${{ env.FK_VERSION }}"
product-name: Flowkeeper
product-version: "${{ env.FK_VERSION }}"
file-description: ${{ startsWith(inputs.os, 'windows') && 'Flowkeeper' || 'Flowkeeper is a Pomodoro Technique desktop timer for power users' }}
copyright: Copyright (c) 2023 Constantine Kulak <contact@flowkeeper.org>
output-dir: 'build'
output-file: 'Flowkeeper'
env:
PYTHONPATH: src
KEYCHAIN_PASSWORD: ${{ secrets.MAC_KEYCHAIN_PASSWORD }}
- name: Create installers
env:
NOTARIZATION_PASSWORD: ${{ secrets.MAC_NOTARIZATION_PASSWORD }}
NOTARIZATION_ID: ${{ secrets.MAC_NOTARIZATION_ID }}
NOTARIZATION_TEAM: ${{ secrets.MAC_NOTARIZATION_TEAM }}
shell: bash
run: |
echo "Prepare dist directory"
mkdir -p dist/standalone
PREFIX="dist/flowkeeper-${FK_VERSION}-${{ inputs.os }}-${{ inputs.compiler }}"
echo "Will create artifacts with $PREFIX prefix"
if [[ "${{ inputs.compiler}}" == "nuitka" ]]; then
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS doesn't support portable binaries"
mv build/desktop.app dist/standalone/Flowkeeper.app
else
mv build/Flowkeeper* $PREFIX-portable${BINARY_EXTENSION}
mv build/desktop.dist/* dist/standalone
fi
if [[ "$OSTYPE" == "linux"* ]]; then
mv dist/standalone/Flowkeeper.bin dist/standalone/Flowkeeper
fi
else
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "macOS doesn't support portable binaries"
mv build/Flowkeeper.app dist/standalone
else
mv build/Flowkeeper${BINARY_EXTENSION} $PREFIX-portable${BINARY_EXTENSION}
mv build/flowkeeper/* dist/standalone
fi
fi
echo "Moved ${{ inputs.compiler }} binaries to dist/standalone"
find dist/
if [[ "$OSTYPE" == "darwin"* ]]; then
echo "Building macOS DMG installer"
scripts/macos/create-dmg.sh
scripts/macos/notarize-dmg.sh
mv dist/Flowkeeper.dmg "$PREFIX-installer.dmg"
elif [[ "$OSTYPE" == "msys" ]]; then
echo "Building Windows setup.exe installer"
scripts/windows/package-installer.sh
mv dist/setup.exe "$PREFIX-installer.exe"
else
echo "Building Debian "fat" DEB package"
scripts/linux/debian/package-deb.sh
mv dist/flowkeeper.deb "$PREFIX-package.deb"
echo "Building Debian "lean" DEB package"
scripts/linux/debian/package-deb-min.sh
mv dist/flowkeeper-min.deb "$PREFIX-min-package.deb"
# Temporarily disable AppImage build
echo "Building AppImage package"
scripts/linux/appimage/package-appimage.sh
mv dist/Flowkeeper-*.AppImage "$PREFIX.AppImage"
fi
echo "Zipping the standalone directory"
cd dist/standalone
if [[ "$OSTYPE" == "msys" ]]; then
echo "No zip on Windows"
powershell Compress-Archive "./*" "../../$PREFIX-standalone.zip"
else
zip -9 -r "../../$PREFIX-standalone.zip" ./*
fi
cd ../..
echo "Cleaning /dist up"
rm -rf dist/standalone
- name: Archive the binaries
uses: actions/upload-artifact@v4
with:
name: dist-${{ inputs.os }}-${{ inputs.compiler }}-all
path: |
dist
================================================
FILE: .github/workflows/main.yml
================================================
name: Tests and checks
on:
pull_request:
branches:
- main
push:
branches:
- main
permissions:
checks: write
jobs:
run-tests:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Create Virtual Environment
run: |
python3 -m venv venv
source venv/bin/activate
- name: Install dependencies
run: |
pip install -r requirements-test.txt
- name: Generate resources
run: |
scripts/common/generate-resources.sh
- name: Run unit tests for fk.core
run: |
PYTHONPATH=src coverage run -m xmlrunner -o test-results discover -v fk.tests
- name: Publish test report
uses: mikepenz/action-junit-report@v4
with:
report_paths: 'test-results/TEST-*.xml'
include_passed: true
- name: Upload code coverage to coveralls.io
run: coveralls --service=github
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Trigger e2e tests (external)
run: |
curl --version
remote-pipeline:
runs-on: ubuntu-22.04
needs: run-tests
steps:
- name: Trigger remote job
env:
REMOTE_URL: ${{ secrets.REMOTE_URL }}
REMOTE_USER_TOKEN: ${{ secrets.REMOTE_USER_TOKEN }}
REMOTE_JOB_TOKEN: ${{ secrets.REMOTE_MAIN_JOB_TOKEN }}
run: |
curl -I -u "github-trigger:$REMOTE_USER_TOKEN" "$REMOTE_URL/job/gh-main/build?token=$REMOTE_JOB_TOKEN" 2>/dev/null > /dev/null
================================================
FILE: .github/workflows/manual-all.yml
================================================
name: Manual build - all
on:
workflow_dispatch:
jobs:
call-build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm, ubuntu-22.04-arm, macos-15, macos-14, macos-13, windows-2025, windows-2022]
compiler: [nuitka, pyinstaller]
exclude:
- os: macos-13
compiler: nuitka
uses: flowkeeper-org/fk-desktop/.github/workflows/build.yml@main
with:
os: ${{ matrix.os }}
compiler: ${{ matrix.compiler }}
secrets: inherit
================================================
FILE: .github/workflows/manual-one.yml
================================================
name: Manual build - one
on:
workflow_dispatch:
inputs:
os:
description: 'Operating system'
required: true
default: 'ubuntu-24.04'
type: choice
options:
- ubuntu-24.04
- ubuntu-22.04
- ubuntu-24.04-arm
- ubuntu-22.04-arm
- macos-15
- macos-14
- macos-13
- windows-2025
- windows-2022
compiler:
description: 'Compiler'
required: true
default: 'nuitka'
type: choice
options:
- nuitka
- pyinstaller
jobs:
call-build:
uses: flowkeeper-org/fk-desktop/.github/workflows/build.yml@main
with:
os: ${{ inputs.os }}
compiler: ${{ inputs.compiler }}
secrets: inherit
================================================
FILE: .github/workflows/release.yml
================================================
name: Release new version
on:
push:
tags:
- "v*.*.*"
permissions:
contents: write
checks: write
jobs:
call-build:
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm, ubuntu-22.04-arm, macos-15, macos-14, macos-13, windows-2025, windows-2022]
compiler: [nuitka, pyinstaller]
exclude:
- os: macos-13
compiler: nuitka
uses: flowkeeper-org/fk-desktop/.github/workflows/build.yml@main
with:
os: ${{ matrix.os }}
compiler: ${{ matrix.compiler }}
secrets: inherit
release:
needs: call-build
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm, ubuntu-22.04-arm, macos-15, macos-14, macos-13, windows-2025, windows-2022]
compiler: [nuitka, pyinstaller]
exclude:
- os: macos-13
compiler: nuitka
runs-on: ubuntu-latest
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist-${{ matrix.os }}-${{ matrix.compiler }}-all
path: .
- name: Release
uses: softprops/action-gh-release@v1
with:
files: "./*"
================================================
FILE: .gitignore
================================================
.venv
venv
venv*
__pycache__
build
dist
.coverage
.DS_Store
htmlcov
src/fk/desktop/resources.py
test-results
Flowkeeper.dmg
notary-key.txt
flowkeeper.icns
*.log
================================================
FILE: .idea/.gitignore
================================================
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
================================================
FILE: .idea/dictionaries/project.xml
================================================
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>flowkeeper</w>
<w>workitem</w>
</words>
</dictionary>
</component>
================================================
FILE: .idea/flowkeeper-python.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/build" />
<excludeFolder url="file://$MODULE_DIR$/htmlcov" />
<excludeFolder url="file://$MODULE_DIR$/test-results" />
<excludeFolder url="file://$MODULE_DIR$/venv38" />
<excludeFolder url="file://$MODULE_DIR$/src/fk/tests/fixtures" />
<excludeFolder url="file://$MODULE_DIR$/venv" />
</content>
<orderEntry type="jdk" jdkName="Python 3.13 (fk-desktop)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
================================================
FILE: .idea/inspectionProfiles/profiles_settings.xml
================================================
<component name="InspectionProjectProfileManager">
<settings>
<option name="PROJECT_PROFILE" value="Default" />
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>
================================================
FILE: .idea/misc.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="Python 3.13 virtualenv at ~/projects/fk-desktop/venv" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (fk-desktop)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>
================================================
FILE: .idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/flowkeeper-python.iml" filepath="$PROJECT_DIR$/.idea/flowkeeper-python.iml" />
</modules>
</component>
</project>
================================================
FILE: .idea/vcs.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
================================================
FILE: .idea/watcherTasks.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectTasksOptions">
<TaskOptions isEnabled="true">
<option name="arguments" value="" />
<option name="checkSyntaxErrors" value="false" />
<option name="description" />
<option name="exitCodeBehavior" value="ERROR" />
<option name="fileExtension" value="*" />
<option name="immediateSync" value="true" />
<option name="name" value="Qt resources" />
<option name="output" value="" />
<option name="outputFilters">
<array />
</option>
<option name="outputFromStdout" value="false" />
<option name="program" value="$ProjectFileDir$/scripts/common/generate-resources.sh" />
<option name="runOnExternalChanges" value="true" />
<option name="scopeName" value="/res" />
<option name="trackOnlyRoot" value="false" />
<option name="workingDir" value="$ProjectFileDir$" />
<envs />
</TaskOptions>
</component>
</project>
================================================
FILE: LICENSE
================================================
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
================================================
FILE: README.md
================================================
# Flowkeeper

[](https://coveralls.io/github/flowkeeper-org/fk-desktop?branch=main)
[](https://sonarcloud.io/summary/new_code?id=flowkeeper-org_fk-desktop)
[](https://build.opensuse.org/package/show/home:flowkeeper/flowkeeper)
Flowkeeper is an independent Pomodoro Technique desktop timer for power users. It is a
simple tool, which focuses on doing one thing well. It is Free Software with open source.
Visit [flowkeeper.org](https://flowkeeper.org) for screenshots, downloads and FAQ.
If you used it, I will appreciate it if you take a minute to
[provide some feedback](https://www.producthunt.com/products/flowkeeper/reviews/new).
Your constructive criticism is welcome!

## Building
Flowkeeper has a single major dependency -- Qt 6.7.0, which in turn requires Python 3.9 or later. To create
installers and binary packages we build Flowkeeper on Ubuntu 22.04 using Python 3.11 and 6.7.0. We also
test Flowkeeper with the latest Qt 6.8.x on OpenSUSE Tumbleweed.
### Building for Linux and macOS
On some lean distributions like a minimal installation of Debian 12, you
might need to install `libxcb-cursor0` first, e.g.
```shell
sudo apt install libxcb-cursor0
```
Create a virtual environment and install dependencies:
```shell
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
Note that `requirements.txt` contains ALL libraries and tools needed to run, test and
create installers. You can use `requirements-run.txt` if you only want to debug
Flowkeeper locally, or `requirements-build.txt` if you also want to create distributable /
portable bundles.
Then you need to "generate resources", which means converting data files in `/res` directory into
the corresponding Python classes. Whenever you make changes to files in `/res` directory, you need
to rerun this command, too:
```shell
build/common/generate-resources.sh
```
From here you can start coding. If you want to build an installer, refer to the CI/CD pipeline in
`.github/workflows/build.yml`. For example, if you want to build a DEB file, you'd need to execute
`pyinstaller installer/normal-build.spec` and then `./package-deb.sh`.
If you see this error on openSUSE with Qt 6.7.x:
```
No QtMultimedia backends found. Only QMediaDevices, QAudioDevice, QSoundEffect, QAudioSink, and QAudioSource are available.
```
then install `libatomic1`:
```shell
sudo zypper install libatomic1
```
### Building for Windows
Consult the above section for details. In short, install Python 3.11. Then:
```shell
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
Generate resources:
```shell
cd res
pyside6-rcc --project -o resources.qrc
pyside6-rcc -g python resources.qrc -o "../src/fk/desktop/resources.py"
```
Package as a distributable / portable bundle (OPTIONAL):
```shell
pyinstaller installer\portable-build.spec
pyinstaller installer\normal-build.spec
```
## Testing Flowkeeper
To execute Flowkeeper:
```shell
PYTHONPATH=src python -m fk.desktop.desktop
```
To run unit tests w/test coverage (install requirements from
`requirements.txt` or `requirements-test.txt` first):
```shell
PYTHONPATH=src python -m coverage run -m unittest discover -v fk.tests
python -m coverage html
```
To execute end-to-end tests:
```shell
PYTHONPATH=src python -m fk.desktop.desktop --e2e
```
## Technical details
- [Design considerations](doc/design.md)
- [Data model](doc/data-model.md)
- [Strategies](doc/strategies.md)
- [Events](doc/events.md)
- [UI actions](doc/actions.md)
- [CI/CD pipeline](doc/pipeline.md)
- [Building for Alpine Linux](doc/build-alpine.md)
- [Building for FreeBSD](doc/build-freebsd.md)
## Copyright
Copyright (c) 2023 - 2024 Constantine Kulak.
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
================================================
FILE: TODO.md
================================================
# To do before release
4. Fonts -- backlogs use default font
5. Fonts -- status uses default font
6. Fonts -- focus mode uses default font. Default focus becomes the same after double-clicking.
8. Backlogs toggle icon doesn't change its color on theme change
9. Rows height doesn't recalculate on fonts change
# Tests
## Windows binaries
1. No sound on Windows with Nuitka
2. "standalone" directory in ZIPs
3. Sign binaries in standalone ZIPs and repack
## Linux binaries
1. KUbuntu 24.04 doesn't support deb-min installer (Qt 6.4.2 max, same for Debian)
2. Ubuntu 22.04 ships with Qt 6.2.4 (Universe repo) -- check all for Pyside6
3. The "fat" versions has GTK / default theme
4. No sound for Nuitka, same as Windows
5. Keyboard doesn't work with PyInstaller binaries on openSUSE -- both 22 and 24
### AppImage
### Flatpak
### openSUSE installer
## macOS binaries
1. On Ventura 13 / x86 and ARM, both Nuitka and PyInstaller -- no signature
2. "Too many values to unpack" when launching Settings, even after settings reset
3. No sound for Nuitka, same as Windows
================================================
FILE: doc/actions.md
================================================
# UI: Actions
## Application
- ('application.settings', "Settings", 'F10', None, Application.show_settings_dialog)
- ('application.quit', "Quit", 'Ctrl+Q', None, Application.quit_local)
- ('application.import', "Import data...", 'Ctrl+I', None, Application.show_import_wizard)
- ('application.export', "Export data...", 'Ctrl+E', None, Application.show_export_wizard)
- ('application.about', "About", '', None, Application.show_about)
## BacklogTableView
- ('backlogs_table.newBacklog', "New Backlog", 'Ctrl+N', None, BacklogTableView.create_backlog)
- ('backlogs_table.renameBacklog', "Rename Backlog", 'Ctrl+R', None, BacklogTableView.rename_selected_backlog)
- ('backlogs_table.deleteBacklog', "Delete Backlog", 'F8', None, BacklogTableView.delete_selected_backlog)
- ('backlogs_table.newBacklogFromIncomplete', "New Backlog From Incomplete", 'Ctrl+M', "tool-add-prefilled", BacklogTableView.create_backlog_from_incomplete)
## WorkitemTableView
- ('workitems_table.newItem', "New Item", 'Ins', None, WorkitemTableView.create_workitem)
- ('workitems_table.renameItem', "Rename Item", 'F6', None, WorkitemTableView.rename_selected_workitem)
- ('workitems_table.deleteItem', "Delete Item", 'Del', None, WorkitemTableView.delete_selected_workitem)
- ('workitems_table.startItem', "Start Item", 'Ctrl+S', 'tool-next', WorkitemTableView.start_selected_workitem)
- ('workitems_table.completeItem', "Complete Item", 'Ctrl+P', 'tool-complete', WorkitemTableView.complete_selected_workitem)
- ('workitems_table.addPomodoro', "Add Pomodoro", 'Ctrl++', None, WorkitemTableView.add_pomodoro)
- ('workitems_table.removePomodoro', "Remove Pomodoro", 'Ctrl+-', None, WorkitemTableView.remove_pomodoro)
- ('workitems_table.hideCompleted', "Hide Completed Items", '', None, WorkitemTableView._toggle_hide_completed_workitems, True, True)
## FocusWidget
- ('focus.voidPomodoro', "Void Pomodoro", 'Ctrl+V', "tool-void", FocusWidget._void_pomodoro)
- ('focus.nextPomodoro', "Next Pomodoro", None, "tool-next", FocusWidget._next_pomodoro)
- ('focus.completeItem', "Complete Item", None, "tool-complete", FocusWidget._complete_item)
## MainWindow
- ('window.focusMode', "Focus Mode", None, "tool-show-timer-only", MainWindow.toggle_focus_mode)
- ('window.showMainWindow', "Show Main Window", None, "tool-show-timer-only", MainWindow.show_window)
- ('window.showBacklogs', "Backlogs", 'Ctrl+B', 'tool-backlogs', MainWindow.show_about)
- ('window.showUsers', "Team", 'Ctrl+T', 'tool-teams', MainWindow.toggle_users)
- ('window.showSearch', "Search...", 'Ctrl+F', '', MainWindow.show_search)
================================================
FILE: doc/build-alpine.md
================================================
# Building for Alpine Linux
Flowkeeper's CI pipeline runs PyInstaller on Ubuntu and thus generates binaries which rely on glibc.
Alpine is based on musl, so you'd get "symbol not found" errors in runtime if you try to run any of the
"official" binaries.
You can still use Flowkeeper with Alpine. We tested it with the edge release + Xfce. Instructions:
1. Install `py3-pyside6` package via `apk`. This is the only tricky bit. We couldn't install PySide6
via pip from inside the venv, as we'd normally do.
2. Clone this repo and create a Python Virtual Environment, *which uses system packages*:
`python3 -m venv venv --system-site-packages`
3. The rest of the steps are the same as for any other Linux OS
================================================
FILE: doc/data-model.md
================================================
# Data model
Flowkeeper data model is strictly hierarchical:
- Tenant: AbstractDataContainer
- User: AbstractDataContainer
- Backlog: AbstractDataContainer
- Workitem: AbstractDataContainer
- Pomodoro: AbstractDataItem
`AbstractDataContainer` acts as a `dict<uid, T>`, and `AbstractDataItem` represents a domain object with
`uid`, `parent`, `create_date` and `last_modified_date`.
Due to its tree nature, sharing backlogs and workitems should be implemented via symlinks.
================================================
FILE: doc/design.md
================================================
# Flowkeeper design considerations
## Client / server architecture
1. Flowkeeper clients are "fat", and the backends are "thin". All messages are sent by
the clients, while the servers are passive. This is done to simplify servers, allow generic
messaging protocols like XMPP or AMQP, and enable "dumb" backends like plain files.
2. A client may safely disconnect or shut down at any moment. Most importantly, there can
be _zero_ clients running in the middle of a Pomodoro. A client which "reconnects" will
see the Pomodoro in the correct state, as if it was running on the server. A Pomodoro,
which ended while the clients were offline, is considered completed successfully.
3. The server should work correctly with all "business" content e2e-encrypted. Any
unencrypted messages are all related to the client's communication, i.e. Authenticate,
Ping/Pong, Error, Replay, and DeleteAccount.
4. As a consequence of (2) and (3), all messages in the system are recorded end-user events.
Neither the client, nor the server generate any "business" events of their own. This makes
client synchronization much easier, since the events like FinishPomodoro are computed and
fired internally, and never go on the wire.
## Event sourcing data model
When a client connects to a backend, it replays all events since the last known state.
5. Two clients must synchronize their changes in real time, meaning that they can't make
conflicting changes offline. This is achieved via message sequencing. This design
consideration is temporary, and will be removed in the future, as we allow "offline mode"
for connected sources. In the future, data from multiple clients can be merged via one of
two Import mechanisms.
6. Some messages might be missing from the end of the list (e.g. the client is offline and
they haven't arrived yet), but we can't have gaps in the middle of the history. It means
that the history must always be consistent. If we detect an inconsistency in the hisory
(e.g. 5 minutes of rest start on a pomodoro in "new" state, i.e. we missed the "work" state
completely), such inconsistencies result in the parsing failure, crashing the client.
We don't try to "fix" the history by adding records retroactively.
7. The history is immutable, but we can create a new one, which is a compressed version
of the original, as long as it results in the exact same final state of the data model.
8. If a user tries to delete or complete a workitem in the middle of its own pomodoro, the
core will void this pomodoro, emitting correct events.
9. The history preserves all data, so we don't have to be too careful about deleting things.
If a backlog, workitem or user is deleted -- the object simply gets deleted. We don't use
"is_deleted" flags, and we don't move things to "orphaned" storage. If we need to restore
a deleted object -- we'll find a way how to do it by processing the history.
10. Strategies are only executed as a result of users' actions or timer events. Client
startup or shutdown won't add any strategies to the history.
11. The Timer never fires "in the past".
12. All Pomodoros run and end implicitly. They can only be started and voided explicitly.
================================================
FILE: doc/events.md
================================================
# Events
Whenever anything changes in the underlying data model, Flowkeeper emits events. To emit an event, the class needs
to subclass `AbstractEventSource`. All UI updates should be based on those events.
- AbstractEventSource
- `BeforeUserCreate(user_identity: str, user_name: str)`, `AfterUserCreate(user: User)`
- `BeforeUserDelete(user: User)`, `AfterUserDelete(--//--)`
- `BeforeUserRename(user: User, old_name: str, new_name: str)`, `AfterUserRename(--//--)`
- `BeforeBacklogCreate(backlog_name: str, backlog_owner: User, backlog_uid: str)`, `AfterBacklogCreate(backlog: Backlog)`
- `BeforeBacklogDelete(backlog: Backlog)`, `AfterBacklogDelete(--//--)`
- `BeforeBacklogRename(backlog: Backlog, old_name: str, new_name: str)`, `AfterBacklogRename(--//--)`
- `BeforeWorkitemCreate(backlog_uid: str, workitem_uid: str, workitem_name: str)`, `AfterWorkitemCreate(workitem: Workitem)`
- `BeforeWorkitemComplete(workitem: Workitem, target_state: str)`, `AfterWorkitemComplete(--//--)`
- `BeforeWorkitemStart(pomodoro: Pomodoro, workitem: Workitem, work_duration: int)`, `AfterWorkitemStart(--//--)`
- `BeforeWorkitemDelete(workitem: Workitem)`, `AfterWorkitemDelete(--//--)`
- `BeforeWorkitemRename(workitem: Workitem, old_name: str, new_name: str)`, `AfterWorkitemRename(--//--)`
- `BeforePomodoroAdd(workitem: Workitem, num_pomodoros: int)`, `AfterPomodoroAdd(--//--)`
- `BeforePomodoroRemove(workitem: Workitem, num_pomodoros: int, pomodoros: List<Pomodoro>)`, `AfterPomodoroRemove(--//--)`
- `BeforePomodoroWorkStart(pomodoro: Pomodoro, workitem: Workitem, work_duration: int)`, `AfterPomodoroWorkStart(--//--)`
- `BeforePomodoroRestStart(pomodoro: Pomodoro, workitem: Workitem, rest_duration: int)`, `AfterPomodoroRestStart(--//--)`
- `BeforePomodoroComplete(pomodoro: Pomodoro, workitem: Workitem, target_state: str)`, `AfterPomodoroComplete`
- `SourceMessagesRequested()`, `SourceMessagesProcessed()`
- `BeforeMessageProcessed(strategy: AbstractStrategy, auto: Bool)`, `AfterMessageProcessed(--//--)`
- `PongReceived(uid: str)`
- AbstractSettings
- `BeforeSettingsChanged(old_values: dict[str, str], new_values: dict[str, str])`, `AfterSettingsChanged(--//--)`
- AbstractTableView
- `BeforeSelectionChanged(before: AbstractDataItem, after: AbstractDataItem)`, `AfterSelectionChanged(--//--)`
- Application
- `AfterFontsChanged(main_font: QFont, header_font: QFont, application: Application)`
- `AfterSourceChanged(source: AbstractEventSource)`
- Heartbeat
- `WentOnline(ping: int)`, `WentOffline(after: int, last_received: datetime)`
- PomodoroTimer
- `TimerTick(timer: PomodoroTimer)`
- `TimerWorkStart(timer: PomodoroTimer)`
- `TimerWorkComplete(timer: PomodoroTimer)`
- `TimerRestComplete(timer: PomodoroTimer, pomodoro: Pomodoro, workitem: Workitem)`
The listeners can also pass the `carry` parameter. It is used for carrying some metadata through
the strategy -- event sequence. For example, when a user creates a workitem, the `CreateWorkitemStrategy`
is executed with `carry="edit"`, the core data model is updated, and the `WorkitemModel` gets updated,
too. After that, the `AfterWorkitemCreate` event fires, carrying the `edit` parameter. In the
corresponding listener, we make the new table row editable, so that the user can update it immediately.
The mandatory `event` parameter for the callbacks
contains the event name.
================================================
FILE: doc/pipeline.md
================================================
## CI/CD Pipeline

================================================
FILE: doc/release.md
================================================
# Releasing Flowkeeper
- Run unit and e2e tests in all available VMs.
- Run the build pipeline and check Windows binaries via Virustotal.
- Collect screenshots from all supported environments, upload them to website repo.
- Prepare the release page for the website. Record screenshots and GIFs for new features.
- Prepare a release announcement for LinkedIn, Reddit, Discord, Telegram, and mailing list.
- Review CHANGELOG.txt and update the date.
- Review and merge the rc PR into main.
- Create a new tag + release in GitHub, mark it as a draft.
- Wait for the release pipeline to complete.
- Trigger private Jenkins pipeline to sign Windows binaries.
- Check the binaries via Virustotal one more time.
- Remove the "draft" flag from GitHub release.
- Check website -- it should pick up changes automatically.
- Update download links on the website, if needed.
- Update OBS repo.
- Update Flatpak repo.
- Reply and close related GitHub issues.
- Distribute the release announcement on LinkedIn, Reddit, Discord, Telegram, and mailing list.
- Write about new Flowkeeper features in r/kde, r/opensource, r/Windows10, r/Windows11, r/windows,
r/macapps, r/Python, r/QtFramework, r/debian, r/openSUSE, r/linux, r/pomodoro, r/ProductivityApps.
# Qt6 versions
Last updated: **9 June 2025** for Flowkeeper **1.0.0**.
| OS | Released | EOL | Python | Qt 6 | PySide6 | Running options | Comments |
|-----------------------|------------|------------|---------|-------|---------|-----------------|-----------------------|
| Debian Bullseye 11 | 2021-08-14 | 2024-08-14 | 3.9 | N/A | | | 6.4.2 is in backports |
| Debian Bookworm 12 | 2023-06-10 | 2026-06-10 | 3.11 | 6.4.2 | | | |
| Debian Trixie 13 | 2025-..-.. | N/A | 3.12 | 6.8.2 | | | |
| Debian Sid | N/A | N/A | 3.13 | 6.8.2 | | | |
| Ubuntu Focal 20.04 | 2020-04-23 | 2025-05-29 | 3.8.2 | N/A | | | |
| Ubuntu Jammy 22.04 | 2022-04-21 | 2027-06-01 | 3.10.6 | 6.2.4 | | | |
| Ubuntu Noble 24.04 | 2024-04-25 | 2029-05-31 | 3.12.3 | 6.4.2 | | | |
| Ubuntu Oracular 24.10 | 2024-10-10 | 2025-07-.. | 3.12.6 | 6.6.2 | | | |
| Ubuntu Plucky 25.04 | 2025-04-17 | 2026-01-.. | 3.13.3 | 6.8.3 | | | |
| Fedora 40 | 2024-04-23 | 2025-05-13 | 3.12.3 | 6.6.2 | | | |
| Fedora 41 | 2024-10-29 | 2025-11-19 | 3.13.0 | 6.7.2 | | | |
| Fedora 42 | 2025-04-15 | 2026-05-16 | 3.13.2 | 6.8.2 | | | |
| RHEL 8 | 2019-05-07 | 2029-.. | 3.6 | N/A | | | |
| RHEL 9 | 2022-05-17 | 2032-.. | 3.12.9 | N/A | | | |
| RHEL 10 | 2025-05-20 | 2035-.. | 3.12.9 | 6.8.1 | | | |
| openSUSE Leap 15.6 | 2024-06-10 | 2025-12-.. | 3.11 | 6.6.3 | | | |
| openSUSE Tumbleweed | N/A | N/A | 3.13 | 6.9.0 | | | |
| Slackware 15 | 2022-02-02 | N/A | 3.9.10 | N/A | | | |
| Slackware Current | N/A | N/A | 3.12.10 | 6.8.3 | | | |
Here *Running options* are:
- **S**: Run directly from source (git clone, generate-resources.sh, run.sh)
- **V**: Run from source in a Python Virtual Environment (venv)
- **D**: Flowkeeper is available in a distro packages repository
- **3**: Flowkeeper is available in a 3rd-party packages repository
- **P**: A portable binary or standalone ZIP from flowkeeper.org
- **I**: An installer (e.g. DEB) from flowkeeper.org
- **F**: Flatpak (org.flowkeeper.Flowkeeper on Flathub)
- **A**: AppImage from flowkeeper.org works
This table needs to be updated with:
1. PySide6 versions
2. Shell command to install those
3. How to run Flowkeeper there (see running options above)
================================================
FILE: doc/strategies.md
================================================
# Strategies
You may find those as _Strategies_ in the code. They correspond to the end-user actions /
data mutations. Each command takes two or three parameters.
All data objects are keyed with `UID`, which is an arbitrary string, typically a GUID.
Apart from "business data", each command has a few metadata fields, associated with it:
- Sequence number, used for ordering and checking uniqueness
- Execution timestamp
- Execution user
## User strategies
Note that users have emails as IDs.
- `CreateUser("<EMAIL>", "<USER_NAME>")` - Fails if a user with this email already
exists, or a non-System user tries to execute this strategy. Emits `BeforeUserCreate` /
`AfterUserCreate` events.
- `DeleteUser("<EMAIL>", "")` - Deletes a user **recursively**, i.e. executes
`DeleteBacklogStrategy` for each of the child backlogs. Fails if a user with a given
email is not found, if a non-System user tries to execute this strategy, or if we are
trying to delete a System user. Emits `BeforeUserDelete` / `AfterUserDelete` events.
- `RenameUser("<EMAIL>", "<NEW_NAME>")` - Fails if a user with a given email is not found,
if a non-System user tries to execute this strategy, or if we are trying to rename a System
user. Emits `BeforeUserRename` / `AfterUserRename` events.
## Backlog strategies
- `CreateBacklog("<UID>", "<BACKLOG_NAME>")` - Fails if a backlog with this UID already
exists for the calling user. Emits `BeforeBacklogCreate` / `AfterBacklogCreate` events.
- `DeleteBacklog("<UID>", "")` - Deletes a backlog **recursively**, i.e. executes
`DeleteWorkitemStrategy` for each of the child workitems. Fails if a backlog with a given
UID is not found for the calling user. Emits `BeforeBacklogDelete` / `AfterBacklogDelete`
events.
- `RenameBacklog("<UID>", "<NEW_NAME>")` - Fails if a backlog with a given UID is not found
for the calling user. Emits `BeforeBacklogRename` / `AfterBacklogRename` events.
## Workitem strategies
- `CreateWorkitem("<WORKITEM_UID>", "<BACKLOG_UID>", "<WORKITEM_NAME>")` - Fails if a backlog
with this UID is not found or if a workitem with this UID already exists in that backlog.
Emits `BeforeWorkitemCreate` / `AfterWorkitemCreate` events.
- `DeleteWorkitem("<UID>", "")` - Deletes a workitem **recursively**, i.e. executes
`VoidPomodoroStrategy` for each of the running pomodoros first. Fails if a workitem with a given
UID is not found in any backlog. Emits `BeforeWorkitemDelete` / `AfterWorkitemDelete` events.
- `RenameWorkitem("<UID>", "<NEW_NAME>")` - Fails if a workitem with a given UID is not found in
any backlog or if it is sealed (finished or canceled). Doesn't do anything if the new name is
identical to the old one, otherwise emits `BeforeWorkitemRename` / `AfterWorkitemRename` events.
- `CompleteWorkitem("<UID>", "<STATE>")` - Seals the workitem with a given state (`finished` or
`canceled`) **recursively**, i.e. executes `VoidPomodoroStrategy` for each of the running
pomodoros, if any. Fails if a workitem with a given UID is not found in any backlog, if the
target state is neither `finished` nor `canceled`, or if the workitem is already sealed. Emits
`BeforeWorkitemComplete` / `AfterWorkitemComplete` events.
## Pomodoro strategies
Individual pomodoros don't have their own UIDs for simplicity. Although UIDs exist in runtime,
they are generated on the fly and not persisted.
- `AddPomodoroStrategy("<WORKITEM_UID>", "<ADDED_COUNT>")` - Fails if the number of added
pomodoros is less than 1, or if the workitem with specified UID is not found or sealed. Emits
`BeforePomodoroAdd` / `AfterPomodoroAdd` events.
- `RemovePomodoroStrategy("<WORKITEM_UID>", "<REMOVED_COUNT>")` - Fails if the number of removed
pomodoros is less than 1, or if the workitem with specified UID is not found or sealed, or if
there's not enough startable (`new` state) pomodoros in the workitem. Emits
`BeforePomodoroRemove` / `AfterPomodoroRemove` events.
- `VoidPomodoroStrategy("<WORKITEM_UID>")` - Fails if the workitem with specified UID is not
found or sealed, or has no running pomodoros. Emits `BeforePomodoroComplete` /
`AfterPomodoroComplete` events with target state `canceled`.
- `StartWorkStrategy("<WORKITEM_UID>", "<WORK_DURATION_IN_SECONDS>", "<REST_DURATION_IN_SECONDS>")` -
Fails if the workitem with specified UID is not found or sealed, or has no startable (`new`) pomodoros.
If the specified work duration is `0`, then the default value at the pomodoro creation moment is used.
If a Workitem is not yet running, it switches into `running` state, emitting a pair of
`BeforeWorkitemStart` / `AfterWorkitemStart` events. If the specified rest duration is `0`, then the
default value at the pomodoro creation moment is used.As long as it doesn't fail, this strategy
emits `BeforePomodoroWorkStart` / `AfterPomodoroWorkStart` events.
"Internal" strategies, triggered by the timer and auto-seal mechanism. Those are not registered
in the strategy factory / event sources, thus cannot appear in persisted form:
- `FinishPomodoroInternalStrategy("<WORKITEM_UID>")` - Fails if the workitem with specified UID is not
found or sealed, or has no running pomodoros. Emits `BeforePomodoroComplete` /
`AfterPomodoroComplete` events with target state `finished`.
- `StartRestInternalStrategy("<WORKITEM_UID>")` - Fails if the workitem
with specified UID is not found or is not running, or has no in-work (`work` state) pomodoros.
Emits `BeforePomodoroRestStart` / `AfterPomodoroRestStart` events.
## Server strategies
All below strategies are used with server-based event sources only, and are not persisted.
- `Authenticate("<EMAIL>", "<TOKEN>")` - This must be the first strategy sent by the client to
the server, otherwise the latter closes the communication channel. Note that it doesn't specify
token's format, leaving it to the authentication implementation.
- `Replay("<AFTER_SEQUENCE>")` - Used for requesting the replay of the strategies from the
server, starting from, but not including, `#AFTER_SEQUENCE`. The server may respond with one
or more messages with event history.
- `ReplayCompleted("")` - Used by the server to signal the last strategy in the replayed list.
- `Error("<ERROR_CODE>", "<ERROR_MESSAGE>")` - Sent by the server to report an error, e.g. wrong
credentials passed to `Authenticate` strategy. Flowkeeper Desktop raises a UI exception when
executing this strategy. This results in a message popup and a request to file a bug in GitHub.
- `PingStrategy("<UID>", "")` - The client sends this to verify connection to the server. It
expects to receive a `PongStrategy` response with the matching UID immediately after. If the
client doesn't receive a pong in a timely matter, it should switch to Offline / read-only mode.
- `PongStrategy("<UID>", "")` - Sent by the server as a reply to `PingStrategy`. If an Offline
client receives a matching pong, it should switch back to Online mode.
================================================
FILE: requirements-test.txt
================================================
-r requirements.txt
coverage
coveralls
assertpy
unittest-xml-reporting
pillow
================================================
FILE: requirements.txt
================================================
# Stay at 6.7.0 due to a macOS bug in 6.7.1 and 6.7.2, which says "No QtMultimedia backends found".
# 6.7.3 has another regression.
# PySide6==6.8.1
PySide6
semantic-version
cryptography
keyring
================================================
FILE: res/CHANGELOG.txt
================================================
### v1.0.2 (26 September 2025)
This version addresses a couple of new bugs, one of which is quite annoying:
- Can't void a pomodoro or record an interruption in non-latest backlogs (#199, #195).
- An occasional exception when waking up from sleep (#197).
### v1.0.1 (30 August 2025)
This version addresses all known bugs that we had in GitHub:
- Miscellaneous font issues (#136).
- Backlogs toggle icon doesn't change its color on theme change (#138).
- Voiding a pomodoro that has already finished (#149).
- Focus mode lost window title on Wayland (#152).
- Timezone not handled in task tooltips (#156).
- Tutorial bubbles don't follow underlying windows (#162).
- Pin/unpin icon is visible on Wayland (#185).
- Unhandled TypeError while clicking "Surprise me" button repeatedly (#188).
- "Long break" keeps on triggering while it is deactivated in the configuration (#192).
Also, Windows binaries are now compiled using Clang to reduce antivirus false positives.
### v1.0.0 (14 July 2025)
This version brings Flowkeeper even closer to the original Pomodoro Technique definition from Francesco Cirillo's book.
Pomodoro interruptions are now handled as the book prescribes, and we introduced support for long breaks. One major
deviation from the Technique in this version is the ability to track unfocused time. As usual there's a bunch of
miscellaneous quality-of-life improvements, bugfixes and better support for Linux.
New features:
- Tracking unfocused time -- try to start a work item with no pomodoros (#94, #98).
- Long breaks and working in series, see Settings > Series and breaks (#53).
- Dragging work items between backlogs (#60).
- Recording interruptions (#75).
- Voided pomodoros are displayed as ticks, and completed ones are crossed out to better match the Book (#41, #92).
- Import from CSV and GitHub, try Ctrl+I (#125).
- Hovering over pomodoros displays a detailed log of your work (#93).
- "Contact us" submenu to facilitate feedback collection (#111).
- Flowkeeper window now hides automatically on auto-start (#102).
- New font selector for macOS, which supports Apple system font (#113).
- Resting music starts playing from the right position if we restart Flowkeeper.
Technical improvements:
- Standard data and log directories on Linux, macOS and Windows (#65).
- Three new ways to get Flowkeeper:
- Install it from Flathub (#63),
- Install from OBS for openSUSE Tumbleweed (#127),
- Download a portable AppImage from flowkeeper.org or GitHub Releases.
- Flowkeeper now supports Linux on arm64 / aarch64.
- Added support for Qt 6.8.x.
- A preview of Flowkeeper CLI is now available in the sources (#46).
- Ignoring own modifications when "watch changes" is enabled (#130).
- Improved technical documentation (#80, #87).
- Complete drag & drop rewrite to accommodate moving work items between backlogs.
- Flowkeeper binaries are now built using Nuitka in addition to PyInstaller (#114).
- Madelene music composition changed its format from mp3 to m4a/aac.
- Removed support for Ubuntu 20.04, as GitHub Actions deprecated it.
Bug fixes:
- Selecting directory as log file (#108).
- Window icon on Wayland (#110).
- Line breaks in work items and backlogs (#132).
- Changing audio devices while Flowkeeper is running (#120).
### v0.9.1 (15 January 2025)
This is a bugfix release, it has no new features.
- [Bugfix] Main window doesn't restore correctly on Hyprland (#48).
- [Bugfix] Unhandled SystemError if Flowkeeper is upgraded while a pomodoro is running (#62).
- [Bugfix] Broken fonts / squares instead of characters on Ubuntu 23.10 (#68).
- [Bugfix] "Unhandled JSONDecodeError" behind the proxy (#69, #73).
- [Bugfix] Flowkeeper crashes when you select a directory as a data file (#70).
- [Bugfix] Error when trying to start another pomodoro while the timer is running (#72, #74).
- [Bugfix] On Windows, the main window close button is disabled (#77).
- [Bugfix] Flowkeeper doesn't switch to focus mode after one completed pomodoro (#79).
- [Bugfix] Unhandled AttributeError when computer wakes up from sleep while playing audio (#81).
- [Bugfix] There's no sound until you change Audio settings once (#85).
- [Bugfix] Able to click "next pomodoro" after marking workitem complete (#88).
- [Technical] Enhanced bug reporting - GitHub issues now include info about versions.
- [Technical] New command-line flag: --debug, enables debug logs for this session.
- [Technical] System proxy settings are applied automatically.
- [Technical] Using embedded Noto Sans font by default, see Settings > Fonts.
### v0.9.0 (30 December 2024)
- You can now drag backlogs and work items to reorder them. Backlogs are not reordered automatically anymore.
- Unplanned work items are now highlighted with asterisk (*).
- You can now choose between two timer widgets -- "Classic" and "Minimalistic".
- Quick configuration wizard when you open Flowkeeper for the first time.
- Moved "Hide completed items" to the toolbar (#42).
- In "minimalistic" mode all focus mode actions are under the "stopwatch" menu.
- You can now configure Flowkeeper to execute programs on different events, see Settings > Integration (#40).
- All buttons and icons now have tooltips with shortcuts in them (#43, #52).
- Journaling-friendly Work Summary (F3) -- more output formats and enhanced usability (#45, #49).
- New setting: General > Single Flowkeeper instance (#50).
- Miscellaneous UI improvements, notably new tray icon visualization (#44, #51, #39).
- You can now enable main menu in Settings > Appearance.
- [Bugfix] Couldn't open Settings when there's no audio devices (#38).
### v0.8.1 (13 November 2024)
- [Bugfix] Fixed a bug in tutorial step 8.
### v0.8.0 (4 November 2024)
- You can now use #tags in work items. You can turn it off in Settings > General.
- When #tags are enabled, work item text wraps if it doesn't fit on one line. This is the default behavior.
- Embedded resting music - "Madelene" by Lobo Loco, with kind permission from its author (CC-BY-NC-ND).
- Selectable audio output in Settings > Audio.
- [Technical] Flowkeeper displays window title in the focus mode on Wayland automatically.
- [Technical] StartWork strategy now also carries planned rest duration.
- [Technical] Smart import now preserves timestamps and history.
- [Technical] We can now import stuff which happened in the past.
- [Bugfix] Fixed "user already exists" import error.
- [Bugfix] Fixed incorrect pomodoro states after import.
- [Bugfix] Fixed incorrect timestamps in smart import.
- [Bugfix] Fixed incorrect work / rest durations in smart import.
### v0.7.1 (13 September 2024)
- [Bugfix] Fixed import/export wizard look & feel on Windows 11 with Qt 6.7.x.
- [Bugfix] Fixed "No QtMultimedia backends found" issue on macOS.
### v0.7.0 (10 September 2024)
- Disabled data sync and e2e encryption features.
- Removed public references to any semi-implemented features.
- Documented the full manual UAT test suite and executed it.
- The hole in the timer widget now displays correctly.
- New "Compress" action in the File source settings (click it for details).
- New "Detect automatically (Default)" theme.
- Added "New Backlog From Incomplete" action to speed up planning.
- New "Work Summary" feature for making human-readable reports like time sheets.
- Special appearance defaults for Gnome to match its quirks.
- Minor improvements in Statistics (different bar colors, better theming)
- [Bugfix] Fixed the 00:00 bug when the user opened the client right when the work ends.
- [Bugfix] Fixed a rare bug when we tried to read a file which was written at the same time.
- [Bugfix] Fixed an "Invalid key" bug in Import.
- [Bugfix] Fixed a bug where workitem actions' visibility was wrong after external changes.
- [Technical] Removed CompletePomodoro and StartRest strategies.
- [Technical] Removed "Auto-seal items after" setting.
- [Technical] Split the README.md into several files.
### v0.6.3 (1 September 2024)
- Added settings to disable data sync and e2e encryption features
- [Bugfix] Fixed "Sign in" button on some Linux, e.g. Debian Sid.
- [Technical] GitHub pipeline now signs and notarizes macOS builds.
- [Technical] GitHub pipeline now signs Windows binaries automatically.
- [Technical] A macOS build for x86.
- [Technical] Simplified the code for events prioritization.
- [Technical] Listed all testable Use Cases.
- [Technical] Created a farm of VMs for running e2e tests, tested as-is.
### v0.6.2 (1 August 2024)
- [Bugfix] OAuth login on Windows (QTBUG-124333).
### v0.6.1 (31 July 2024)
- Brand new interactive tutorial.
- The window loses its frame in Focus mode, with double-click and move.
- The window can be pinned to stay always-on-top.
- New appearance settings (always on top, window title in focus mode).
- Users may now ignore "A keyring is not available" error.
- [Technical] macOS DMG installer is now properly signed and notarized.
- [Technical] macOS now asks to unlock login keychain only once.
- [Technical] Screenshots collection via e2e tests.
- [Technical] Enabled SonarCloud static code analysis + "code smells" GitHub badge.
### v0.6.0 (21 June 2024)
- Implemented the Statistics feature AKA Pomodoro Health.
- [Bugfix] Fixed NoKeyringError on Kubuntu 22.04.
- [Technical] Removed dependency on typing.Self, which required Python 3.11+.
### v0.5.1 (14 June 2024)
- Simplified Connection settings.
- Gradient can be now selected in the Settings popup.
- [Bugfix] Fixed an error with the passwords stored in the OS keychain.
- [Bugfix] Fixed Settings not detecting the changes correctly.
- [Technical] Now the user can choose to ignore errors for the Local File data source.
- [Technical] Buttons like "Sign out" can now change Settings window state.
- [Technical] Alternative keyrings (keyrings.alt package) are added as a fallback.
### v0.5.0 (9 June 2024)
- Support for end-to-end encryption using Fernet (mandatory for flowkeeper.org).
- Encrypted content can be mixed with plaintext.
- Support for encryption and compression in Import and Export.
- Import now works in a new "smart merge" mode, which should prevent duplicate pomodoros.
- You can now sign up to flowkeeper.org from the desktop app.
- You can now request flowkeeper.org account deletion from Settings.
- You can now change appearance theme on the fly.
- New color themes.
- New fonts can now be applied without application restart.
- You can now start another pomodoro by clicking the lime "play" icon in the system tray.
- New Focus-mode button to complete the current item while it's running.
- [Bugfix] Fixed workitem search.
- [Bugfix] Fixed "Start another pomodoro?" logic.
- [Bugfix] Modal dialogs open shrunk on secondary displays on Linux.
- [Technical] Restructured the resources, icons and colors.
- [Technical] Sharing timer state with teammates (no support in the UI yet).
- [Technical] Introduced Serializer and Cryptograph facilities.
- [Technical] Simplified tray icon and Focus view implementation.
- [Technical] E2e tests handle exceptions correctly now.
- [Technical] Introduced a configurable logger.
- [Technical] New facility for creating timer displays - AbstractTimerDisplay.
- [Technical] Migrated to the latest Qt 6.7.
- [Technical] The secrets are now stored in the OS keychain.
### v0.4.1 (25 April 2024)
- Added configurable toolbars.
- Improved performance of changing the data source when there are large backlogs.
- [Bugfix] Fixed the actions state update.
- [Bugfix] Fixed binary builds for Wayland-enabled Linux. Now 22.04 is the minimal supported Ubuntu version.
- [Technical] Started working on the e2e test farm triggered from the GitHub pipeline.
- [Technical] Added an "info overlay" feature, which can be used for walkthroughs. Disabled the Tutorial by default.
- [Technical] Upgraded to Qt 6.7.0.
- [Technical] Release pipeline now runs unit tests.
### v0.4.0 (18 April 2024)
- Changes to data source settings do not require Flowkeeper restart.
- Some rare and complex bugs are now fixed thanks to improved Event Source handling.
- [Technical] Rest and work durations are now floating-point numbers.
- [Technical] Windows dev. environment is now supported and documented.
- [Technical] First working end-to-end tests providing 61% UI code coverage.
- [Technical] Unit test coverage for `fk.core` module increased to 68%.
### v0.3.3 (5 April 2024)
- Added a tutorial / quickstart wizard.
- If something goes terribly wrong on startup, Flowkeeper asks to reset the settings.
- Removed deprecated settings and the progress bar, which looked wrong on Windows.
- All table items now have tooltips.
- When installed on Windows or macOS, Flowkeeper now launches much faster.
- CI/CD pipeline now also builds a DEB installer for Debian-based systems.
- Ubuntu 20.04 is now supported (used to require 22.04+)
- Minor improvements to the Settings UI.
- [Technical] Settings are now set in bulk.
- [Technical] Gradient generator now has a fallback, just in case.
- [Technical] Wrote new unit tests.
- [Technical] Implemented Ephemeral event source, useful for the unit tests.
- [Technical] Started working on end-to-end (e2e) tests.
- [Technical] CI pipeline now collects unit test coverage.
- [Bugfix] Fixed gradient generation which was failing on Windows and Mac sometimes.
- [Bugfix] Timer window now resizes correctly on Windows.
- [Bugfix] Void Pomodoro button is now displayed correctly.
### v0.3.2 (14 March 2024)
- Flowkeeper checks for updates against GitHub (configurable).
- Flowkeeper shuts down automatically when important settings are changed.
- The filter for completed items is now stored in Settings.
- [Bugfix] Backlog auto-ordering is corrected.
- [Bugfix] Completed items now hide immediately if the filter is enabled.
- [Technical] App version is parsed from the changelog.
- [Technical] Event sequence errors are ignored by default.
- [Technical] New troubleshooting mechanism ("backlog dump") via Ctrl+D.
### v0.3.1 (10 March 2024)
- Flowkeeper.org authentication via Google OAuth.
- [Known bug] The app crashes after you authenticate with Google. Just restart it.
- [Technical] Pomodoros expired offline now finish successfully.
- [Technical] All pomodoros now complete implicitly. CompletePomodoro strategy is deprecated.
### v0.2.1 (22 February 2024)
- Fixed work item actions visibility
- Keyboard shortcuts are now configurable in Settings
### v0.2.0 (20 February 2024)
- Support for gradient timer header background.
- Better resizing for the header background images.
- Better error handling with "Submit an Issue" option.
- Placeholders for empty tables.
- Asynchronous event sources to support "Loading..." mode.
- Backlog progress bar is now updated automatically on pomodoro actions.
- Window configuration is now persisted.
- Import errors can be now ignored.
- Server connection state in the window title + icon.
- Websocket heartbeat to keep connection alive.
- New settings for sounds volume.
- [Technical] Updated documentation.
- [Technical] Major UI code refactoring, should make the app easier to maintain.
- [Technical] Mini-apps to test UI components independently.
- [Technical] Moved Actions from .ui file to the code.
### v0.1.3 (25 December 2023)
- Multiple bugfixes.
- Configurable fonts and timer header background.
- Themes: Light, Dark and Mixed.
### v0.1.2, (11 December 2023)
- Unit tests.
- Websocket authentication.
### v0.1.1, (6 December 2023)
- Packaged for Windows, Mac and Linux.
### v0.1.0, (27 November 2023)
- First public version.
================================================
FILE: res/CREDITS.txt
================================================
Constantine Kulak <https://github.com/co-stig>: Flowkeeper author.
Marco Sarti <marco@elogiclab.com>: AUR package maintainer.
faveoled <https://github.com/faveoled>: Created the first Flatpak package for Flowkeeper, which we adopted.
Flowkeeper is built using Qt Community Edition by The Qt Company <https://www.qt.io/licensing>.
All monochrome icons, including Pomodoro symbols are Google Material Icons <https://fonts.google.com/icons>.
Flowkeeper logo (red tomato icon): "Tomate" by Andreas Preuss AKA marauder <https://openclipart.org/detail/117469/tomate>.
Pomodoro and Pomodoro Technique are registered trademarks of Francesco Cirillo <https://www.pomodorotechnique.com/pomodoro-trademark-guidelines.php>.
Embedded sounds (bell and tick): Cannot find the original author. I used the same sounds in the original Flowkeeper (v1) in 2010.
Embedded resting music: Madelene (ID 1315), kindly provided by Lobo Loco <https://www.musikbrause.de> under CC-BY-NC-ND (Creative Commons Attribution NonCommercial NoDerivs).
Flowkeeper is hosted on GitHub <https://github.com/flowkeeper-org/fk-desktop>, with its CI/CD based on GitHub Actions.
We use Coverage.py by Ned Batchelder <https://github.com/nedbat/coveragepy/blob/master/NOTICE.txt> for collecting unit test coverage.
The build pipeline automatically uploads test coverage results to coveralls.io under its "Open Source" plan <https://coveralls.io/terms>.
SonarCloud (Free Edition) <https://sonarcloud.io> provides us useful code quality metrics and the "code smells" GitHub badge.
JetBrains PyCharm (Community Edition) <https://www.jetbrains.com/pycharm/> is our IDE of choice.
Flowkeeper uses "python-semanticversion" library by Raphaël Barrois <https://github.com/rbarrois/python-semanticversion/blob/master/LICENSE> to check for updates.
Flowkeeper uses "keyring" library by Jason R. Coombs <https://github.com/jaraco/keyring?tab=MIT-1-ov-file#readme> for storing secrets in the OS-native keychain / secret storage.
Flowkeeper uses "cryptography" library by Paul Kehrer <https://github.com/pyca/cryptography/blob/main/LICENSE> for its Fernet end-to-end encryption algorithm.
Flowkeeper uses "assertpy" library by Activision Publishing, Inc. <https://github.com/assertpy/assertpy/blob/main/LICENSE> and "unittest-xml-reporting" by
Daniel Fernandes Martins <https://github.com/xmlrunner/unittest-xml-reporting/blob/master/LICENSE> for writing and executing unit tests.
Python Pillow library by Jeffrey A. Clark and contributors <https://github.com/python-pillow/Pillow/blob/main/LICENSE> is used for taking screenshots in the end-to-end tests.
To produce desktop binaries for Linux, macOS and Windows we use PyInstaller <https://github.com/pyinstaller/pyinstaller?tab=License-1-ov-file#readme>.
The Windows installer is created using Inno Setup by Jordan Russell <https://jrsoftware.org/files/is/license.txt>.
The Debian installer is created using dpkg-deb tool <https://www.apt-browse.org/browse/ubuntu/bionic/main/amd64/dpkg/1.19.0.5ubuntu2/file/usr/share/doc/dpkg/copyright>.
The embedded font is Noto Sans, licensed under the SIL Open Font License, Version 1.1. Copyright 2022 The Noto Project Authors <https://github.com/notofonts/latin-greek-cyrillic>.
Linux package repositories are provided by (awesome!) openSUSE Build Service <https://build.opensuse.org/>.
================================================
FILE: res/LICENSE.txt
================================================
GNU General Public License
==========================
_Version 3, 29 June 2007_
_Copyright © 2007 Free Software Foundation, Inc. <<http://fsf.org/>>_
Everyone is permitted to copy and distribute verbatim copies of this license
document, but changing it is not allowed.
## Preamble
The GNU General Public License is a free, copyleft license for software and other
kinds of works.
The licenses for most software and other practical works are designed to take away
your freedom to share and change the works. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change all versions of a
program--to make sure it remains free software for all its users. We, the Free
Software Foundation, use the GNU General Public License for most of our software; it
applies also to any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not price. Our General
Public Licenses are designed to make sure that you have the freedom to distribute
copies of free software (and charge for them if you wish), that you receive source
code or can get it if you want it, that you can change the software or use pieces of
it in new free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you these rights or
asking you to surrender the rights. Therefore, you have certain responsibilities if
you distribute copies of the software, or if you modify it: responsibilities to
respect the freedom of others.
For example, if you distribute copies of such a program, whether gratis or for a fee,
you must pass on to the recipients the same freedoms that you received. You must make
sure that they, too, receive or can get the source code. And you must show them these
terms so they know their rights.
Developers that use the GNU GPL protect your rights with two steps: **(1)** assert
copyright on the software, and **(2)** offer you this License giving you legal permission
to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains that there is
no warranty for this free software. For both users' and authors' sake, the GPL
requires that modified versions be marked as changed, so that their problems will not
be attributed erroneously to authors of previous versions.
Some devices are designed to deny users access to install or run modified versions of
the software inside them, although the manufacturer can do so. This is fundamentally
incompatible with the aim of protecting users' freedom to change the software. The
systematic pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we have designed
this version of the GPL to prohibit the practice for those products. If such problems
arise substantially in other domains, we stand ready to extend this provision to
those domains in future versions of the GPL, as needed to protect the freedom of
users.
Finally, every program is threatened constantly by software patents. States should
not allow patents to restrict development and use of software on general-purpose
computers, but in those that do, we wish to avoid the special danger that patents
applied to a free program could make it effectively proprietary. To prevent this, the
GPL assures that patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and modification follow.
## TERMS AND CONDITIONS
### 0. Definitions
“This License” refers to version 3 of the GNU General Public License.
“Copyright” also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
“The Program” refers to any copyrightable work licensed under this
License. Each licensee is addressed as “you”. “Licensees” and
“recipients” may be individuals or organizations.
To “modify” a work means to copy from or adapt all or part of the work in
a fashion requiring copyright permission, other than the making of an exact copy. The
resulting work is called a “modified version” of the earlier work or a
work “based on” the earlier work.
A “covered work” means either the unmodified Program or a work based on
the Program.
To “propagate” a work means to do anything with it that, without
permission, would make you directly or secondarily liable for infringement under
applicable copyright law, except executing it on a computer or modifying a private
copy. Propagation includes copying, distribution (with or without modification),
making available to the public, and in some countries other activities as well.
To “convey” a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through a computer
network, with no transfer of a copy, is not conveying.
An interactive user interface displays “Appropriate Legal Notices” to the
extent that it includes a convenient and prominently visible feature that **(1)**
displays an appropriate copyright notice, and **(2)** tells the user that there is no
warranty for the work (except to the extent that warranties are provided), that
licensees may convey the work under this License, and how to view a copy of this
License. If the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
### 1. Source Code
The “source code” for a work means the preferred form of the work for
making modifications to it. “Object code” means any non-source form of a
work.
A “Standard Interface” means an interface that either is an official
standard defined by a recognized standards body, or, in the case of interfaces
specified for a particular programming language, one that is widely used among
developers working in that language.
The “System Libraries” of an executable work include anything, other than
the work as a whole, that **(a)** is included in the normal form of packaging a Major
Component, but which is not part of that Major Component, and **(b)** serves only to
enable use of the work with that Major Component, or to implement a Standard
Interface for which an implementation is available to the public in source code form.
A “Major Component”, in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system (if any) on which
the executable work runs, or a compiler used to produce the work, or an object code
interpreter used to run it.
The “Corresponding Source” for a work in object code form means all the
source code needed to generate, install, and (for an executable work) run the object
code and to modify the work, including scripts to control those activities. However,
it does not include the work's System Libraries, or general-purpose tools or
generally available free programs which are used unmodified in performing those
activities but which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for the work, and
the source code for shared libraries and dynamically linked subprograms that the work
is specifically designed to require, such as by intimate data communication or
control flow between those subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate
automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
### 2. Basic Permissions
All rights granted under this License are granted for the term of copyright on the
Program, and are irrevocable provided the stated conditions are met. This License
explicitly affirms your unlimited permission to run the unmodified Program. The
output from running a covered work is covered by this License only if the output,
given its content, constitutes a covered work. This License acknowledges your rights
of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without
conditions so long as your license otherwise remains in force. You may convey covered
works to others for the sole purpose of having them make modifications exclusively
for you, or provide you with facilities for running those works, provided that you
comply with the terms of this License in conveying all material for which you do not
control copyright. Those thus making or running the covered works for you must do so
exclusively on your behalf, under your direction and control, on terms that prohibit
them from making any copies of your copyrighted material outside their relationship
with you.
Conveying under any other circumstances is permitted solely under the conditions
stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
### 3. Protecting Users' Legal Rights From Anti-Circumvention Law
No covered work shall be deemed part of an effective technological measure under any
applicable law fulfilling obligations under article 11 of the WIPO copyright treaty
adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention
of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of
technological measures to the extent such circumvention is effected by exercising
rights under this License with respect to the covered work, and you disclaim any
intention to limit operation or modification of the work as a means of enforcing,
against the work's users, your or third parties' legal rights to forbid circumvention
of technological measures.
### 4. Conveying Verbatim Copies
You may convey verbatim copies of the Program's source code as you receive it, in any
medium, provided that you conspicuously and appropriately publish on each copy an
appropriate copyright notice; keep intact all notices stating that this License and
any non-permissive terms added in accord with section 7 apply to the code; keep
intact all notices of the absence of any warranty; and give all recipients a copy of
this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer
support or warranty protection for a fee.
### 5. Conveying Modified Source Versions
You may convey a work based on the Program, or the modifications to produce it from
the Program, in the form of source code under the terms of section 4, provided that
you also meet all of these conditions:
* **a)** The work must carry prominent notices stating that you modified it, and giving a
relevant date.
* **b)** The work must carry prominent notices stating that it is released under this
License and any conditions added under section 7. This requirement modifies the
requirement in section 4 to “keep intact all notices”.
* **c)** You must license the entire work, as a whole, under this License to anyone who
comes into possession of a copy. This License will therefore apply, along with any
applicable section 7 additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no permission to license the
work in any other way, but it does not invalidate such permission if you have
separately received it.
* **d)** If the work has interactive user interfaces, each must display Appropriate Legal
Notices; however, if the Program has interactive interfaces that do not display
Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are
not by their nature extensions of the covered work, and which are not combined with
it such as to form a larger program, in or on a volume of a storage or distribution
medium, is called an “aggregate” if the compilation and its resulting
copyright are not used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work in an aggregate
does not cause this License to apply to the other parts of the aggregate.
### 6. Conveying Non-Source Forms
You may convey a covered work in object code form under the terms of sections 4 and
5, provided that you also convey the machine-readable Corresponding Source under the
terms of this License, in one of these ways:
* **a)** Convey the object code in, or embodied in, a physical product (including a
physical distribution medium), accompanied by the Corresponding Source fixed on a
durable physical medium customarily used for software interchange.
* **b)** Convey the object code in, or embodied in, a physical product (including a
physical distribution medium), accompanied by a written offer, valid for at least
three years and valid for as long as you offer spare parts or customer support for
that product model, to give anyone who possesses the object code either **(1)** a copy of
the Corresponding Source for all the software in the product that is covered by this
License, on a durable physical medium customarily used for software interchange, for
a price no more than your reasonable cost of physically performing this conveying of
source, or **(2)** access to copy the Corresponding Source from a network server at no
charge.
* **c)** Convey individual copies of the object code with a copy of the written offer to
provide the Corresponding Source. This alternative is allowed only occasionally and
noncommercially, and only if you received the object code with such an offer, in
accord with subsection 6b.
* **d)** Convey the object code by offering access from a designated place (gratis or for
a charge), and offer equivalent access to the Corresponding Source in the same way
through the same place at no further charge. You need not require recipients to copy
the Corresponding Source along with the object code. If the place to copy the object
code is a network server, the Corresponding Source may be on a different server
(operated by you or a third party) that supports equivalent copying facilities,
provided you maintain clear directions next to the object code saying where to find
the Corresponding Source. Regardless of what server hosts the Corresponding Source,
you remain obligated to ensure that it is available for as long as needed to satisfy
these requirements.
* **e)** Convey the object code using peer-to-peer transmission, provided you inform
other peers where the object code and Corresponding Source of the work are being
offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the
Corresponding Source as a System Library, need not be included in conveying the
object code work.
A “User Product” is either **(1)** a “consumer product”, which
means any tangible personal property which is normally used for personal, family, or
household purposes, or **(2)** anything designed or sold for incorporation into a
dwelling. In determining whether a product is a consumer product, doubtful cases
shall be resolved in favor of coverage. For a particular product received by a
particular user, “normally used” refers to a typical or common use of
that class of product, regardless of the status of the particular user or of the way
in which the particular user actually uses, or expects or is expected to use, the
product. A product is a consumer product regardless of whether the product has
substantial commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
“Installation Information” for a User Product means any methods,
procedures, authorization keys, or other information required to install and execute
modified versions of a covered work in that User Product from a modified version of
its Corresponding Source. The information must suffice to ensure that the continued
functioning of the modified object code is in no case prevented or interfered with
solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for
use in, a User Product, and the conveying occurs as part of a transaction in which
the right of possession and use of the User Product is transferred to the recipient
in perpetuity or for a fixed term (regardless of how the transaction is
characterized), the Corresponding Source conveyed under this section must be
accompanied by the Installation Information. But this requirement does not apply if
neither you nor any third party retains the ability to install modified object code
on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to
continue to provide support service, warranty, or updates for a work that has been
modified or installed by the recipient, or for the User Product in which it has been
modified or installed. Access to a network may be denied when the modification itself
materially and adversely affects the operation of the network or violates the rules
and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with
this section must be in a format that is publicly documented (and with an
implementation available to the public in source code form), and must require no
special password or key for unpacking, reading or copying.
### 7. Additional Terms
“Additional permissions” are terms that supplement the terms of this
License by making exceptions from one or more of its conditions. Additional
permissions that are applicable to the entire Program shall be treated as though they
were included in this License, to the extent that they are valid under applicable
law. If additional permissions apply only to part of the Program, that part may be
used separately under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any
additional permissions from that copy, or from any part of it. (Additional
permissions may be written to require their own removal in certain cases when you
modify the work.) You may place additional permissions on material, added by you to a
covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a
covered work, you may (if authorized by the copyright holders of that material)
supplement the terms of this License with terms:
* **a)** Disclaiming warranty or limiting liability differently from the terms of
sections 15 and 16 of this License; or
* **b)** Requiring preservation of specified reasonable legal notices or author
attributions in that material or in the Appropriate Legal Notices displayed by works
containing it; or
* **c)** Prohibiting misrepresentation of the origin of that material, or requiring that
modified versions of such material be marked in reasonable ways as different from the
original version; or
* **d)** Limiting the use for publicity purposes of names of licensors or authors of the
material; or
* **e)** Declining to grant rights under trademark law for use of some trade names,
trademarks, or service marks; or
* **f)** Requiring indemnification of licensors and authors of that material by anyone
who conveys the material (or modified versions of it) with contractual assumptions of
liability to the recipient, for any liability that these contractual assumptions
directly impose on those licensors and authors.
All other non-permissive additional terms are considered “further
restrictions” within the meaning of section 10. If the Program as you received
it, or any part of it, contains a notice stating that it is governed by this License
along with a term that is a further restriction, you may remove that term. If a
license document contains a further restriction but permits relicensing or conveying
under this License, you may add to a covered work material governed by the terms of
that license document, provided that the further restriction does not survive such
relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in
the relevant source files, a statement of the additional terms that apply to those
files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a
separately written license, or stated as exceptions; the above requirements apply
either way.
### 8. Termination
You may not propagate or modify a covered work except as expressly provided under
this License. Any attempt otherwise to propagate or modify it is void, and will
automatically terminate your rights under this License (including any patent licenses
granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a
particular copyright holder is reinstated **(a)** provisionally, unless and until the
copyright holder explicitly and finally terminates your license, and **(b)** permanently,
if the copyright holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently
if the copyright holder notifies you of the violation by some reasonable means, this
is the first time you have received notice of violation of this License (for any
work) from that copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of
parties who have received copies or rights from you under this License. If your
rights have been terminated and not permanently reinstated, you do not qualify to
receive new licenses for the same material under section 10.
### 9. Acceptance Not Required for Having Copies
You are not required to accept this License in order to receive or run a copy of the
Program. Ancillary propagation of a covered work occurring solely as a consequence of
using peer-to-peer transmission to receive a copy likewise does not require
acceptance. However, nothing other than this License grants you permission to
propagate or modify any covered work. These actions infringe copyright if you do not
accept this License. Therefore, by modifying or propagating a covered work, you
indicate your acceptance of this License to do so.
### 10. Automatic Licensing of Downstream Recipients
Each time you convey a covered work, the recipient automatically receives a license
from the original licensors, to run, modify and propagate that work, subject to this
License. You are not responsible for enforcing compliance by third parties with this
License.
An “entity transaction” is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an organization, or
merging organizations. If propagation of a covered work results from an entity
transaction, each party to that transaction who receives a copy of the work also
receives whatever licenses to the work the party's predecessor in interest had or
could give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if the predecessor
has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or
affirmed under this License. For example, you may not impose a license fee, royalty,
or other charge for exercise of rights granted under this License, and you may not
initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging
that any patent claim is infringed by making, using, selling, offering for sale, or
importing the Program or any portion of it.
### 11. Patents
A “contributor” is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The work thus
licensed is called the contributor's “contributor version”.
A contributor's “essential patent claims” are all patent claims owned or
controlled by the contributor, whether already acquired or hereafter acquired, that
would be infringed by some manner, permitted by this License, of making, using, or
selling its contributor version, but do not include claims that would be infringed
only as a consequence of further modification of the contributor version. For
purposes of this definition, “control” includes the right to grant patent
sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license
under the contributor's essential patent claims, to make, use, sell, offer for sale,
import and otherwise run, modify and propagate the contents of its contributor
version.
In the following three paragraphs, a “patent license” is any express
agreement or commitment, however denominated, not to enforce a patent (such as an
express permission to practice a patent or covenant not to sue for patent
infringement). To “grant” such a patent license to a party means to make
such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the
Corresponding Source of the work is not available for anyone to copy, free of charge
and under the terms of this License, through a publicly available network server or
other readily accessible means, then you must either **(1)** cause the Corresponding
Source to be so available, or **(2)** arrange to deprive yourself of the benefit of the
patent license for this particular work, or **(3)** arrange, in a manner consistent with
the requirements of this License, to extend the patent license to downstream
recipients. “Knowingly relying” means you have actual knowledge that, but
for the patent license, your conveying the covered work in a country, or your
recipient's use of the covered work in a country, would infringe one or more
identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you
convey, or propagate by procuring conveyance of, a covered work, and grant a patent
license to some of the parties receiving the covered work authorizing them to use,
propagate, modify or convey a specific copy of the covered work, then the patent
license you grant is automatically extended to all recipients of the covered work and
works based on it.
A patent license is “discriminatory” if it does not include within the
scope of its coverage, prohibits the exercise of, or is conditioned on the
non-exercise of one or more of the rights that are specifically granted under this
License. You may not convey a covered work if you are a party to an arrangement with
a third party that is in the business of distributing software, under which you make
payment to the third party based on the extent of your activity of conveying the
work, and under which the third party grants, to any of the parties who would receive
the covered work from you, a discriminatory patent license **(a)** in connection with
copies of the covered work conveyed by you (or copies made from those copies), or **(b)**
primarily for and in connection with specific products or compilations that contain
the covered work, unless you entered into that arrangement, or that patent license
was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied
license or other defenses to infringement that may otherwise be available to you
under applicable patent law.
### 12. No Surrender of Others' Freedom
If conditions are imposed on you (whether by court order, agreement or otherwise)
that contradict the conditions of this License, they do not excuse you from the
conditions of this License. If you cannot convey a covered work so as to satisfy
simultaneously your obligations under this License and any other pertinent
obligations, then as a consequence you may not convey it at all. For example, if you
agree to terms that obligate you to collect a royalty for further conveying from
those to whom you convey the Program, the only way you could satisfy both those terms
and this License would be to refrain entirely from conveying the Program.
### 13. Use with the GNU Affero General Public License
Notwithstanding any other provision of this License, you have permission to link or
combine any covered work with a work licensed under version 3 of the GNU Affero
General Public License into a single combined work, and to convey the resulting work.
The terms of this License will continue to apply to the part which is the covered
work, but the special requirements of the GNU Affero General Public License, section
13, concerning interaction through a network will apply to the combination as such.
### 14. Revised Versions of this License
The Free Software Foundation may publish revised and/or new versions of the GNU
General Public License from time to time. Such new versions will be similar in spirit
to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that
a certain numbered version of the GNU General Public License “or any later
version” applies to it, you have the option of following the terms and
conditions either of that numbered version or of any later version published by the
Free Software Foundation. If the Program does not specify a version number of the GNU
General Public License, you may choose any version ever published by the Free
Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU
General Public License can be used, that proxy's public statement of acceptance of a
version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no
additional obligations are imposed on any author or copyright holder as a result of
your choosing to follow a later version.
### 15. Disclaimer of Warranty
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER
EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE
QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE
DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
### 16. Limitation of Liability
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS
PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL,
INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE
OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE
WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
### 17. Interpretation of Sections 15 and 16
If the disclaimer of warranty and limitation of liability provided above cannot be
given local legal effect according to their terms, reviewing courts shall apply local
law that most closely approximates an absolute waiver of all civil liability in
connection with the Program, unless a warranty or assumption of liability accompanies
a copy of the Program in return for a fee.
_END OF TERMS AND CONDITIONS_
================================================
FILE: res/about.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Flowkeeper - Pomodoro timer for power users and teams
~ Copyright (c) 2023 Constantine Kulak
~
~ This program 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.
~
~ This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ui version="4.0">
<class>Dialog</class>
<widget class="QDialog" name="Dialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>650</height>
</rect>
</property>
<property name="windowTitle">
<string>About</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="hl">
<item>
<widget class="QLabel" name="icon">
<property name="text">
<string/>
</property>
<property name="scaledContents">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="vl">
<item>
<widget class="QLabel" name="title">
<property name="font">
<font>
<pointsize>24</pointsize>
</font>
</property>
<property name="text">
<string>Flowkeeper</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="version">
<property name="text">
<string>Version</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="description">
<property name="text">
<string>Flowkeeper is an independent and free Pomodoro Technique desktop timer for power users.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="linkWebsite">
<property name="text">
<string>[Website](https://flowkeeper.org/)</string>
</property>
<property name="textFormat">
<enum>Qt::MarkdownText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="linkGithub">
<property name="text">
<string>[GitHub](https://github.com/flowkeeper-org/)</string>
</property>
<property name="textFormat">
<enum>Qt::MarkdownText</enum>
</property>
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="hs">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QTabWidget" name="tabs">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_notes">
<attribute name="title">
<string>What's new</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QTextEdit" name="notes">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_credits">
<attribute name="title">
<string>Credits</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QTextEdit" name="credits">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_license">
<attribute name="title">
<string>License</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QTextEdit" name="license">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttons">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Close</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttons</sender>
<signal>accepted()</signal>
<receiver>Dialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttons</sender>
<signal>rejected()</signal>
<receiver>Dialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>
================================================
FILE: res/core.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Flowkeeper - Pomodoro timer for power users and teams
~ Copyright (c) 2023 Constantine Kulak
~
~ This program 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.
~
~ This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>960</width>
<height>650</height>
</rect>
</property>
<property name="windowTitle">
<string>Flowkeeper</string>
</property>
<property name="windowIcon">
<iconset>
<normaloff>:/flowkeeper.png</normaloff>:/flowkeeper.png</iconset>
</property>
<widget class="QWidget" name="rootLayout">
<layout class="QVBoxLayout" name="rootLayoutInternal">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="mainLayout" native="true">
<layout class="QHBoxLayout" name="mainLayoutInternal">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="left_toolbar" native="true">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QVBoxLayout" name="left_toolbar_layout">
<item>
<widget class="QToolButton" name="toolBacklogs">
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toolTeams">
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="toolSettings">
<property name="text">
<string/>
</property>
<property name="iconSize">
<size>
<width>32</width>
<height>32</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="handleWidth">
<number>0</number>
</property>
<widget class="QWidget" name="leftTableLayout" native="true">
<layout class="QVBoxLayout" name="leftTableLayoutInternal">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
</layout>
</widget>
<widget class="QWidget" name="rightTableLayout" native="true">
<layout class="QVBoxLayout" name="rightTableLayoutInternal">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="searchBar">
<property name="spacing">
<number>0</number>
</property>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>960</width>
<height>35</height>
</rect>
</property>
<property name="nativeMenuBar">
<bool>true</bool>
</property>
</widget>
</widget>
<tabstops />
<resources/>
<connections/>
</ui>
================================================
FILE: res/icons/dark/index.theme
================================================
[Icon Theme]
Name=dark
Inherits=hicolor
Directories=24x24
[24x24]
Size=24
================================================
FILE: res/icons/light/index.theme
================================================
[Icon Theme]
Name=light
Inherits=hicolor
Directories=24x24
[24x24]
Size=24
================================================
FILE: res/icons/mixed/index.theme
================================================
[Icon Theme]
Name=mixed
Inherits=hicolor
Directories=24x24
[24x24]
Size=24
================================================
FILE: res/stats.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Flowkeeper - Pomodoro timer for power users and teams
~ Copyright (c) 2023 Constantine Kulak
~
~ This program 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.
~
~ This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
-->
<ui version="4.0">
<class>StatsWindow</class>
<widget class="QDialog" name="StatsWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1000</width>
<height>700</height>
</rect>
</property>
<property name="windowTitle">
<string>Pomodoro Health</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="statsHeader">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="topMargin">
<number>10</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QLabel" name="statsHeaderText">
<property name="text">
<string>Completed $done out of $total</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="statsHeaderSubtext">
<property name="text">
<string>Average over $from -- $to</string>
</property>
<property name="margin">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="statsMain" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="statsControls">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<item>
<layout class="QHBoxLayout" name="periodLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="topMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QToolButton" name="prev">
<property name="text">
<string><< Prev</string>
</property>
</widget>
</item>
<item>
<spacer name="hs1">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="day">
<property name="text">
<string>Day</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="week">
<property name="text">
<string>Week</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="month">
<property name="text">
<string>Month</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="month6">
<property name="text">
<string>6 Months</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="year">
<property name="text">
<string>Year</string>
</property>
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="hs2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="next">
<property name="text">
<string>Next >></string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="statsGraph"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
================================================
FILE: res/style-beach.json
================================================
{
"ICON_THEME": "mixed",
"FOCUS_TEXT_COLOR": "#ffffff",
"FOCUS_BG_COLOR": "#F038FF",
"FOCUS_BORDER_COLOR": "#F038FF",
"BORDER_COLOR": "#EEEEEE",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#E2EF70",
"SECONDARY_BG_COLOR": "#70E4EF",
"SELECTION_BG_COLOR": "#FFFFFF",
"TOOLBAR_BG_COLOR": "#3772FF",
"TOOLBAR_CHECKED_BG_COLOR": "#3772FF"
}
================================================
FILE: res/style-dark.json
================================================
{
"ICON_THEME": "dark",
"FOCUS_TEXT_COLOR": "#ffffff",
"FOCUS_BG_COLOR": "#27282e",
"FOCUS_BORDER_COLOR": "#000000",
"BORDER_COLOR": "#000000",
"TABLE_TEXT_COLOR": "#ced0d6",
"TABLE_CROSSOUT_COLOR": "#555555",
"PRIMARY_BG_COLOR": "#1e1f22",
"SECONDARY_BG_COLOR": "#2b2d30",
"SELECTION_BG_COLOR": "#43454a",
"TOOLBAR_BG_COLOR": "#44484C",
"TOOLBAR_CHECKED_BG_COLOR": "#64686C"
}
================================================
FILE: res/style-desert.json
================================================
{
"ICON_THEME": "mixed",
"FOCUS_TEXT_COLOR": "#ffffff",
"FOCUS_BG_COLOR": "#D52941",
"FOCUS_BORDER_COLOR": "#FCD581",
"BORDER_COLOR": "#FCD581",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#ffffff",
"SECONDARY_BG_COLOR": "#FFF8E8",
"SELECTION_BG_COLOR": "#FCD581",
"TOOLBAR_BG_COLOR": "#FCD581",
"TOOLBAR_CHECKED_BG_COLOR": "#990D35"
}
================================================
FILE: res/style-highlight.json
================================================
{
"ICON_THEME": "dark",
"FOCUS_TEXT_COLOR": "#FFFFFF",
"FOCUS_BG_COLOR": "#EF6306",
"FOCUS_BORDER_COLOR": "#000000",
"BORDER_COLOR": "#000000",
"TABLE_TEXT_COLOR": "#FFFFFF",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#1e1f22",
"SECONDARY_BG_COLOR": "#2b2d30",
"SELECTION_BG_COLOR": "#EF6306",
"TOOLBAR_BG_COLOR": "#44484C",
"TOOLBAR_CHECKED_BG_COLOR": "#EF6306"
}
================================================
FILE: res/style-light.json
================================================
{
"ICON_THEME": "light",
"FOCUS_TEXT_COLOR": "#000000",
"FOCUS_BG_COLOR": "#f7f8fa",
"FOCUS_BORDER_COLOR": "#d7d8da",
"BORDER_COLOR": "#d7d8da",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#ffffff",
"SECONDARY_BG_COLOR": "#f7f8fa",
"SELECTION_BG_COLOR": "#cfdefc",
"TOOLBAR_BG_COLOR": "#f7f8fa",
"TOOLBAR_CHECKED_BG_COLOR": "#dfe1e5"
}
================================================
FILE: res/style-lime.json
================================================
{
"ICON_THEME": "light",
"FOCUS_TEXT_COLOR": "#000000",
"FOCUS_BG_COLOR": "#E0FF4F",
"FOCUS_BORDER_COLOR": "#A7CC00",
"BORDER_COLOR": "#d7d8da",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#FEFFFE",
"SECONDARY_BG_COLOR": "#E0ECF5",
"SELECTION_BG_COLOR": "#A3C6E1",
"TOOLBAR_BG_COLOR": "#FF6663",
"TOOLBAR_CHECKED_BG_COLOR": "#E0FF4F"
}
================================================
FILE: res/style-mixed.json
================================================
{
"ICON_THEME": "mixed",
"FOCUS_TEXT_COLOR": "#ffffff",
"FOCUS_BG_COLOR": "#27282e",
"FOCUS_BORDER_COLOR": "#535553",
"BORDER_COLOR": "#d7d8da",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#ffffff",
"SECONDARY_BG_COLOR": "#f7f8fa",
"SELECTION_BG_COLOR": "#cfdefc",
"TOOLBAR_BG_COLOR": "#44484C",
"TOOLBAR_CHECKED_BG_COLOR": "#64686C"
}
================================================
FILE: res/style-motel.json
================================================
{
"ICON_THEME": "mixed",
"FOCUS_TEXT_COLOR": "#FFFFFF",
"FOCUS_BG_COLOR": "#343233",
"FOCUS_BORDER_COLOR": "#343233",
"BORDER_COLOR": "#FFFFFF",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#FFEEF2",
"SECONDARY_BG_COLOR": "#FFE4F3",
"SELECTION_BG_COLOR": "#FFC8FB",
"TOOLBAR_BG_COLOR": "#FF92C2",
"TOOLBAR_CHECKED_BG_COLOR": "#343233"
}
================================================
FILE: res/style-purple.json
================================================
{
"ICON_THEME": "dark",
"FOCUS_TEXT_COLOR": "#E9EDE9",
"FOCUS_BG_COLOR": "#2D2327",
"FOCUS_BORDER_COLOR": "#45364B",
"BORDER_COLOR": "#45364B",
"TABLE_TEXT_COLOR": "#E9EDE9",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#413347",
"SECONDARY_BG_COLOR": "#5A4063",
"SELECTION_BG_COLOR": "#2D2327",
"TOOLBAR_BG_COLOR": "#606880",
"TOOLBAR_CHECKED_BG_COLOR": "#2D2327"
}
================================================
FILE: res/style-resort.json
================================================
{
"ICON_THEME": "mixed",
"FOCUS_TEXT_COLOR": "#FFFFFF",
"FOCUS_BG_COLOR": "#F5AB00",
"FOCUS_BORDER_COLOR": "#F5AB00",
"BORDER_COLOR": "#FFFFFF",
"TABLE_TEXT_COLOR": "#000000",
"TABLE_CROSSOUT_COLOR": "#777777",
"PRIMARY_BG_COLOR": "#FFFFFF",
"SECONDARY_BG_COLOR": "#F7EDDE",
"SELECTION_BG_COLOR": "#2DC7FF",
"TOOLBAR_BG_COLOR": "#2DC7FF",
"TOOLBAR_CHECKED_BG_COLOR": "#0081AF"
}
================================================
FILE: res/style-template.qss
================================================
QWidget {
font-family: $FONT_MAIN_FAMILY;
font-size: $FONT_MAIN_SIZE;
}
QMainWindow, #StatsWindow, #FocusBackground {
background-color: $FOCUS_BG_COLOR;
color: $TABLE_TEXT_COLOR;
}
#headerText, #statsHeaderText {
color: $FOCUS_TEXT_COLOR;
font-family: $FONT_HEADER_FAMILY;
font-size: $FONT_HEADER_SIZE;
}
#headerSubtext, #statsHeaderSubtext {
color: $FOCUS_TEXT_COLOR;
}
#headerSubSubtext {
color: $FOCUS_TEXT_COLOR;
font-size: $FONT_SUBTEXT_SIZE;
}
#statusBar {
color: $TABLE_TEXT_COLOR;
background-color: $TOOLBAR_BG_COLOR;
}
#footerLabel {
color: $TABLE_TEXT_COLOR;
background-color: $PRIMARY_BG_COLOR;
padding: 4px;
}
#search {
padding-left: 20px;
padding-top: 3px;
padding-bottom: 3px;
background: no-repeat left url(:/icons/$ICON_THEME/24x24/tool-search.svg);
color: $TABLE_TEXT_COLOR;
background-color: $PRIMARY_BG_COLOR;
}
#left_toolbar {
color: $TABLE_TEXT_COLOR;
background-color: $TOOLBAR_BG_COLOR;
border: none;
border-right: 1px solid $BORDER_COLOR;
}
#workitems_table, #users_table, #backlogs_table, #backlogs_widget, #workitems_widget {
color: $TABLE_TEXT_COLOR;
border: none;
padding: 10px;
}
#workitems_table::item:selected, #users_table::item:selected, #backlogs_table::item:selected, #workitems_table QLineEdit {
color: $TABLE_TEXT_COLOR;
background-color: $SELECTION_BG_COLOR;
border: none;
}
#users_table, #tags_table {
border-top: 0px solid $BORDER_COLOR;
border-right: 1px solid $BORDER_COLOR;
}
#backlogs_table, #users_table, #tags_table, #backlogs_widget {
background-color: $SECONDARY_BG_COLOR;
border-right: 1px solid $BORDER_COLOR;
}
#workitems_widget, #statsView, #statsMain {
background-color: $PRIMARY_BG_COLOR;
}
#workitems_table {
background-color: $PRIMARY_BG_COLOR;
padding: 10px 10px 10px 8px;
}
QToolButton {
background: none;
border-radius: 5px;
color: $TABLE_TEXT_COLOR;
}
#toolBacklogs:checked {
background: none;
color: $TABLE_TEXT_COLOR;
}
QToolButton:checked {
background: $TOOLBAR_CHECKED_BG_COLOR;
color: $FOCUS_TEXT_COLOR;
}
QScrollBar {
height: 0px;
width: 0px;
}
QSplitter::handle {
background-color: $PRIMARY_BG_COLOR;
}
InfoOverlayContent {
background-color: #3366cc;
}
InfoOverlay #overlay_text, #prev_button {
color: #ffffff;
}
#timer {
qproperty-fg_color: $FOCUS_TEXT_COLOR;
qproperty-bg_color: $FOCUS_BG_COLOR;
}
.tag_label {
background: none;
color: $TABLE_TEXT_COLOR;
border-radius: 5px;
border: 0px solid $BORDER_COLOR;
padding: 0px 6px 0px 6px;
}
.tag_label:checked {
color: $TABLE_TEXT_COLOR;
background-color: $SELECTION_BG_COLOR;
border: none;
}
#tags_table {
padding: 10px 10px 10px 10px;
}
#work_summary_results {
font-family: "Source Code Pro", Hack, "Ubuntu Mono", "Noto Mono", "Roboto Mono", "Droid Sans Mono", "Monaco", Consolas, "Lucida Console", "Courier New", Monospace;
}
#trayDark {
background-color: #17242c;
border: 1px solid #555555;
}
#trayLight {
background-color: #d6dff3;
border: 1px solid #555555;
}
#warning {
color: red;
}
================================================
FILE: res/style-terra.json
================================================
{
"ICON_THEME": "dark",
"FOCUS_TEXT_COLOR": "#ffffff",
"FOCUS_BG_COLOR": "#2D381A",
"FOCUS_BORDER_COLOR": "#2D381A",
"BORDER_COLOR": "#2D381A",
"TABLE_TEXT_COLOR": "#ffffff",
"TABLE_CROSSOUT_COLOR": "#AAAAAA",
"PRIMARY_BG_COLOR": "#5F8349",
"SECONDARY_BG_COLOR": "#426B69",
"SELECTION_BG_COLOR": "#2D381A",
"TOOLBAR_BG_COLOR": "#2A4849",
"TOOLBAR_CHECKED_BG_COLOR": "#2D381A"
}
================================================
FILE: res/summary.ui
================================================
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>WorkSummaryWindow</class>
<widget class="QDialog" name="WorkSummaryWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>600</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Work Summary</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<layout class="QVBoxLayout" name="layout">
<property name="spacing">
<number>10</number>
</property>
<property name="leftMargin">
<number>15</number>
</property>
<property name="topMargin">
<number>15</number>
</property>
<property name="rightMargin">
<number>15</number>
</property>
<property name="bottomMargin">
<number>15</number>
</property>
<item>
<widget class="QComboBox" name="format"/>
</item>
<item>
<widget class="QComboBox" name="period"/>
</item>
<item>
<widget class="QCheckBox" name="view_time_spent">
<property name="text">
<string>Include time spent</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="view_backlogs">
<property name="text">
<string>Include information about backlogs</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="work_summary_results"/>
</item>
<item>
<widget class="QDialogButtonBox" name="buttons">
<property name="standardButtons">
<set>QDialogButtonBox::Close|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>
================================================
FILE: run-tests.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
source venv/bin/activate
PYTHONPATH=src python -m coverage run -m unittest discover -v fk.tests
python -m coverage html # --include="src/fk/core/*"
# Simple test run
# PYTHONPATH=src python -m unittest discover -v fk.tests
================================================
FILE: run.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
PYTHONPATH=src python3 -m fk.desktop.desktop "$@"
================================================
FILE: scripts/README.md
================================================
# Scripts
## Directory structure
- `.github/` - GitHub Actions pipelines definitions, not packaged
- `doc/` - Technical docs, not packaged
- `res/` - Resources, which are compiled to `src/fk/desktop/resources.py` using `generate-resources.sh`
- `icons/` - In-program icons
- `img/` -- For e2e testing only, can be stripped out
- `sound/` -- Music and WAVs, packaged
- `flowkeeper.icns` -- macOS icons, can be stripped out on Windows and Linux
- * -- all other files needs to be packaged
- `scripts/` - Build scripts for all operating systems, not packaged. See `README.md` in subdirectories.
- `src/` - PYTHONPATH for executing Flowkeeper, packaged
- `fk/`
- `core/` - The core logic, which allows writing GUI and CLI apps. Only depends on `semantic-versioning`
- `desktop/` - Qt windows, packaged
- `desktop.py` - The entry point for Flowkeeper Desktop GUI
- `e2e/` - End-to-end tests and screenshot generator scripts, not packaged
- `qt/` - Qt-specific logic, including widgets, delegates, etc. Packaged.
- `tests/` - Unit tests for `core/` module, not packaged
- `tools/` - Command-line tools, mainly for testing, not packaged
- `cli.py` - The entry point for Flowkeeper CLI, can be packaged with `core/` module
- `build/` - Temporary files created when building Flowkeeper packages, should be in `.gitignore`
- `desktop.build` - Temp dir by Nuitka, can be deleted
- `desktop.onefile.build` - Temp dir by Nuitka, can be deleted
- `desktop.dist` - A standalone package by Nuitka, to be packaged
- `Flowkeeper.bin` - The entry point for standalone build by Nuitka
- * -- all other files are libraries
- `Flowkeeper` - A one-file portable binary by Nuitka, to be packaged
- `dist/` - Resulting Flowkeeper building artifacts, should be in `.gitignore`
- `standalone/` - Standalone build package, depending on the OS and compiler, which can be zipped
- `flowkeeper-x.y.z-macOS-latest-nuitka-installer.dmg` - DMG built with Nuitka
- `flowkeeper-x.y.z-macOS-latest-pyinstaller-installer.dmg` - DMG built with PyInstaller
- `flowkeeper-x.y.z-macOS-latest-nuitka-portable` - macOS portable binary
- `flowkeeper-x.y.z-windows-latest-nuitka-installer.exe` - Windows installer built with Nuitka
- `flowkeeper-x.y.z-windows-latest-nuitka-portable.exe` - Windows portable EXE built with Nuitka
- `flowkeeper-x.y.z-windows-latest-pyinstaller-installer.exe` - Windows installer built with PyInstaller
- `flowkeeper-x.y.z-windows-latest-pyinstaller-portable.exe` - Windows portable EXE built with PyInstaller
- `flowkeeper-x.y.z-ubuntu-latest-nuitka-min-package.deb`
- `flowkeeper-x.y.z-ubuntu-latest-nuitka-package.deb`
- `flowkeeper-x.y.z-ubuntu-latest-nuitka-portable`
- `flowkeeper-x.y.z-ubuntu-latest-pyinstaller-min-package.deb`
- `flowkeeper-x.y.z-ubuntu-latest-pyinstaller-package.deb`
- `flowkeeper-x.y.z-ubuntu-latest-pyinstaller-portable`
- `venv/` - Virtual env for building purposes, in `.gitignore`
- `README.md` - The main README file, not packaged
- `requirements.txt` - Requirements file for running, building and testing Flowkeeper, not packaged
- `run.sh` - A convenience shell script for running Flowkeeper in dev environment, not packaged
- `run-tests.sh` - A convenience shell script for unit tests, not packaged
- `LICENSE` - GPLv3 license file
## Installing dependencies for build
All operating systems:
- Install Git
- Install Python 3.11 for Qt 6.7, or 3.12+ for 6.8
Windows:
- Install InnoSetup: `scripts/windows/install-innosetup.sh`
macOS:
- Install create-dmg utility and provision certificates for signing code. This requires env
variables and secrets only available in GitHub Actions. When building locally you don't
have to run `install-certificates.sh`.
```bash
scripts/macos/install-create-dmg.sh
scripts/macos/install-certificates.sh
```
Linux:
Install AppImage Tool:
```bash
scripts/linux/appimage/install-appimage.sh
```
## Building
Note that all commands here are Bash. On Windows you have to use Git Bash, not WSL. Otherwise,
build scripts won't be able to detect Windows environment.
### Create a virtual environment and install Python requirements:
Linux and macOS:
```bash
python3 -m venv venv
source venv/bin/activate
pip install -r requirements-build.txt
```
Windows:
```bash
python -m venv venv
venv/Scripts/activate
pip install -r requirements-build.txt
```
### Generate resources
This will create `src/fk/desktop/resources.py` and `flowkeeper.icns` for macOS.
```bash
scripts/common/generate-resources.sh
```
### Create binary packages
TODO: Complete this section
With Nuitka: ``
With PyInstaller: ``
================================================
FILE: scripts/bsd/README.md
================================================
# Building for FreeBSD
We successfully built and tested Flowkeeper on FreeBSD:
```
pkg install python3 git devel/pyside6 devel/pyside6-tools py311-keyring py311-semantic-version
git clone https://github.com/flowkeeper-org/fk-desktop.git
cd fk-desktop/res
/usr/local/bin/pyside6/rcc --project -o resources.qrc
/usr/local/bin/pyside6/rcc -g python resources.qrc -o ../src/fk/desktop/resources.py
cd ..
PYTHONPATH=src python3.11 -m fk.desktop.desktop
```
Tested with:
- KDE 5.27.11 on X11
- FreeBSD 14.1 RELEASE
- Flowkeeper v1.0.0
- Python 3.11.11
- Qt 6.8.2 (xcb)
================================================
FILE: scripts/common/generate-resources.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
source venv/bin/activate
set -e
if [[ "$OSTYPE" == "msys" ]]; then
alias "pyside6-rcc=$(pwd)/venv/Lib/site-packages/PySide6/rcc"
elif [[ "$OSTYPE" == "darwin"* ]]; then
scripts/macos/create-icons.sh
echo "Generated icns file for macOS"
ls -al
fi
cd res
qrc="resources.qrc"
pyside6-rcc --project -o "$qrc"
pyside6-rcc -g python "$qrc" -o "../src/fk/desktop/resources.py"
rm "$qrc"
================================================
FILE: scripts/common/get-version.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
VERSION_REGEX='^### v(.+) \(.*$'
VERSION_LINE=$(head --lines=1 res/CHANGELOG.txt)
if [[ $VERSION_LINE =~ $VERSION_REGEX ]]; then
echo "${BASH_REMATCH[1]}"
else
echo "N/A"
fi
================================================
FILE: scripts/common/pyinstaller/entitlements.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
</dict>
</plist>
================================================
FILE: scripts/common/pyinstaller/normal.spec
================================================
# -*- mode: python ; coding: utf-8 -*-
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--sign", action="store_true")
options = parser.parse_args()
a = Analysis(
['../../../src/fk/desktop/desktop.py'],
pathex=[],
binaries=[],
datas=[],
hiddenimports=[],
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
[],
exclude_binaries=True,
name='Flowkeeper',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=('Developer ID Application: Constantine Kulak (ELWZ9S676C)' if options.sign else None),
entitlements_file=('/Users/runner/work/fk-desktop/fk-desktop/scripts/common/pyinstaller/entitlements.plist' if options.sign else None),
icon=['../../../res/flowkeeper.ico'],
)
coll = COLLECT(
exe,
a.binaries,
a.datas,
strip=False,
upx=True,
upx_exclude=[],
name='flowkeeper',
)
app = BUNDLE(
coll,
name='Flowkeeper.app',
icon='../../../flowkeeper.icns',
bundle_identifier='org.flowkeeper.Flowkeeper',
)
================================================
FILE: scripts/common/pyinstaller/portable.spec
================================================
# -*- mode: python ; coding: utf-8 -*-
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
from PyInstaller.utils.hooks import collect_all
datas = []
binaries = []
hiddenimports = []
tmp_ret = collect_all('fk')
datas += tmp_ret[0]; binaries += tmp_ret[1]; hiddenimports += tmp_ret[2]
a = Analysis(
['../../../src/fk/desktop/desktop.py'],
pathex=['../../../src'],
binaries=binaries,
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=True,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[('v', None, 'OPTION')],
name='Flowkeeper',
debug=True,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
icon=['../../../res/flowkeeper.ico'],
)
================================================
FILE: scripts/linux/appimage/install-appimage.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
# AppImage installer
sudo wget "https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage" -O /usr/local/bin/appimagetool
sudo chmod +x /usr/local/bin/appimagetool
================================================
FILE: scripts/linux/appimage/package-appimage.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
# In the next version(s) think of installing it in /opt/Flowkeeper instead
# 1. Prepare temp folder
cd build
rm -rf AppDir
mkdir AppDir
echo "1. Prepared temp folder"
# 2. Copy application files
mkdir -p AppDir/usr/lib/flowkeeper
mkdir -p AppDir/usr/share/icons/hicolor/{48x48,1024x1024}/apps
mkdir -p AppDir/usr/share/metainfo
mkdir -p AppDir/usr/share/applications
cp -r ../dist/standalone/* AppDir/usr/lib/flowkeeper/
cp ../res/flowkeeper.png AppDir/usr/share/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png
cp ../flowkeeper-48x48.png AppDir/usr/share/icons/hicolor/48x48/apps/org.flowkeeper.Flowkeeper.png
cp ../scripts/linux/common/org.flowkeeper.Flowkeeper.metainfo.xml AppDir/usr/share/metainfo/org.flowkeeper.Flowkeeper.appdata.xml
echo "2. Copied application files"
# 3. Create a desktop shortcut
export FK_AUTOSTART_ARGS=""
< ../scripts/linux/common/org.flowkeeper.Flowkeeper.desktop envsubst > AppDir/usr/share/applications/org.flowkeeper.Flowkeeper.desktop
cd AppDir
ln -s usr/share/applications/org.flowkeeper.Flowkeeper.desktop org.flowkeeper.Flowkeeper.desktop
echo "3. Created a desktop shortcut:"
cat org.flowkeeper.Flowkeeper.desktop
cd ..
# 4. Create AppRun symlink
cd AppDir
ln -s ./usr/lib/flowkeeper/Flowkeeper ./AppRun
cd ..
echo "4. Created AppRun symlink"
# 5. Create .DirIcon symlink
cd AppDir
ln -s usr/share/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png ./.DirIcon
ln -s usr/share/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png ./org.flowkeeper.Flowkeeper.png
cd ..
echo "5. Create .DirIcon symlink"
# 6. Build AppImage file
ls -al AppDir/
appimagetool AppDir
echo "6. Built AppImage file: $(ls ./*.AppImage)"
mv ./*.AppImage ../dist
================================================
FILE: scripts/linux/common/flowkeeper
================================================
#!/bin/bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
PYTHONPATH=/usr/lib/flowkeeper:/usr/libexec/flowkeeper python3 -m fk.desktop.desktop $@
================================================
FILE: scripts/linux/common/org.flowkeeper.Flowkeeper.desktop
================================================
[Desktop Entry]
Name=Flowkeeper
Comment=Pomodoro Technique desktop timer for power users
Exec=/usr/bin/flowkeeper $FK_AUTOSTART_ARGS
Icon=org.flowkeeper.Flowkeeper
Terminal=false
Type=Application
Categories=Utility;
================================================
FILE: scripts/linux/common/org.flowkeeper.Flowkeeper.metainfo.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<component type="desktop-application">
<id>org.flowkeeper.Flowkeeper</id>
<name>Flowkeeper</name>
<summary>Pomodoro timer for power users</summary>
<url type="homepage">https://flowkeeper.org</url>
<metadata_license>MIT</metadata_license>
<project_license>GPL-3.0-only</project_license>
<description>
<p>
Flowkeeper is a Pomodoro timer with a "classic" cross-platform UI paradigm (desktop-first, no Electron).
With its keyboard shortcuts and advanced settings, Flowkeeper is optimized for power users. It stays as
close as possible to the Pomodoro Technique definition and format from the original book by Francesco
Cirillo.
</p>
</description>
<launchable type="desktop-id">org.flowkeeper.Flowkeeper.desktop</launchable>
<screenshots>
<screenshot type="default">
<image>https://flowkeeper.org/images/releases/v1.0.1/Linux/fulls/19-main-dark-window-border.png</image>
</screenshot>
<screenshot>
<image>https://flowkeeper.org/images/releases/v1.0.1/Linux/fulls/14-stats-month-window-border.png</image>
</screenshot>
<screenshot>
<image>https://flowkeeper.org/images/releases/v1.0.1/Linux/fulls/16-work-summary-window-border.png</image>
</screenshot>
</screenshots>
<branding>
<color type="primary" scheme_preference="light">#f6f5f4</color>
<color type="primary" scheme_preference="dark">#241f31</color>
</branding>
<provides>
<binary>flowkeeper</binary>
</provides>
<releases>
<release version="1.0.2" date="2025-09-26">
<description>
<p>Bugfix: Can't void a pomodoro or record an interruption in non-latest backlogs (#199, #195).</p>
<p>Bugfix: An occasional exception when waking up from sleep (#197).</p>
</description>
</release>
</releases>
<developer id="org.flowkeeper">
<name>Flowkeeper Team</name>
</developer>
<content_rating type="oars-1.0" />
</component>
================================================
FILE: scripts/linux/debian/debian-control
================================================
Package: flowkeeper
Version: $FK_VERSION
Maintainer: Constantine Kulak
Architecture: amd64
Description: Flowkeeper is a free Pomodoro Technique desktop timer for power users.
================================================
FILE: scripts/linux/debian/debian-control-min
================================================
Package: flowkeeper
Version: $FK_VERSION
Maintainer: Constantine Kulak
Architecture: amd64
Description: Flowkeeper is a free Pomodoro Technique desktop timer for power users.
Depends: python3-pyside6.qtcore(>= 6.7.0), python3-pyside6.qtwidgets(>= 6.7.0), python3-pyside6.qtgui(>= 6.7.0), python3-pyside6.qtnetwork(>= 6.7.0), python3-pyside6.qtnetworkauth(>= 6.7.0), python3-pyside6.qtmultimedia(>= 6.7.0), python3-pyside6.qtsvg(>= 6.7.0), python3-pyside6.qtwebsockets(>= 6.7.0), python3-pyside6.qtuitools(>= 6.7.0), python3-pyside6.qtasyncio(>= 6.7.0), python3-pyside6.qtcharts(>= 6.7.0), python3-semantic-version, python3-cryptography, python3-keyring
================================================
FILE: scripts/linux/debian/package-deb-min.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
# In the next version(s) think of installing it in /opt/Flowkeeper instead
# 1. Get the version
echo "1. Version = $FK_VERSION"
# 2. Prepare temp folder
dist="build/deb"
rm -rf "$dist"
mkdir "$dist"
echo "2. Prepared temp folder"
# 3. Copy application files
mkdir -p "$dist/usr/lib/flowkeeper"
cp -r src/* "$dist/usr/lib/flowkeeper/"
mkdir -p "$dist/usr/share/icons/hicolor/1024x1024/apps"
mkdir -p "$dist/usr/share/icons/hicolor/48x48/apps"
cp res/flowkeeper.png "$dist/usr/share/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png"
cp flowkeeper-48x48.png "$dist/usr/share/icons/hicolor/48x48/apps/org.flowkeeper.Flowkeeper.png"
mkdir -p "$dist/usr/bin"
cp scripts/linux/common/flowkeeper "$dist/usr/bin/flowkeeper"
echo "3. Copied application files"
# 4. Create a desktop shortcut
mkdir -p "$dist/usr/share/applications"
export FK_AUTOSTART_ARGS=""
< scripts/linux/common/org.flowkeeper.Flowkeeper.desktop envsubst > "$dist/usr/share/applications/org.flowkeeper.Flowkeeper.desktop"
echo "4. Created a desktop shortcut:"
cat "$dist/usr/share/applications/org.flowkeeper.Flowkeeper.desktop"
# 5. Create another one for autostart (with --autostart argument)
mkdir -p "$dist/etc/xdg/autostart"
export FK_AUTOSTART_ARGS="--autostart"
< scripts/linux/common/org.flowkeeper.Flowkeeper.desktop envsubst > "$dist/etc/xdg/autostart/org.flowkeeper.Flowkeeper.desktop"
echo "5. Added it to autostart"
# 6. Create metadata
mkdir "$dist/DEBIAN"
< scripts/linux/debian/debian-control-min envsubst > "$dist/DEBIAN/control"
echo "6. Created metadata"
cat "$dist/DEBIAN/control"
# 7. Build DEB file
dpkg-deb --build "$dist" dist/flowkeeper-min.deb
echo "7. Built DEB file"
================================================
FILE: scripts/linux/debian/package-deb.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
# In the next version(s) think of installing it in /opt/Flowkeeper instead
# 1. Get the version
echo "1. Version = $FK_VERSION"
# 2. Prepare temp folder
cd build
rm -rf deb
mkdir deb
echo "2. Prepared temp folder"
# 3. Copy application files
mkdir -p deb/usr/lib/flowkeeper
mkdir -p deb/usr/share/icons/hicolor/{48x48,1024x1024}/apps
cp -r ../dist/standalone/* deb/usr/lib/flowkeeper/
cp ../res/flowkeeper.png deb/usr/share/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png
cp ../flowkeeper-48x48.png deb/usr/share/icons/hicolor/48x48/apps/org.flowkeeper.Flowkeeper.png
echo "3. Copied application files"
# 4. Create a desktop shortcut
mkdir -p deb/usr/share/applications
export FK_AUTOSTART_ARGS=""
< ../scripts/linux/common/org.flowkeeper.Flowkeeper.desktop envsubst > deb/usr/share/applications/org.flowkeeper.Flowkeeper.desktop
echo "4. Created a desktop shortcut:"
cat deb/usr/share/applications/org.flowkeeper.Flowkeeper.desktop
# 5. Create another one for autostart (with --autostart argument)
mkdir -p deb/etc/xdg/autostart
export FK_AUTOSTART_ARGS="--autostart"
< ../scripts/linux/common/org.flowkeeper.Flowkeeper.desktop envsubst > deb/etc/xdg/autostart/org.flowkeeper.Flowkeeper.desktop
echo "5. Added it to autostart"
# 6. Create a relative symlink in /usr/bin
mkdir -p deb/usr/bin
cd deb/usr/bin
ln -s ../lib/flowkeeper/Flowkeeper ./flowkeeper
cd ../../..
echo "6. Create a relative symlink in /usr/bin"
# 7. Create metadata
mkdir deb/DEBIAN
< ../scripts/linux/debian/debian-control envsubst > deb/DEBIAN/control
echo "7. Created metadata"
cat deb/DEBIAN/control
# 8. Build DEB file
dpkg-deb --build deb ../dist/flowkeeper.deb
echo "8. Built DEB file"
================================================
FILE: scripts/linux/flatpak/README.md
================================================
# Flowkeeper in Flatpak
- End-user link: https://flathub.org/apps/org.flowkeeper.Flowkeeper
- Fork repo: https://github.com/flowkeeper-org/org.flowkeeper.Flowkeeper
- Upstream repo: https://github.com/flathub/org.flowkeeper.Flowkeeper
## Build locally
```shell
flatpak-builder --force-clean --user --install-deps-from=flathub --repo=repo --install builddir org.flowkeeper.Flowkeeper.yaml
flatpak run org.flowkeeper.Flowkeeper
flatpak uninstall org.flowkeeper.Flowkeeper
```
Update the fork repo and open a PR to upstream. Wait till the build pipeline passes.
================================================
FILE: scripts/linux/obs/README.md
================================================
# Updating OBS build
Step 1: Download the latest tar.gz
Step 2: Update `flowkeeper.spec` if needed
Step 3: Update the changelog
Step 4: Commit the changes:
```bash
osc delete <old.tar.gz>
osc add *
osc commit
```
Step 5: Check the build job status
Step 6: Test it: `sudo zypper install flowkeeper`
================================================
FILE: scripts/linux/obs/obs.spec
================================================
#
# spec file for package flowkeeper
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
Name: flowkeeper
Version: 1.0.0
Release: 0
Summary: Pomodoro Technique desktop timer for power users
License: GPL-3.0-only
Group: Productivity/Text/Utilities
URL: https://flowkeeper.org/
Source0: https://github.com/flowkeeper-org/fk-desktop/release/fk-desktop-%{version}.tar.gz
BuildRequires: python3-pyside6
BuildRequires: python3-semantic_version
BuildRequires: python3-cryptography
BuildRequires: python3-keyring
Requires: python3-pyside6
Requires: python3-semantic_version
Requires: python3-cryptography
Requires: python3-keyring
BuildArch: noarch
%description
Flowkeeper is a Pomodoro timer with a "classic" cross-platform UI paradigm
(desktop-first, no Electron). With its keyboard shortcuts and advanced settings,
Flowkeeper is optimized for power users. It stays as close as possible to the
Pomodoro Technique definition and format from the original book by Francesco
Cirillo.
Flowkeeper stores data in $XDG_DATA_HOME/Flowkeeper.
%prep
%setup -q -n "fk-desktop-%{version}"
%build
cd res
qrc="resources.qrc"
/usr/libexec/qt6/rcc --project -o "$qrc"
/usr/libexec/qt6/rcc -g python "$qrc" -o "../src/fk/desktop/resources.py"
rm "$qrc"
%install
mkdir -p "%{buildroot}%{_libexecdir}/flowkeeper"
cp -r src/* "%{buildroot}%{_libexecdir}/flowkeeper/"
mkdir -p "%{buildroot}%{_datadir}/icons/hicolor/1024x1024/apps"
mkdir -p "%{buildroot}%{_datadir}/icons/hicolor/48x48/apps"
cp -av res/flowkeeper.png "%{buildroot}%{_datadir}/icons/hicolor/1024x1024/apps/org.flowkeeper.Flowkeeper.png"
cp -av flowkeeper-48x48.png "%{buildroot}%{_datadir}/icons/hicolor/48x48/apps/org.flowkeeper.Flowkeeper.png"
mkdir -p "%{buildroot}%{_bindir}"
cp -av installer/flowkeeper "%{buildroot}%{_bindir}/flowkeeper"
echo "3. Copied application files"
mkdir -p "%{buildroot}%{_datadir}/applications"
export FK_AUTOSTART_ARGS=""
< installer/org.flowkeeper.Flowkeeper.desktop envsubst > "%{buildroot}%{_datadir}/applications/org.flowkeeper.Flowkeeper.desktop"
%check
%files
%doc README.md
%license LICENSE
%{_datadir}/applications/org.flowkeeper.Flowkeeper.desktop
%{_datadir}/icons/hicolor/
%{_libexecdir}/flowkeeper/
%{_bindir}/flowkeeper
%changelog
-------------------------------------------------------------------
Mon Feb 17 15:46:00 UTC 2025 - Constantine Kulak <contact@flowkeeper.org>
- Ability to track unfocused time, try to start a work item with no pomodoros (#94, #98).
- Ability to drag work items between backlogs (#60).
- Voided pomodoros are displayed as ticks, and completed ones are displayed as crosses to better match the Book (#41, #92).
- Hovering over pomodoros displays a detailed log of your work (#93).
- Flowkeeper window now hides automatically on auto-start (#102).
- Standard data and log directories are used on Linux, macOS and Windows (#65).
- Import from CSV, try Ctrl+I (#125).
- You can now find Flowkeeper on Flathub (#63).
- We now build Flowkeeper for ARM (arm64 / aarch64).
- We now build an AppImage binary for Flowkeeper.
- "Contact us" submenu to facilitate feedback collection (#111).
- [Technical] Added support for Qt 6.8.1.
- [Bugfix] Window icon on Wayland (#110).
================================================
FILE: scripts/linux/package-nuitka.sh
================================================
#!/usr/bin/env bash
#
# Flowkeeper - Pomodoro timer for power users and teams
# Copyright (c) 2023 Constantine Kulak
#
# This program 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.
#
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#
set -e
source venv/bin/activate
FK_VERSION=$(scripts/common/get-version.sh)
PYTHONPATH=src python3 -m nuitka \
--onefile \
--enable-plugin=pyside6 \
--include-qt-plugins=multimedia \
--product-name=Flowkeeper \
--product-version="$FK_VERSION" \
--output-dir=build \
--outp
gitextract_0tdm8aod/ ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ ├── build.yml │ ├── main.yml │ ├── manual-all.yml │ ├── manual-one.yml │ └── release.yml ├── .gitignore ├── .idea/ │ ├── .gitignore │ ├── dictionaries/ │ │ └── project.xml │ ├── flowkeeper-python.iml │ ├── inspectionProfiles/ │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── vcs.xml │ └── watcherTasks.xml ├── LICENSE ├── README.md ├── TODO.md ├── doc/ │ ├── actions.md │ ├── build-alpine.md │ ├── data-model.md │ ├── design.md │ ├── events.md │ ├── pipeline.md │ ├── release.md │ └── strategies.md ├── requirements-test.txt ├── requirements.txt ├── res/ │ ├── CHANGELOG.txt │ ├── CREDITS.txt │ ├── LICENSE.txt │ ├── about.ui │ ├── core.ui │ ├── icons/ │ │ ├── dark/ │ │ │ └── index.theme │ │ ├── light/ │ │ │ └── index.theme │ │ └── mixed/ │ │ └── index.theme │ ├── sound/ │ │ └── Madelene.m4a │ ├── stats.ui │ ├── style-beach.json │ ├── style-dark.json │ ├── style-desert.json │ ├── style-highlight.json │ ├── style-light.json │ ├── style-lime.json │ ├── style-mixed.json │ ├── style-motel.json │ ├── style-purple.json │ ├── style-resort.json │ ├── style-template.qss │ ├── style-terra.json │ └── summary.ui ├── run-tests.sh ├── run.sh ├── scripts/ │ ├── README.md │ ├── bsd/ │ │ └── README.md │ ├── common/ │ │ ├── generate-resources.sh │ │ ├── get-version.sh │ │ └── pyinstaller/ │ │ ├── entitlements.plist │ │ ├── normal.spec │ │ └── portable.spec │ ├── linux/ │ │ ├── appimage/ │ │ │ ├── install-appimage.sh │ │ │ └── package-appimage.sh │ │ ├── common/ │ │ │ ├── flowkeeper │ │ │ ├── org.flowkeeper.Flowkeeper.desktop │ │ │ └── org.flowkeeper.Flowkeeper.metainfo.xml │ │ ├── debian/ │ │ │ ├── debian-control │ │ │ ├── debian-control-min │ │ │ ├── package-deb-min.sh │ │ │ └── package-deb.sh │ │ ├── flatpak/ │ │ │ └── README.md │ │ ├── obs/ │ │ │ ├── README.md │ │ │ └── obs.spec │ │ ├── package-nuitka.sh │ │ └── rpm/ │ │ └── package-rpm-min.sh │ ├── macos/ │ │ ├── create-dmg.sh │ │ ├── create-icons.sh │ │ ├── install-certificates.sh │ │ ├── install-create-dmg.sh │ │ ├── notarize-dmg.sh │ │ ├── package-macos-pkg.sh │ │ └── package-nuitka.sh │ └── windows/ │ ├── generate-resources-windows.sh │ ├── install-innosetup.sh │ ├── package-installer.sh │ └── windows-installer.iss ├── src/ │ └── fk/ │ ├── __init__.py │ ├── core/ │ │ ├── __init__.py │ │ ├── abstract_cryptograph.py │ │ ├── abstract_data_container.py │ │ ├── abstract_data_item.py │ │ ├── abstract_event_emitter.py │ │ ├── abstract_event_source.py │ │ ├── abstract_filesystem_watcher.py │ │ ├── abstract_serializer.py │ │ ├── abstract_settings.py │ │ ├── abstract_strategy.py │ │ ├── abstract_timer.py │ │ ├── abstract_timer_display.py │ │ ├── backlog.py │ │ ├── backlog_strategies.py │ │ ├── ephemeral_event_source.py │ │ ├── event_source_factory.py │ │ ├── event_source_holder.py │ │ ├── events.py │ │ ├── fernet_cryptograph.py │ │ ├── file_event_source.py │ │ ├── import_export.py │ │ ├── integration_executor.py │ │ ├── interruption.py │ │ ├── mock_settings.py │ │ ├── no_cryptograph.py │ │ ├── pomodoro.py │ │ ├── pomodoro_strategies.py │ │ ├── sandbox.py │ │ ├── simple_serializer.py │ │ ├── strategy_factory.py │ │ ├── tag.py │ │ ├── tags.py │ │ ├── tenant.py │ │ ├── timer.py │ │ ├── timer_data.py │ │ ├── timer_strategies.py │ │ ├── user.py │ │ ├── user_strategies.py │ │ ├── workitem.py │ │ └── workitem_strategies.py │ ├── desktop/ │ │ ├── __init__.py │ │ ├── application.py │ │ ├── config_wizard.py │ │ ├── desktop.py │ │ ├── desktop_strategies.py │ │ ├── export_wizard.py │ │ ├── import_wizard.py │ │ ├── interruption_dialog.py │ │ ├── settings.py │ │ ├── stats_window.py │ │ ├── tutorial.py │ │ └── work_summary_window.py │ ├── e2e/ │ │ ├── __init__.py │ │ ├── abstract_e2e_test.py │ │ ├── all-tests.json │ │ ├── backlog_e2e.py │ │ ├── screenshot.py │ │ └── screenshots_e2e.py │ ├── qt/ │ │ ├── __init__.py │ │ ├── about_window.py │ │ ├── abstract_drop_model.py │ │ ├── abstract_item_delegate.py │ │ ├── abstract_tableview.py │ │ ├── actions.py │ │ ├── app_version.py │ │ ├── audio_player.py │ │ ├── backlog_model.py │ │ ├── backlog_tableview.py │ │ ├── backlog_widget.py │ │ ├── configurable_toolbar.py │ │ ├── connection_widget.py │ │ ├── flow_layout.py │ │ ├── focus_widget.py │ │ ├── heartbeat.py │ │ ├── info_overlay.py │ │ ├── oauth.py │ │ ├── pomodoro_delegate.py │ │ ├── progress_widget.py │ │ ├── qt_filesystem_watcher.py │ │ ├── qt_invoker.py │ │ ├── qt_settings.py │ │ ├── qt_timer.py │ │ ├── render/ │ │ │ ├── __init__.py │ │ │ ├── abstract_timer_renderer.py │ │ │ ├── classic_timer_renderer.py │ │ │ └── minimal_timer_renderer.py │ │ ├── resize_event_filter.py │ │ ├── search_completer.py │ │ ├── tags_widget.py │ │ ├── theme_change_event_filter.py │ │ ├── threaded_event_source.py │ │ ├── timer_widget.py │ │ ├── tray_icon.py │ │ ├── user_model.py │ │ ├── user_tableview.py │ │ ├── websocket_event_source.py │ │ ├── workitem_model.py │ │ ├── workitem_state_delegate.py │ │ ├── workitem_tableview.py │ │ ├── workitem_text_delegate.py │ │ └── workitem_widget.py │ ├── tests/ │ │ ├── __init__.py │ │ ├── abstract_test_case.py │ │ ├── data_generator.py │ │ ├── fixtures/ │ │ │ ├── random-dump.txt │ │ │ ├── random.txt │ │ │ └── test-tags.txt │ │ ├── test_backlogs.py │ │ ├── test_events.py │ │ ├── test_file_event_source.py │ │ ├── test_import_export.py │ │ ├── test_pomodoros.py │ │ ├── test_settings.py │ │ ├── test_tags.py │ │ ├── test_users.py │ │ ├── test_utils.py │ │ └── test_workitems.py │ └── tools/ │ ├── __init__.py │ ├── cli.py │ ├── minimal_actions.py │ ├── minimal_audio.py │ ├── minimal_auth.py │ ├── minimal_backlogs.py │ ├── minimal_common.py │ ├── minimal_focus.py │ ├── minimal_settings.py │ ├── minimal_timer_widget.py │ ├── minimal_tray.py │ ├── minimal_tutorial.py │ ├── minimal_update.py │ ├── minimal_users.py │ └── minimal_workitems.py └── ws-tests.md
SYMBOL INDEX (1603 symbols across 114 files)
FILE: src/fk/core/abstract_cryptograph.py
class AbstractCryptograph (line 24) | class AbstractCryptograph(ABC):
method __init__ (line 29) | def __init__(self, settings: AbstractSettings):
method _generate_key (line 37) | def _generate_key(self) -> None:
method _on_setting_changed (line 44) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _on_key_changed (line 51) | def _on_key_changed(self) -> None:
method encrypt (line 55) | def encrypt(self, s: str) -> str:
method decrypt (line 59) | def decrypt(self, s: str) -> str:
FILE: src/fk/core/abstract_data_container.py
class AbstractDataContainer (line 25) | class AbstractDataContainer(AbstractDataItem[TParent], Generic[TChild, T...
method __init__ (line 30) | def __init__(self,
method __getitem__ (line 40) | def __getitem__(self, uid: str) -> TChild:
method __contains__ (line 43) | def __contains__(self, uid: str):
method __setitem__ (line 46) | def __setitem__(self, uid: str, value: TChild):
method __delitem__ (line 51) | def __delitem__(self, uid: str):
method __iter__ (line 56) | def __iter__(self) -> Iterable[str]:
method __len__ (line 60) | def __len__(self):
method values (line 63) | def values(self) -> list[TChild]:
method keys (line 66) | def keys(self) -> Iterable[str]:
method names (line 70) | def names(self) -> list[str]:
method get_name (line 73) | def get_name(self) -> str:
method set_name (line 76) | def set_name(self, new_name: str) -> None:
method move_child (line 79) | def move_child(self, child: TChild, index_to: int) -> None:
method get (line 84) | def get(self, key: str, default: TChild = None) -> TChild:
method supports_children (line 90) | def supports_children(self) -> bool:
method dump (line 93) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method to_dict (line 103) | def to_dict(self) -> dict:
FILE: src/fk/core/abstract_data_item.py
function generate_uid (line 26) | def generate_uid() -> str:
function generate_unique_name (line 30) | def generate_unique_name(prefix: str, names: Iterable) -> str:
class AbstractDataItem (line 43) | class AbstractDataItem(ABC, Generic[TParent]):
method __init__ (line 49) | def __init__(self,
method get_uid (line 58) | def get_uid(self) -> str:
method get_owner (line 62) | def get_owner(self) -> 'User':
method get_parent (line 66) | def get_parent(self) -> TParent:
method dump (line 69) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method to_dict (line 80) | def to_dict(self) -> dict:
method get_create_date (line 87) | def get_create_date(self) -> datetime.datetime:
method get_last_modified_date (line 90) | def get_last_modified_date(self) -> datetime.datetime:
method item_updated (line 94) | def item_updated(self, date: datetime.datetime = None):
method supports_children (line 104) | def supports_children(self) -> bool:
method change_parent (line 107) | def change_parent(self, new_parent: TParent) -> None:
FILE: src/fk/core/abstract_event_emitter.py
function _callback_display (line 26) | def _callback_display(callback) -> str:
class AbstractEventEmitter (line 33) | class AbstractEventEmitter:
method __init__ (line 41) | def __init__(self, allowed_events: list[str], callback_invoker: Callab...
method on (line 56) | def on(self, event_pattern: str, callback: Callable, last: bool = Fals...
method cancel (line 70) | def cancel(self, event_pattern: str) -> None:
method unsubscribe (line 77) | def unsubscribe(self, callback: Callable) -> None:
method unsubscribe_one (line 85) | def unsubscribe_one(self, callback: Callable, event_pattern: str) -> N...
method _emit (line 94) | def _emit(self, event: str, params: dict[str, any], carry: any = None)...
method _is_muted (line 110) | def _is_muted(self) -> bool:
method unmute (line 113) | def unmute(self) -> None:
method mute (line 117) | def mute(self) -> None:
FILE: src/fk/core/abstract_event_source.py
class AbstractEventSource (line 45) | class AbstractEventSource(AbstractEventEmitter, ABC, Generic[TRoot]):
method __init__ (line 55) | def __init__(self,
method get_data (line 125) | def get_data(self) -> TRoot:
method get_name (line 130) | def get_name(self) -> str:
method get_config_parameter (line 133) | def get_config_parameter(self, name: str) -> str:
method set_config_parameters (line 136) | def set_config_parameters(self, values: dict[str, str]) -> None:
method _append (line 142) | def _append(self, strategies: list[AbstractStrategy[TRoot]]) -> None:
method start (line 147) | def start(self, mute_events: bool = True) -> None:
method _auto_seal_at_the_end (line 150) | def _auto_seal_at_the_end(self, last_executed: AbstractStrategy) -> None:
method _auto_seal (line 160) | def _auto_seal(self, strategy: AbstractStrategy[TRoot], second_time: b...
method execute_prepared_strategy (line 191) | def execute_prepared_strategy(self,
method execute (line 217) | def execute(self,
method users (line 237) | def users(self) -> Iterable[User]:
method backlogs (line 241) | def backlogs(self) -> Iterable[Backlog]:
method tags (line 246) | def tags(self) -> Iterable[Tag]:
method workitems (line 251) | def workitems(self) -> Iterable[Workitem]:
method find_workitem (line 256) | def find_workitem(self, uid: str) -> Workitem | None:
method find_backlog (line 261) | def find_backlog(self, uid: str) -> Backlog | None:
method find_tag (line 266) | def find_tag(self, uid: str) -> Tag | None:
method find_user (line 271) | def find_user(self, identity: str) -> User | None:
method pomodoros (line 276) | def pomodoros(self) -> Iterable[Pomodoro]:
method clone (line 282) | def clone(self, new_root: TRoot) -> AbstractEventSource[TRoot]:
method _sequence_error (line 285) | def _sequence_error(self, prev: int, next_: int) -> None:
method disconnect (line 291) | def disconnect(self):
method get_settings (line 294) | def get_settings(self) -> AbstractSettings:
method send_ping (line 298) | def send_ping(self) -> str | None:
method can_connect (line 302) | def can_connect(self):
method repair (line 306) | def repair(self) -> tuple[list[str], str | None]:
method connect (line 309) | def connect(self):
method get_init_strategy (line 312) | def get_init_strategy(self, emit: Callable[[str, dict[str, any], any],...
method get_last_sequence (line 319) | def get_last_sequence(self):
function start_workitem (line 326) | def start_workitem(workitem: Workitem, source: AbstractEventSource) -> N...
FILE: src/fk/core/abstract_filesystem_watcher.py
class AbstractFilesystemWatcher (line 20) | class AbstractFilesystemWatcher(ABC):
method watch (line 22) | def watch(self, filename: str, callback: Callable[[str], None]):
method unwatch (line 26) | def unwatch(self, filename: str) -> None:
method unwatch_all (line 30) | def unwatch_all(self) -> None:
FILE: src/fk/core/abstract_serializer.py
function sanitize_user_input (line 27) | def sanitize_user_input(s: str) -> str:
class AbstractSerializer (line 31) | class AbstractSerializer(ABC, Generic[T, TRoot]):
method __init__ (line 35) | def __init__(self, settings: AbstractSettings | None, cryptograph: Abs...
method serialize (line 40) | def serialize(self, s: AbstractStrategy[TRoot]) -> T:
method deserialize (line 44) | def deserialize(self, t: T) -> AbstractStrategy[TRoot] | None:
FILE: src/fk/core/abstract_settings.py
function _get_desktop (line 30) | def _get_desktop() -> [str]:
function _is_gnome (line 34) | def _is_gnome() -> bool:
function _always_show (line 38) | def _always_show(_) -> bool:
function _never_show (line 42) | def _never_show(_) -> bool:
function _show_for_simple_long_breaks (line 46) | def _show_for_simple_long_breaks(values: dict[str, str]) -> bool:
function _show_for_smart_long_breaks (line 50) | def _show_for_smart_long_breaks(values: dict[str, str]) -> bool:
function _show_for_gradient_eyecandy (line 54) | def _show_for_gradient_eyecandy(values: dict[str, str]) -> bool:
function _show_for_image_eyecandy (line 58) | def _show_for_image_eyecandy(values: dict[str, str]) -> bool:
function _show_for_file_source (line 62) | def _show_for_file_source(values: dict[str, str]) -> bool:
function _hide_for_ephemeral_source (line 66) | def _hide_for_ephemeral_source(values: dict[str, str]) -> bool:
function _show_for_websocket_source (line 70) | def _show_for_websocket_source(values: dict[str, str]) -> bool:
function _show_when_encryption_is_enabled (line 74) | def _show_when_encryption_is_enabled(values: dict[str, str]) -> bool:
function _show_when_encryption_is_optional (line 79) | def _show_when_encryption_is_optional(values: dict[str, str]) -> bool:
function _show_for_custom_websocket_source (line 83) | def _show_for_custom_websocket_source(values: dict[str, str]) -> bool:
function _show_for_basic_auth (line 87) | def _show_for_basic_auth(values: dict[str, str]) -> bool:
function _show_for_google_auth (line 91) | def _show_for_google_auth(values: dict[str, str]) -> bool:
function _show_if_play_alarm_enabled (line 95) | def _show_if_play_alarm_enabled(values: dict[str, str]) -> bool:
function _show_if_signed_in (line 99) | def _show_if_signed_in(values: dict[str, str]) -> bool:
function _show_if_signed_out (line 103) | def _show_if_signed_out(values: dict[str, str]) -> bool:
function _show_if_play_rest_enabled (line 107) | def _show_if_play_rest_enabled(values: dict[str, str]) -> bool:
function _show_if_madelene (line 111) | def _show_if_madelene(values: dict[str, str]) -> bool:
function _show_if_play_tick_enabled (line 115) | def _show_if_play_tick_enabled(values: dict[str, str]) -> bool:
function _show_for_flatpak (line 119) | def _show_for_flatpak(values: dict[str, str]) -> bool:
function _hide_for_sandbox (line 123) | def _hide_for_sandbox(values: dict[str, str]) -> bool:
function _is_tiling_wm (line 127) | def _is_tiling_wm() -> bool:
function prepare_file_for_writing (line 134) | def prepare_file_for_writing(filename):
class AbstractSettings (line 138) | class AbstractSettings(AbstractEventEmitter, ABC):
method __init__ (line 144) | def __init__(self,
method invoke_callback (line 339) | def invoke_callback(self, fn: Callable, **kwargs) -> None:
method set (line 343) | def set(self, values: dict[str, str], force_fire=False) -> None:
method is_set (line 347) | def is_set(self, name: str) -> bool:
method get (line 351) | def get(self, name: str) -> str:
method clear (line 356) | def clear(self) -> None:
method location (line 360) | def location(self) -> str:
method get_username (line 363) | def get_username(self) -> str:
method is_team_supported (line 370) | def is_team_supported(self) -> bool:
method is_remote_source (line 373) | def is_remote_source(self) -> bool:
method get_fullname (line 376) | def get_fullname(self) -> str:
method get_work_duration (line 379) | def get_work_duration(self) -> float:
method get_rest_duration (line 382) | def get_rest_duration(self) -> float:
method get_categories (line 385) | def get_categories(self) -> Iterable[str]:
method get_settings (line 388) | def get_settings(self, category) -> Iterable[tuple[str, str, str, str,...
method _get_property (line 402) | def _get_property(self, option_id, n) -> str:
method hide (line 409) | def hide(self, option_id: str) -> None:
method get_type (line 420) | def get_type(self, option_id) -> str:
method get_display_name (line 423) | def get_display_name(self, option_id) -> str:
method get_configuration (line 426) | def get_configuration(self, option_id) -> list[any]:
method reset_to_defaults (line 429) | def reset_to_defaults(self) -> None:
method is_e2e_encryption_enabled (line 439) | def is_e2e_encryption_enabled(self) -> bool:
method is_keyring_enabled (line 446) | def is_keyring_enabled(self) -> bool:
method get_auto_theme (line 450) | def get_auto_theme(self) -> str:
method get_theme (line 453) | def get_theme(self) -> str:
method update_default (line 457) | def update_default(self, name: str, value: str) -> None:
method init_audio_outputs (line 463) | def init_audio_outputs(self):
method init_gradients (line 467) | def init_gradients(self):
method init_fonts (line 471) | def init_fonts(self):
method init_appearance (line 475) | def init_appearance(self):
method init_network_access (line 479) | def init_network_access(self):
FILE: src/fk/core/abstract_strategy.py
class AbstractStrategy (line 31) | class AbstractStrategy(ABC, Generic[TRoot]):
method __init__ (line 39) | def __init__(self,
method get_name (line 53) | def get_name(self) -> str:
method get_when (line 58) | def get_when(self) -> datetime.datetime:
method get_user_identity (line 61) | def get_user_identity(self) -> str:
method replace_user_identity (line 64) | def replace_user_identity(self, user_identity: str) -> None:
method get_sequence (line 67) | def get_sequence(self) -> int:
method update_sequence (line 70) | def update_sequence(self, new_seq: int) -> None:
method encryptable (line 73) | def encryptable(self) -> bool:
method requires_sealing (line 77) | def requires_sealing(self) -> bool:
method execute (line 82) | def execute(self,
method get_params (line 87) | def get_params(self):
method execute_another (line 92) | def execute_another(self,
method get_user (line 115) | def get_user(self, data: Tenant, fail_if_not_found: bool = True) -> Us...
FILE: src/fk/core/abstract_timer.py
class AbstractTimer (line 21) | class AbstractTimer(ABC):
method schedule (line 23) | def schedule(self,
method cancel (line 31) | def cancel(self) -> None:
FILE: src/fk/core/abstract_timer_display.py
class AbstractTimerDisplay (line 30) | class AbstractTimerDisplay:
method timer (line 41) | def timer(self) -> TimerData:
method __init__ (line 44) | def __init__(self,
method _set_mode (line 59) | def _set_mode(self, mode):
method _on_source_changed (line 77) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method initialized (line 91) | def initialized(self):
method _on_timer_initialized (line 95) | def _on_timer_initialized(self, **kwargs) -> None:
method _on_tick (line 105) | def _on_tick(self, **kwargs) -> None:
method _on_work_start (line 134) | def _on_work_start(self, timer: TimerData, **kwargs) -> None:
method _on_work_complete (line 140) | def _on_work_complete(self, pomodoro: Pomodoro, **kwargs) -> None:
method _on_rest_complete (line 146) | def _on_rest_complete(self, pomodoro: Pomodoro, **kwargs) -> None:
method _on_workitem_complete_or_delete (line 154) | def _on_workitem_complete_or_delete(self, workitem: Workitem, **kwargs...
method _on_pomodoro_remove (line 160) | def _on_pomodoro_remove(self, workitem: Workitem, **kwargs) -> None:
method tick (line 168) | def tick(self, pomodoro: Pomodoro, state_text: str, my_value: float, m...
method mode_changed (line 171) | def mode_changed(self, old_mode: str, new_mode: str) -> None:
method kill (line 174) | def kill(self) -> None:
FILE: src/fk/core/backlog.py
class Backlog (line 26) | class Backlog(AbstractDataContainer[Workitem, 'User']):
method __init__ (line 30) | def __init__(self,
method __str__ (line 38) | def __str__(self):
method get_running_workitem (line 41) | def get_running_workitem(self) -> Tuple[Workitem, Pomodoro] | Tuple[No...
method get_incomplete_workitems (line 48) | def get_incomplete_workitems(self) -> Iterable[Workitem]:
method is_today (line 53) | def is_today(self) -> bool:
method get_owner (line 58) | def get_owner(self) -> 'User':
method get_start_date (line 61) | def get_start_date(self) -> datetime.datetime | None:
method update_start_date (line 64) | def update_start_date(self, when: datetime.datetime) -> None:
method to_dict (line 68) | def to_dict(self) -> dict:
FILE: src/fk/core/backlog_strategies.py
class CreateBacklogStrategy (line 31) | class CreateBacklogStrategy(AbstractStrategy[Tenant]):
method get_backlog_uid (line 35) | def get_backlog_uid(self) -> str:
method __init__ (line 38) | def __init__(self,
method execute (line 49) | def execute(self,
class DeleteBacklogStrategy (line 72) | class DeleteBacklogStrategy(AbstractStrategy[Tenant]):
method get_backlog_uid (line 75) | def get_backlog_uid(self) -> str:
method __init__ (line 78) | def __init__(self,
method requires_sealing (line 88) | def requires_sealing(self) -> bool:
method execute (line 91) | def execute(self,
class RenameBacklogStrategy (line 124) | class RenameBacklogStrategy(AbstractStrategy[Tenant]):
method get_backlog_uid (line 128) | def get_backlog_uid(self) -> str:
method __init__ (line 131) | def __init__(self,
method execute (line 142) | def execute(self,
class ReorderBacklogStrategy (line 164) | class ReorderBacklogStrategy(AbstractStrategy[Tenant]):
method get_backlog_uid (line 168) | def get_backlog_uid(self) -> str:
method __init__ (line 171) | def __init__(self,
method execute (line 182) | def execute(self,
FILE: src/fk/core/ephemeral_event_source.py
class EphemeralEventSource (line 32) | class EphemeralEventSource(AbstractEventSource[TRoot]):
method __init__ (line 36) | def __init__(self,
method start (line 47) | def start(self, mute_events=True) -> None:
method _append (line 62) | def _append(self, strategies: list[AbstractStrategy[TRoot]]) -> None:
method get_name (line 66) | def get_name(self) -> str:
method get_data (line 69) | def get_data(self) -> TRoot:
method clone (line 72) | def clone(self, new_root: TRoot, existing_strategies: Iterable[Abstrac...
method disconnect (line 77) | def disconnect(self):
method send_ping (line 80) | def send_ping(self) -> str | None:
method can_connect (line 83) | def can_connect(self):
method dump (line 86) | def dump(self):
method repair (line 90) | def repair(self) -> tuple[list[str], str | None]:
FILE: src/fk/core/event_source_factory.py
class EventSourceFactory (line 28) | class EventSourceFactory(Generic[TRoot]):
method __init__ (line 32) | def __init__(self):
method is_valid (line 35) | def is_valid(self, name: str) -> bool:
method get_producer (line 38) | def get_producer(self, name: str) -> Callable[[AbstractSettings, Abstr...
method register_producer (line 41) | def register_producer(self,
method get_event_source_factory (line 47) | def get_event_source_factory() -> EventSourceFactory[Tenant]:
FILE: src/fk/core/event_source_holder.py
class EventSourceHolder (line 33) | class EventSourceHolder(AbstractEventEmitter, Generic[TRoot]):
method __init__ (line 38) | def __init__(self, settings: AbstractSettings, cryptograph: AbstractCr...
method close_current_source (line 45) | def close_current_source(self) -> None:
method request_new_source (line 52) | def request_new_source(self) -> AbstractEventSource[TRoot]:
method get_source (line 78) | def get_source(self) -> AbstractEventSource[TRoot] | None:
method get_settings (line 81) | def get_settings(self) -> AbstractSettings:
FILE: src/fk/core/events.py
class EmittedEvent (line 96) | class EmittedEvent:
method __init__ (line 100) | def __init__(self, event: str, emitter: object):
method __str__ (line 104) | def __str__(self):
function set_emitter_added_callback (line 119) | def set_emitter_added_callback(callback: Callable[[object], None]) -> None:
function register_event (line 124) | def register_event(event: str, emitter: object):
function get_all_events (line 134) | def get_all_events() -> set[str]:
FILE: src/fk/core/fernet_cryptograph.py
class FernetCryptograph (line 29) | class FernetCryptograph(AbstractCryptograph):
method __init__ (line 32) | def __init__(self, settings: AbstractSettings):
method _create_fernet (line 38) | def _create_fernet(self, cached_key) -> Fernet:
method _on_key_changed (line 57) | def _on_key_changed(self) -> None:
method encrypt (line 60) | def encrypt(self, s: str) -> str:
method decrypt (line 65) | def decrypt(self, s: str) -> str:
FILE: src/fk/core/file_event_source.py
class FileEventSource (line 46) | class FileEventSource(AbstractEventSource[TRoot]):
method __init__ (line 52) | def __init__(self,
method get_last_strategy (line 70) | def get_last_strategy(self) -> AbstractStrategy | None:
method _on_file_change (line 73) | def _on_file_change(self, filename: str) -> None:
method _get_filename (line 108) | def _get_filename(self) -> str:
method _is_watch_changes (line 111) | def _is_watch_changes(self) -> bool:
method start (line 114) | def start(self, mute_events: bool = True, fail_early: bool = False) ->...
method _process_from_existing (line 121) | def _process_from_existing(self, fail_early: bool) -> None:
method _process_from_file (line 156) | def _process_from_file(self, mute_events=True) -> None:
method repair (line 212) | def repair(self) -> tuple[list[str], str | None]:
method _overwrite_file (line 446) | def _overwrite_file(self, strategies: Iterable[AbstractStrategy], log:...
method _append (line 460) | def _append(self, strategies: list[AbstractStrategy]) -> None:
method get_name (line 474) | def get_name(self) -> str:
method get_data (line 477) | def get_data(self) -> TRoot:
method _count_valid_strategies (line 480) | def _count_valid_strategies(self) -> int:
method compress (line 491) | def compress(self) -> list[str]:
method clone (line 518) | def clone(self, new_root: TRoot, existing_strategies: Iterable[Abstrac...
method disconnect (line 525) | def disconnect(self):
method send_ping (line 529) | def send_ping(self) -> str | None:
method can_connect (line 532) | def can_connect(self):
FILE: src/fk/core/import_export.py
function _export_message_processed (line 45) | def _export_message_processed(source: AbstractEventSource[TRoot],
function _export_completed (line 61) | def _export_completed(source: AbstractEventSource[TRoot],
function compressed_strategies (line 69) | def compressed_strategies(source: AbstractEventSource[TRoot]) -> Iterabl...
function merge_strategies (line 158) | def merge_strategies(source: AbstractEventSource[TRoot],
function _export_compressed (line 305) | def _export_compressed(source: AbstractEventSource[TRoot],
function export (line 316) | def export(source: AbstractEventSource[TRoot],
function create_export_serializer (line 355) | def create_export_serializer(source: AbstractEventSource[TRoot], encrypt...
function import_ (line 364) | def import_(source: AbstractEventSource[TRoot],
function import_github_issues (line 394) | def import_github_issues(source: AbstractEventSource[TRoot],
function import_simple (line 477) | def import_simple(source: AbstractEventSource[TRoot],
function _merge_sources (line 539) | def _merge_sources(existing_source,
function import_classic (line 561) | def import_classic(source: AbstractEventSource[TRoot],
FILE: src/fk/core/integration_executor.py
class IntegrationExecutor (line 31) | class IntegrationExecutor:
method __init__ (line 35) | def __init__(self, settings: AbstractSettings):
method _on_emitter_added (line 43) | def _on_emitter_added(self, emitter: object):
method _on_setting_changed (line 46) | def _on_setting_changed(self, new_values: dict[str, str], **kwargs):
method _resync_subscriptions_from_settings (line 50) | def _resync_subscriptions_from_settings(self) -> None:
method _sync_subscriptions (line 53) | def _sync_subscriptions(self, new_conf: dict[str, str]) -> None:
method on_event (line 78) | def on_event(self, full_event, **kwargs):
FILE: src/fk/core/interruption.py
class Interruption (line 26) | class Interruption(AbstractDataItem['Pomodoro']):
method __init__ (line 31) | def __init__(self,
method __str__ (line 43) | def __str__(self):
method get_reason (line 49) | def get_reason(self) -> str | None:
method get_duration (line 52) | def get_duration(self) -> datetime.timedelta | None:
method is_void (line 55) | def is_void(self) -> bool:
method get_parent (line 60) | def get_parent(self) -> 'Pomodoro':
method dump (line 63) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method to_dict (line 69) | def to_dict(self) -> dict:
method __eq__ (line 76) | def __eq__(self, other: Interruption) -> bool:
FILE: src/fk/core/mock_settings.py
function invoke_direct (line 25) | def invoke_direct(fn, **kwargs):
class MockSettings (line 29) | class MockSettings(AbstractSettings):
method __init__ (line 32) | def __init__(self, filename=None, username=None, source_type="local"):
method get (line 47) | def get(self, name: str) -> str:
method is_set (line 53) | def is_set(self, name: str) -> bool:
method set (line 56) | def set(self, values: dict[str, str], force_fire=False) -> None:
method location (line 72) | def location(self) -> str:
method clear (line 75) | def clear(self) -> None:
method get_displayed_settings (line 78) | def get_displayed_settings(self) -> list[str]:
method is_keyring_enabled (line 92) | def is_keyring_enabled(self) -> bool:
method get_auto_theme (line 95) | def get_auto_theme(self) -> str:
method init_gradients (line 98) | def init_gradients(self):
method init_audio_outputs (line 101) | def init_audio_outputs(self):
method init_fonts (line 104) | def init_fonts(self):
method init_appearance (line 107) | def init_appearance(self):
method init_network_access (line 110) | def init_network_access(self):
FILE: src/fk/core/no_cryptograph.py
class NoCryptograph (line 20) | class NoCryptograph(AbstractCryptograph):
method __init__ (line 21) | def __init__(self, settings: AbstractSettings):
method _on_key_changed (line 26) | def _on_key_changed(self) -> None:
method encrypt (line 29) | def encrypt(self, s: str) -> str:
method decrypt (line 32) | def decrypt(self, s: str) -> str:
FILE: src/fk/core/pomodoro.py
class Pomodoro (line 33) | class Pomodoro(AbstractDataContainer[Interruption, 'Workitem']):
method __init__ (line 44) | def __init__(self,
method __str__ (line 64) | def __str__(self):
method update_work_duration (line 76) | def update_work_duration(self, work_duration: float) -> None:
method get_state (line 82) | def get_state(self) -> str:
method get_work_start_date (line 85) | def get_work_start_date(self) -> datetime.datetime:
method get_rest_start_date (line 88) | def get_rest_start_date(self) -> datetime.datetime:
method update_rest_duration (line 94) | def update_rest_duration(self, rest_duration: float) -> None:
method seal (line 103) | def seal(self, when: datetime.datetime) -> None:
method void (line 136) | def void(self, when: datetime.datetime) -> None:
method start_work (line 147) | def start_work(self, when: datetime.datetime) -> None:
method start_rest (line 156) | def start_rest(self, when: datetime.datetime) -> None:
method is_running (line 164) | def is_running(self) -> bool:
method is_startable (line 167) | def is_startable(self) -> bool:
method is_working (line 170) | def is_working(self) -> bool:
method is_resting (line 173) | def is_resting(self) -> bool:
method is_long_break (line 176) | def is_long_break(self) -> bool:
method is_finished (line 179) | def is_finished(self) -> bool:
method get_elapsed_work_duration (line 182) | def get_elapsed_work_duration(self, when: datetime.datetime = None) ->...
method get_elapsed_rest_duration (line 196) | def get_elapsed_rest_duration(self, when: datetime.datetime = None) ->...
method get_work_duration (line 208) | def get_work_duration(self, when: datetime.datetime = None) -> float:
method get_rest_duration (line 221) | def get_rest_duration(self) -> float:
method get_type (line 227) | def get_type(self):
method remaining_time_in_current_state (line 230) | def remaining_time_in_current_state(self, when: datetime.datetime | No...
method remaining_minutes_in_current_state_str (line 246) | def remaining_minutes_in_current_state_str(self, when: datetime.dateti...
method planned_end_of_work (line 256) | def planned_end_of_work(self) -> datetime.datetime:
method planned_end_of_rest (line 264) | def planned_end_of_rest(self) -> datetime.datetime:
method get_parent (line 272) | def get_parent(self) -> 'Workitem':
method dump (line 275) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method add_interruption (line 286) | def add_interruption(self, reason: str | None, duration: datetime.time...
method is_planned (line 290) | def is_planned(self) -> bool:
method get_timer (line 293) | def get_timer(self) -> 'TimerData':
method to_dict (line 296) | def to_dict(self) -> dict:
FILE: src/fk/core/pomodoro_strategies.py
class AddPomodoroStrategy (line 31) | class AddPomodoroStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 37) | def get_workitem_uid(self) -> str:
method __init__ (line 40) | def __init__(self,
method execute (line 53) | def execute(self,
class RemovePomodoroStrategy (line 93) | class RemovePomodoroStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 97) | def get_workitem_uid(self) -> str:
method requires_sealing (line 100) | def requires_sealing(self) -> bool:
method __init__ (line 103) | def __init__(self,
method execute (line 114) | def execute(self,
class AddInterruptionStrategy (line 157) | class AddInterruptionStrategy(AbstractStrategy[Tenant]):
method requires_sealing (line 162) | def requires_sealing(self) -> bool:
method get_workitem_uid (line 165) | def get_workitem_uid(self) -> str:
method __init__ (line 168) | def __init__(self,
method execute (line 183) | def execute(self,
FILE: src/fk/core/sandbox.py
function get_sandbox_type (line 20) | def get_sandbox_type() -> str | None:
FILE: src/fk/core/simple_serializer.py
class SimpleSerializer (line 31) | class SimpleSerializer(AbstractSerializer[str, TRoot]):
method __init__ (line 40) | def __init__(self, settings: AbstractSettings, cryptograph: AbstractCr...
method escape_parameter (line 44) | def escape_parameter(value):
method serialize (line 47) | def serialize(self, s: AbstractStrategy) -> str:
method deserialize (line 61) | def deserialize(self, t: str) -> AbstractStrategy[TRoot] | None:
method __str__ (line 96) | def __str__(self):
FILE: src/fk/core/strategy_factory.py
function strategy (line 28) | def strategy(cls: Type[AbstractStrategy[TRoot]]):
FILE: src/fk/core/tag.py
class Tag (line 28) | class Tag(AbstractDataItem['Tags']):
method __init__ (line 31) | def __init__(self,
method __str__ (line 40) | def __str__(self):
method get_workitems (line 43) | def get_workitems(self) -> Set[Workitem]:
method add_workitem (line 46) | def add_workitem(self, workitem: Workitem) -> None:
method remove_workitem (line 49) | def remove_workitem(self, workitem: Workitem) -> None:
method dump (line 52) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
FILE: src/fk/core/tags.py
function sanitize_tag (line 22) | def sanitize_tag(tag: str) -> str:
class Tags (line 26) | class Tags(AbstractDataContainer[Tag, 'User']):
method __init__ (line 27) | def __init__(self, user: 'User'):
method __str__ (line 33) | def __str__(self):
method dump (line 36) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
FILE: src/fk/core/tenant.py
class Tenant (line 25) | class Tenant(AbstractDataContainer[User, None]):
method __init__ (line 31) | def __init__(self, settings: AbstractSettings):
method get_settings (line 45) | def get_settings(self) -> AbstractSettings:
method get_user (line 48) | def get_user(self, identity: str) -> User:
method get_current_user (line 51) | def get_current_user(self) -> User:
FILE: src/fk/core/timer.py
class PomodoroTimer (line 32) | class PomodoroTimer(AbstractEventEmitter):
method timer (line 39) | def timer(self) -> TimerData:
method __init__ (line 45) | def __init__(self,
method _on_source_changed (line 60) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method _refresh (line 67) | def _refresh(self, event: str | None = None, when: datetime.datetime |...
method _schedule_tick (line 99) | def _schedule_tick(self) -> None:
method _handle_tick (line 103) | def _handle_tick(self, params: dict | None, when: datetime.datetime | ...
method _schedule_transition (line 114) | def _schedule_transition(self,
method _handle_transition (line 125) | def _handle_transition(self, params: dict | None, when: datetime.datet...
method _handle_pomodoro_work_start (line 149) | def _handle_pomodoro_work_start(self,
method _handle_pomodoro_rest_start (line 161) | def _handle_pomodoro_rest_start(self,
method _handle_pomodoro_complete (line 173) | def _handle_pomodoro_complete(self,
FILE: src/fk/core/timer_data.py
class TimerData (line 26) | class TimerData(AbstractDataItem['User']):
method __init__ (line 37) | def __init__(self,
method get_running_pomodoro (line 50) | def get_running_pomodoro(self) -> Pomodoro | None:
method get_running_workitem (line 53) | def get_running_workitem(self) -> Workitem | None:
method get_state (line 56) | def get_state(self) -> str:
method idle (line 59) | def idle(self, when: datetime.datetime | None = None) -> None:
method work (line 69) | def work(self, pomodoro: Pomodoro, work_duration: float, when: datetim...
method _refresh_today (line 83) | def _refresh_today(self, when: datetime.datetime | None = None):
method rest (line 92) | def rest(self, rest_duration: float, when: datetime.datetime | None = ...
method is_working (line 113) | def is_working(self) -> bool:
method is_resting (line 116) | def is_resting(self) -> bool:
method is_idling (line 119) | def is_idling(self) -> bool:
method is_ticking (line 122) | def is_ticking(self) -> bool:
method get_planned_duration (line 125) | def get_planned_duration(self) -> int:
method get_remaining_duration (line 128) | def get_remaining_duration(self) -> float:
method get_next_state_change (line 131) | def get_next_state_change(self) -> datetime.datetime | None:
method format_remaining_duration (line 135) | def format_remaining_duration(self) -> str:
method format_elapsed_work_duration (line 141) | def format_elapsed_work_duration(self, when: datetime.datetime | None ...
method format_elapsed_rest_duration (line 149) | def format_elapsed_rest_duration(self, when: datetime.datetime | None ...
method __str__ (line 157) | def __str__(self) -> str:
method update_remaining_duration (line 166) | def update_remaining_duration(self, when: datetime.datetime | None):
method to_dict (line 175) | def to_dict(self) -> dict:
method get_pomodoro_in_series (line 185) | def get_pomodoro_in_series(self) -> int:
FILE: src/fk/core/timer_strategies.py
class StartTimerStrategy (line 32) | class StartTimerStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 37) | def get_workitem_uid(self) -> str:
method requires_sealing (line 40) | def requires_sealing(self) -> bool:
method __init__ (line 43) | def __init__(self,
method get_workitem (line 62) | def get_workitem(self,
method execute (line 79) | def execute(self,
class StopTimerStrategy (line 124) | class StopTimerStrategy(AbstractStrategy[Tenant]):
method __init__ (line 125) | def __init__(self,
method requires_sealing (line 134) | def requires_sealing(self) -> bool:
method execute (line 137) | def execute(self,
class TimerRingInternalStrategy (line 187) | class TimerRingInternalStrategy(AbstractStrategy[Tenant]):
method __init__ (line 188) | def __init__(self,
method execute (line 197) | def execute(self,
class StartWorkStrategy (line 256) | class StartWorkStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 261) | def get_workitem_uid(self) -> str:
method requires_sealing (line 264) | def requires_sealing(self) -> bool:
method __init__ (line 267) | def __init__(self,
method execute (line 282) | def execute(self,
class VoidPomodoroStrategy (line 294) | class VoidPomodoroStrategy(AbstractStrategy[Tenant]):
method __init__ (line 297) | def __init__(self,
method requires_sealing (line 307) | def requires_sealing(self) -> bool:
method execute (line 310) | def execute(self,
class FinishTrackingStrategy (line 326) | class FinishTrackingStrategy(AbstractStrategy[Tenant]):
method __init__ (line 327) | def __init__(self,
method requires_sealing (line 336) | def requires_sealing(self) -> bool:
method execute (line 339) | def execute(self,
FILE: src/fk/core/user.py
class User (line 27) | class User(AbstractDataContainer[Backlog, 'Tenant']):
method __init__ (line 32) | def __init__(self,
method __str__ (line 43) | def __str__(self):
method get_identity (line 46) | def get_identity(self) -> str:
method is_system_user (line 49) | def is_system_user(self) -> bool:
method get_state (line 53) | def get_state(self, when: datetime.datetime) -> (str, int):
method get_tags (line 64) | def get_tags(self) -> Tags:
method get_timer (line 67) | def get_timer(self) -> TimerData:
method dump (line 70) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method to_dict (line 75) | def to_dict(self) -> dict:
FILE: src/fk/core/user_strategies.py
function is_system_user (line 28) | def is_system_user(user_identity: str):
class CreateUserStrategy (line 34) | class CreateUserStrategy(AbstractStrategy[Tenant]):
method get_target_user_identity (line 38) | def get_target_user_identity(self) -> str:
method __init__ (line 41) | def __init__(self,
method execute (line 52) | def execute(self,
class DeleteUserStrategy (line 73) | class DeleteUserStrategy(AbstractStrategy[Tenant]):
method get_target_user_identity (line 76) | def get_target_user_identity(self) -> str:
method requires_sealing (line 79) | def requires_sealing(self) -> bool:
method __init__ (line 82) | def __init__(self,
method execute (line 92) | def execute(self,
class RenameUserStrategy (line 125) | class RenameUserStrategy(AbstractStrategy[Tenant]):
method get_target_user_identity (line 129) | def get_target_user_identity(self) -> str:
method __init__ (line 132) | def __init__(self,
method execute (line 143) | def execute(self,
class AutoSealInternalStrategy (line 167) | class AutoSealInternalStrategy(AbstractStrategy[Tenant]):
method __init__ (line 168) | def __init__(self,
method requires_sealing (line 177) | def requires_sealing(self) -> bool:
method execute (line 180) | def execute(self,
FILE: src/fk/core/workitem.py
class Interval (line 31) | class Interval:
method __init__ (line 37) | def __init__(self, started: datetime.datetime, work_duration: float, r...
method end (line 43) | def end(self, when: datetime.datetime):
method get_started (line 46) | def get_started(self) -> datetime.datetime:
method is_ended_manually (line 49) | def is_ended_manually(self) -> bool:
method get_ended (line 52) | def get_ended(self) -> datetime.datetime:
method get_work_duration (line 55) | def get_work_duration(self) -> float:
method get_rest_duration (line 58) | def get_rest_duration(self) -> float:
method __str__ (line 61) | def __str__(self) -> str:
method __eq__ (line 64) | def __eq__(self, other: Interval) -> bool:
class Workitem (line 71) | class Workitem(AbstractDataContainer[Pomodoro, 'Backlog']):
method __init__ (line 78) | def __init__(self,
method __str__ (line 89) | def __str__(self):
method seal (line 103) | def seal(self, target_state: str, when: datetime.datetime) -> None:
method add_pomodoro (line 110) | def add_pomodoro(self,
method remove_pomodoro (line 135) | def remove_pomodoro(self, pomodoro: Pomodoro) -> None:
method is_running (line 138) | def is_running(self) -> bool:
method has_running_pomodoro (line 141) | def has_running_pomodoro(self) -> bool:
method get_running_pomodoro (line 144) | def get_running_pomodoro(self) -> Pomodoro | None:
method is_sealed (line 150) | def is_sealed(self) -> bool:
method is_planned (line 153) | def is_planned(self) -> bool:
method is_startable (line 160) | def is_startable(self) -> bool:
method start (line 167) | def start(self, when: datetime.datetime) -> None:
method add_interval (line 172) | def add_interval(self, start: datetime.datetime, work_duration: float,...
method end_interval (line 175) | def end_interval(self, when: datetime.datetime):
method dump (line 178) | def dump(self, indent: str = '', mask_uid: bool = False, mask_last_mod...
method get_work_start_date (line 185) | def get_work_start_date(self) -> datetime.datetime:
method get_incomplete_pomodoros (line 188) | def get_incomplete_pomodoros(self) -> Iterable[Pomodoro]:
method get_tags (line 193) | def get_tags(self) -> Set[str]:
method get_display_name (line 199) | def get_display_name(self) -> str:
method get_short_display_name (line 202) | def get_short_display_name(self) -> str:
method get_total_elapsed_time (line 205) | def get_total_elapsed_time(self) -> datetime.timedelta:
method is_tracker (line 209) | def is_tracker(self) -> bool:
method get_intervals (line 215) | def get_intervals(self) -> Iterable[Interval]:
method to_dict (line 218) | def to_dict(self) -> dict:
FILE: src/fk/core/workitem_strategies.py
class CreateWorkitemStrategy (line 34) | class CreateWorkitemStrategy(AbstractStrategy[Tenant]):
method get_backlog_uid (line 39) | def get_backlog_uid(self) -> str:
method get_workitem_uid (line 42) | def get_workitem_uid(self) -> str:
method __init__ (line 45) | def __init__(self,
method execute (line 57) | def execute(self,
class DeleteWorkitemStrategy (line 100) | class DeleteWorkitemStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 103) | def get_workitem_uid(self) -> str:
method __init__ (line 106) | def __init__(self,
method requires_sealing (line 116) | def requires_sealing(self) -> bool:
method execute (line 119) | def execute(self,
class RenameWorkitemStrategy (line 162) | class RenameWorkitemStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 166) | def get_workitem_uid(self) -> str:
method __init__ (line 169) | def __init__(self,
method execute (line 180) | def execute(self,
class CompleteWorkitemStrategy (line 245) | class CompleteWorkitemStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 249) | def get_workitem_uid(self) -> str:
method requires_sealing (line 252) | def requires_sealing(self) -> bool:
method __init__ (line 255) | def __init__(self,
method execute (line 266) | def execute(self,
class ReorderWorkitemStrategy (line 307) | class ReorderWorkitemStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 311) | def get_workitem_uid(self) -> str:
method __init__ (line 314) | def __init__(self,
method execute (line 325) | def execute(self,
class MoveWorkitemStrategy (line 352) | class MoveWorkitemStrategy(AbstractStrategy[Tenant]):
method get_workitem_uid (line 356) | def get_workitem_uid(self) -> str:
method get_backlog_uid (line 359) | def get_backlog_uid(self) -> str:
method __init__ (line 362) | def __init__(self,
method execute (line 373) | def execute(self,
FILE: src/fk/desktop/application.py
function setting_requires_new_source (line 76) | def setting_requires_new_source(name: str) -> bool:
class Application (line 85) | class Application(QApplication, AbstractEventEmitter):
method __init__ (line 100) | def __init__(self, args: [str]):
method _get_versions (line 200) | def _get_versions(self):
method _initialize_logger (line 208) | def _initialize_logger(self):
method _check_upgrade (line 244) | def _check_upgrade(self, event: str, when: datetime.datetime | None = ...
method initialize_source (line 259) | def initialize_source(self):
method _register_source_producers (line 262) | def _register_source_producers(self):
method _on_source_changed (line 283) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method is_e2e_mode (line 293) | def is_e2e_mode(self):
method is_hide_on_start (line 296) | def is_hide_on_start(self):
method is_screenshot_mode (line 301) | def is_screenshot_mode(self):
method is_testing_mode (line 304) | def is_testing_mode(self):
method _on_went_offline (line 307) | def _on_went_offline(self, event, after: int, last_received: datetime....
method _on_went_online (line 312) | def _on_went_online(self, event, ping: int) -> None:
method get_settings (line 316) | def get_settings(self):
method get_source_holder (line 319) | def get_source_holder(self):
method get_theme_variables (line 322) | def get_theme_variables(self) -> dict[str, str]:
method get_icon_theme (line 335) | def get_icon_theme(self):
method refresh_theme_and_fonts (line 339) | def refresh_theme_and_fonts(self):
method _load_embedded_font (line 364) | def _load_embedded_font(self):
method _initialize_fonts (line 371) | def _initialize_fonts(self) -> (QFont, QFont):
method _auto_resize (line 400) | def _auto_resize(self) -> int:
method on_exception (line 411) | def on_exception(self, exc_type, exc_value, exc_trace):
method bad_file_for_file_source (line 432) | def bad_file_for_file_source(self):
method get_main_font (line 442) | def get_main_font(self):
method get_header_font (line 445) | def get_header_font(self):
method get_row_height (line 448) | def get_row_height(self):
method _before_settings_changed (line 451) | def _before_settings_changed(self, event: str, old_values: dict[str, s...
method _after_settings_changed (line 459) | def _after_settings_changed(self, event: str, old_values: dict[str, st...
method is_another_instance_running (line 495) | def is_another_instance_running(self) -> bool:
method show_settings_dialog (line 504) | def show_settings_dialog(self):
method repair_file_event_source (line 517) | def repair_file_event_source(self, _, callback: Callable) -> bool:
method compress_file_event_source (line 547) | def compress_file_event_source(self, _, callback: Callable) -> bool:
method delete_account (line 579) | def delete_account(self, _, callback: Callable) -> bool:
method generate_gradient (line 608) | def generate_gradient(self, _, callback: Callable) -> bool:
method sign_in (line 617) | def sign_in(self, _, callback: Callable) -> bool:
method sign_out (line 633) | def sign_out(self, _, callback: Callable) -> bool:
method define_actions (line 648) | def define_actions(actions: Actions):
method quit_local (line 667) | def quit_local(self):
method show_import_wizard (line 670) | def show_import_wizard(self):
method show_export_wizard (line 674) | def show_export_wizard(self):
method show_about (line 678) | def show_about(self):
method toggle_toolbar (line 681) | def toggle_toolbar(self, state: bool):
method get_heartbeat (line 684) | def get_heartbeat(self) -> Heartbeat:
method check_version (line 687) | def check_version(self, event: str, when: datetime.datetime | None = N...
method show_stats (line 703) | def show_stats(self, event: str = None) -> None:
method show_work_summary (line 709) | def show_work_summary(self, event: str = None) -> None:
method on_new_version (line 712) | def on_new_version(self, event: str, current: Version, latest: Version...
method is_dark_theme (line 736) | def is_dark_theme(self):
FILE: src/fk/desktop/config_wizard.py
function wrap_in_widget (line 39) | def wrap_in_widget(widget: QWidget):
class PageConfigFocus (line 50) | class PageConfigFocus(QWizardPage):
method __init__ (line 59) | def __init__(self, application: Application, actions: Actions):
method _handle_tick (line 113) | def _handle_tick(self, params: dict | None = None, when: datetime.date...
method get_setting (line 120) | def get_setting(self) -> str:
method unsubscribe (line 123) | def unsubscribe(self) -> None:
class FakeTrayIcon (line 129) | class FakeTrayIcon(TrayIcon):
method __init__ (line 134) | def __init__(self,
method setIcon (line 152) | def setIcon(self, icon: QIcon | QPixmap) -> None:
method showMessage (line 160) | def showMessage(self, title: str, msg: str, icon: QIcon = None, **_) -...
method setToolTip (line 163) | def setToolTip(self, tip: str) -> None:
method setContextMenu (line 166) | def setContextMenu(self, menu: QMenu) -> None:
class PageConfigIcons (line 170) | class PageConfigIcons(QWizardPage):
method __init__ (line 177) | def __init__(self, application: Application, actions: Actions):
method _create_icons (line 222) | def _create_icons(self, container: QWidget, kind: str, cls: Type[Abstr...
method get_setting (line 270) | def get_setting(self) -> str:
class ConfigWizard (line 281) | class ConfigWizard(QWizard):
method __init__ (line 288) | def __init__(self, application: Application, actions: Actions, parent:...
method _on_finish (line 306) | def _on_finish(self):
method unsubscribe (line 312) | def unsubscribe(self):
method hideEvent (line 315) | def hideEvent(self, event: QHideEvent) -> None:
FILE: src/fk/desktop/desktop.py
function get_timer_ui_mode (line 58) | def get_timer_ui_mode() -> str:
function pin_if_needed (line 63) | def pin_if_needed(always_on_top_setting: str):
function to_focus_mode (line 79) | def to_focus_mode(**kwargs) -> None:
function from_focus_mode (line 102) | def from_focus_mode(**_) -> None:
function update_tables_visibility (line 110) | def update_tables_visibility() -> None:
function update_mode (line 118) | def update_mode(timer_ticking: bool) -> None:
function recreate_tray_icon (line 139) | def recreate_tray_icon(flavor: str, show_tray_icon_setting: str) -> None:
function on_settings_changed (line 158) | def on_settings_changed(event: str, old_values: dict[str, str], new_valu...
class MainWindow (line 196) | class MainWindow:
method __init__ (line 197) | def __init__(self):
method toggle_focus_mode (line 200) | def toggle_focus_mode(self, state: bool):
method toggle_pin_window (line 206) | def toggle_pin_window(self, state: bool):
method toggle_main_window (line 210) | def toggle_main_window(self):
method show_search (line 229) | def show_search(self):
method show_tutorial (line 232) | def show_tutorial(self):
method on_upgrade (line 236) | def on_upgrade(self, from_version: Version):
method toggle_backlogs (line 245) | def toggle_backlogs(self, enabled):
method toggle_users (line 248) | def toggle_users(self, enabled):
method define_actions (line 252) | def define_actions(actions: Actions):
function _on_workitem_complete (line 294) | def _on_workitem_complete(workitem: Workitem, timer: TimerData):
function _on_source_changed (line 298) | def _on_source_changed(event: str, source: AbstractEventSource):
FILE: src/fk/desktop/desktop_strategies.py
class AuthenticateStrategy (line 35) | class AuthenticateStrategy(AbstractStrategy[Tenant]):
method __init__ (line 39) | def __init__(self,
method encryptable (line 50) | def encryptable(self) -> bool:
method execute (line 53) | def execute(self,
class ReplayStrategy (line 62) | class ReplayStrategy(AbstractStrategy):
method __init__ (line 65) | def __init__(self,
method encryptable (line 75) | def encryptable(self) -> bool:
method execute (line 78) | def execute(self,
class ReplayCompletedStrategy (line 87) | class ReplayCompletedStrategy(AbstractStrategy):
method __init__ (line 88) | def __init__(self,
method encryptable (line 97) | def encryptable(self) -> bool:
method requires_sealing (line 100) | def requires_sealing(self) -> bool:
method execute (line 103) | def execute(self,
class ErrorStrategy (line 112) | class ErrorStrategy(AbstractStrategy):
method __init__ (line 116) | def __init__(self,
method encryptable (line 127) | def encryptable(self) -> bool:
method execute (line 130) | def execute(self,
class PongStrategy (line 183) | class PongStrategy(AbstractStrategy):
method __init__ (line 186) | def __init__(self,
method encryptable (line 196) | def encryptable(self) -> bool:
method execute (line 199) | def execute(self,
class PingStrategy (line 211) | class PingStrategy(AbstractStrategy):
method __init__ (line 214) | def __init__(self,
method encryptable (line 224) | def encryptable(self) -> bool:
method execute (line 227) | def execute(self,
class DeleteAccountStrategy (line 236) | class DeleteAccountStrategy(AbstractStrategy):
method __init__ (line 239) | def __init__(self,
method encryptable (line 249) | def encryptable(self) -> bool:
method execute (line 252) | def execute(self,
FILE: src/fk/desktop/export_wizard.py
class PageExportIntro (line 36) | class PageExportIntro(QWizardPage):
method __init__ (line 40) | def __init__(self):
class PageExportSettings (line 50) | class PageExportSettings(QWizardPage):
method isComplete (line 60) | def isComplete(self):
method __init__ (line 63) | def __init__(self, settings: AbstractSettings):
class PageExportProgress (line 103) | class PageExportProgress(QWizardPage):
method isComplete (line 111) | def isComplete(self):
method __init__ (line 114) | def __init__(self, source: AbstractEventSource):
method initializePage (line 130) | def initializePage(self):
method finish (line 133) | def finish(self):
method start (line 149) | def start(self):
class ExportWizard (line 162) | class ExportWizard(QWizard):
method __init__ (line 171) | def __init__(self, source: AbstractEventSource, parent: QWidget | None):
method set_filename (line 190) | def set_filename(self, filename):
method set_encrypted (line 193) | def set_encrypted(self, encrypted):
method set_compressed (line 196) | def set_compressed(self, compressed):
FILE: src/fk/desktop/import_wizard.py
class PageImportIntro (line 35) | class PageImportIntro(QWizardPage):
method __init__ (line 47) | def __init__(self):
method get_selected_import_type (line 95) | def get_selected_import_type(self) -> str:
class PageImportSettings (line 106) | class PageImportSettings(QWizardPage):
method isComplete (line 124) | def isComplete(self):
method __init__ (line 133) | def __init__(self, get_type: Callable[[], str]):
method _reset (line 138) | def _reset(self):
method cleanupPage (line 155) | def cleanupPage(self):
method initializePage (line 158) | def initializePage(self):
method _init_for_file (line 175) | def _init_for_file(self, layout_v):
method _init_for_csv (line 207) | def _init_for_csv(self, layout_v):
method _init_for_github (line 239) | def _init_for_github(self, layout_v):
method _init_for_other (line 276) | def _init_for_other(self, layout_v):
method get_settings (line 281) | def get_settings(self) -> dict[str, any]:
class PageImportProgress (line 314) | class PageImportProgress(QWizardPage):
method isComplete (line 323) | def isComplete(self):
method __init__ (line 326) | def __init__(self,
method initializePage (line 334) | def initializePage(self):
method finish_for_file (line 353) | def finish_for_file(self):
method finish (line 363) | def finish(self, callback: Callable[[], None] | None = None):
method _send_request (line 374) | def _send_request(self,
method _import_from_file (line 408) | def _import_from_file(self):
method _import_from_csv (line 418) | def _import_from_csv(self):
method _import_from_github (line 459) | def _import_from_github(self):
method start (line 497) | def start(self):
class ImportWizard (line 507) | class ImportWizard(QWizard):
method __init__ (line 513) | def __init__(self, source_holder: EventSourceHolder, parent: QWidget |...
FILE: src/fk/desktop/interruption_dialog.py
class InterruptionDialog (line 27) | class InterruptionDialog(QDialog):
method __init__ (line 33) | def __init__(self,
method hideEvent (line 69) | def hideEvent(self, event: QHideEvent) -> None:
method _on_pomodoro_complete (line 74) | def _on_pomodoro_complete(self, **_) -> None:
method _on_action (line 79) | def _on_action(self, role: QDialogButtonBox.ButtonRole):
method get_reason (line 87) | def get_reason(self) -> str:
FILE: src/fk/desktop/settings.py
function _from_total_seconds (line 36) | def _from_total_seconds(total_seconds: int) -> QTime:
class SettingsDialog (line 43) | class SettingsDialog(QDialog):
method __init__ (line 51) | def __init__(self,
method _init_sign_out_button (line 101) | def _init_sign_out_button(self):
method _on_action (line 110) | def _on_action(self, role: QDialogButtonBox.ButtonRole):
method _computed_values (line 129) | def _computed_values(self) -> dict[str, str]:
method _on_value_changed (line 135) | def _on_value_changed(self, option_id, new_value):
method _recompute_visibility (line 150) | def _recompute_visibility(self, option_id, new_value):
method _set_buttons_state (line 159) | def _set_buttons_state(self, is_enabled: bool):
method _save_settings (line 163) | def _save_settings(self) -> bool:
method do_browse (line 178) | def do_browse(edit: QLineEdit) -> None:
method do_browse_simple (line 182) | def do_browse_simple(preselected: str, callback: Callable[[str], None]...
method display_key_warning (line 191) | def display_key_warning(name: str) -> bool:
method _display_option (line 210) | def _display_option(self,
method _handle_button_click (line 490) | def _handle_button_click(self, option_id: str):
method _value_changed_externally (line 505) | def _value_changed_externally(self, name: str, value: str):
method _create_tab (line 509) | def _create_tab(self, tabs: QTabWidget, settings) -> QWidget:
FILE: src/fk/desktop/stats_window.py
class StatsWindow (line 30) | class StatsWindow(QObject):
method __init__ (line 52) | def __init__(self,
method _init_colors (line 125) | def _init_colors(self, theme_variables: dict[str, str]) -> None:
method _style_chart (line 130) | def _style_chart(self) -> None:
method _time_delta_for_period (line 145) | def _time_delta_for_period(self, period: str, date: datetime.datetime,...
method _drop_time (line 189) | def _drop_time(date: datetime.datetime, period: str, start: bool):
method _substep_delta_for_period (line 229) | def _substep_delta_for_period(self, period: str, date: datetime.dateti...
method _prev (line 243) | def _prev(self):
method _next (line 248) | def _next(self):
method _format_date (line 254) | def _format_date(date: datetime.datetime):
method _update_chart (line 257) | def _update_chart(self, period: str, to: datetime.datetime) -> None:
method _create_checkable_action (line 297) | def _create_checkable_action(self, name: str, shortcut: str) -> QAction:
method _create_simple_action (line 307) | def _create_simple_action(self, name: str, callback: Callable) -> QAct...
method _reset_to (line 315) | def _reset_to(self, period):
method select_period (line 318) | def select_period(self, period: str) -> None:
method _rotate (line 326) | def _rotate(lst: list, n: int) -> list:
method extract_data (line 329) | def extract_data(self, group: str, period_from: datetime.datetime, per...
method show (line 408) | def show(self):
FILE: src/fk/desktop/tutorial.py
function _get_row_position (line 40) | def _get_row_position(widget: QAbstractItemView, x: float, row: int, col...
class Tutorial (line 48) | class Tutorial:
method __init__ (line 56) | def __init__(self,
method _subscribe (line 89) | def _subscribe(self):
method _unsubscribe (line 94) | def _unsubscribe(self):
method _on_event (line 102) | def _on_event(self, event: str, **kwargs):
method _on_setting_changed (line 108) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _mark_completed (line 113) | def _mark_completed(self, step: str) -> None:
method _is_to_complete (line 125) | def _is_to_complete(self, step: str) -> bool:
method _before_source_changed (line 128) | def _before_source_changed(self, event: str, source: AbstractEventSour...
method _after_source_changed (line 135) | def _after_source_changed(self, event: str, source: AbstractEventSourc...
method _get_toolbar_button_position (line 140) | def _get_toolbar_button_position(self, action_name: str, arrow: str):
method _on_messages (line 154) | def _on_messages(self, complete: Callable, skip: Callable, **kwargs) -...
method _on_backlog_create (line 165) | def _on_backlog_create(self, complete: Callable, skip: Callable, **kwa...
method _on_backlog_rename (line 176) | def _on_backlog_rename(self, complete: Callable, skip: Callable, **kwa...
method _on_workitem_create (line 187) | def _on_workitem_create(self, complete: Callable, skip: Callable, **kw...
method _on_workitem_rename (line 199) | def _on_workitem_rename(self, complete: Callable, skip: Callable, work...
method _on_pomodoro_add (line 210) | def _on_pomodoro_add(self, complete: Callable, skip: Callable, workite...
method _on_pomodoro_remove (line 223) | def _on_pomodoro_remove(self, complete: Callable, skip: Callable, work...
method _on_pomodoro_work_start (line 235) | def _on_pomodoro_work_start(self, complete: Callable, skip: Callable, ...
method _on_pomodoro_complete (line 260) | def _on_pomodoro_complete(self, complete: Callable, skip: Callable, po...
method _on_workitem_complete (line 290) | def _on_workitem_complete(self, complete: Callable, skip: Callable, **...
FILE: src/fk/desktop/work_summary_window.py
function _format_date (line 38) | def _format_date(date: datetime.datetime):
function _format_duration (line 42) | def _format_duration(duration: datetime.timedelta):
class Formatter (line 46) | class Formatter(ABC):
method header (line 48) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 52) | def week(self, text: str) -> str:
method day (line 56) | def day(self, text: str) -> str:
method workitem_plaintext (line 59) | def workitem_plaintext(self, text: str, duration: datetime.timedelta =...
method workitem (line 67) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 71) | def footer(self) -> str:
class MarkdownFormatter (line 75) | class MarkdownFormatter(Formatter):
method header (line 76) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 79) | def week(self, text: str) -> str:
method day (line 82) | def day(self, text: str) -> str:
method workitem (line 85) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 88) | def footer(self) -> str:
class OrgModeFormatter (line 92) | class OrgModeFormatter(Formatter):
method header (line 93) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 100) | def week(self, text: str) -> str:
method day (line 103) | def day(self, text: str) -> str:
method workitem (line 106) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 109) | def footer(self) -> str:
class MarkdownTableFormatter (line 113) | class MarkdownTableFormatter(Formatter):
method __init__ (line 117) | def __init__(self):
method header (line 121) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 125) | def week(self, text: str) -> str:
method day (line 129) | def day(self, text: str) -> str:
method workitem (line 133) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 136) | def footer(self) -> str:
class PlaintextFormatter (line 140) | class PlaintextFormatter(Formatter):
method header (line 141) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 144) | def week(self, text: str) -> str:
method day (line 147) | def day(self, text: str) -> str:
method workitem (line 150) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 153) | def footer(self) -> str:
class CsvFormatter (line 157) | class CsvFormatter(Formatter):
method __init__ (line 161) | def __init__(self):
method header (line 165) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 178) | def week(self, text: str) -> str:
method day (line 182) | def day(self, text: str) -> str:
method workitem (line 186) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 195) | def footer(self) -> str:
class JsonFormatter (line 199) | class JsonFormatter(Formatter):
method __init__ (line 204) | def __init__(self):
method header (line 209) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 213) | def week(self, text: str) -> str:
method day (line 218) | def day(self, text: str) -> str:
method workitem (line 223) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 232) | def footer(self) -> str:
class XmlFormatter (line 236) | class XmlFormatter(Formatter):
method __init__ (line 241) | def __init__(self):
method header (line 246) | def header(self, include_durations: bool, include_backlogs: bool) -> str:
method week (line 249) | def week(self, text: str) -> str:
method day (line 255) | def day(self, text: str) -> str:
method workitem (line 261) | def workitem(self, text: str, duration: datetime.timedelta = None, bac...
method footer (line 273) | def footer(self) -> str:
class WorkSummaryWindow (line 278) | class WorkSummaryWindow(QObject):
method __init__ (line 289) | def __init__(self, parent: QWidget, source: AbstractEventSource):
method _save_settings (line 348) | def _save_settings(self):
method _load_settings (line 358) | def _load_settings(self):
method _extract_data (line 377) | def _extract_data(self) -> dict[datetime.date, dict[str, list[datetime...
method _display_formatted (line 401) | def _display_formatted(self) -> None:
method _format_data (line 408) | def _format_data(self, include_durations: bool, include_backlogs: bool...
method show (line 493) | def show(self):
method _get_file_extension (line 496) | def _get_file_extension(self) -> str:
method _export_to_file (line 514) | def _export_to_file(self, filename: str):
method _on_action (line 528) | def _on_action(self, role: QDialogButtonBox.ButtonRole):
FILE: src/fk/e2e/abstract_e2e_test.py
class AbstractE2eTest (line 46) | class AbstractE2eTest(ABC):
method __init__ (line 62) | def __init__(self, app: Application):
method _get_test_cases (line 77) | def _get_test_cases(self):
method _run (line 85) | async def _run(self):
method _update_log_for_method (line 127) | def _update_log_for_method(self, name: str, value: str):
method _append_to_system_out_for_method (line 131) | def _append_to_system_out_for_method(self, line: str):
method init_log (line 138) | def init_log(self) -> None:
method close_log (line 159) | def close_log(self) -> None:
method info (line 175) | def info(self, txt):
method error (line 179) | def error(self, e: Exception):
method _get_row_position (line 184) | def _get_row_position(self, widget: QAbstractItemView, row: int, col: ...
method mouse_click_row (line 187) | async def mouse_click_row(self, widget: QAbstractItemView, row: int, c...
method mouse_doubleclick_row (line 191) | async def mouse_doubleclick_row(self, widget: QAbstractItemView, row: ...
method mouse_click (line 195) | async def mouse_click(self, widget: QWidget, pos: QPoint, left_button:...
method mouse_doubleclick (line 218) | async def mouse_doubleclick(self, widget: QWidget, pos: QPoint = QPoin...
method keypress (line 229) | def keypress(self, key: int, ctrl: bool = False, widget: QWidget = None):
method close_modal (line 239) | def close_modal(self, ok: bool = True):
method type_text (line 248) | def type_text(self, text: str):
method get_focused (line 257) | def get_focused(self) -> QWidget:
method get_application (line 260) | def get_application(self) -> Application:
method window (line 263) | def window(self) -> QWidget:
method click_button (line 273) | def click_button(self, text: str = None, name: str = None):
method check_checkbox (line 280) | def check_checkbox(self, checked: bool = True, text: str = None, name:...
method check_radiobutton (line 287) | def check_radiobutton(self, checked: bool = True, text: str = None, na...
method execute_action (line 294) | def execute_action(self, name: str) -> None:
method is_action_enabled (line 298) | def is_action_enabled(self, name: str) -> bool:
method custom_settings (line 301) | def custom_settings(self) -> dict[str, str]:
method start (line 304) | def start(self) -> None:
method setup (line 307) | def setup(self) -> None:
method teardown (line 310) | def teardown(self) -> None:
method instant_pause (line 313) | async def instant_pause(self) -> None:
method longer_pause (line 316) | async def longer_pause(self) -> None:
method on_exception (line 323) | def on_exception(self, exc_type, exc_value, exc_trace):
method take_screenshot (line 327) | def take_screenshot(self, name: str):
method center_window (line 380) | def center_window(self):
FILE: src/fk/e2e/backlog_e2e.py
class BacklogE2eTest (line 19) | class BacklogE2eTest(AbstractE2eTest):
method __init__ (line 20) | def __init__(self, app: Application):
method custom_settings (line 23) | def custom_settings(self) -> dict[str, str]:
method teardown (line 39) | def teardown(self) -> None:
method _new_backlog (line 43) | async def _new_backlog(self, name: str) -> None:
method _start_pomodoro (line 50) | async def _start_pomodoro(self) -> None:
method _finish_tracking (line 54) | async def _finish_tracking(self) -> None:
method _wait_pomodoro_complete (line 58) | async def _wait_pomodoro_complete(self) -> None:
method _wait_mid_pomodoro (line 63) | async def _wait_mid_pomodoro(self) -> None:
method _complete_workitem (line 66) | async def _complete_workitem(self) -> None:
method _void_pomodoro (line 70) | async def _void_pomodoro(self) -> None:
method _add_pomodoro (line 76) | async def _add_pomodoro(self) -> None:
method _remove_pomodoro (line 80) | async def _remove_pomodoro(self) -> None:
method _new_workitem (line 84) | async def _new_workitem(self, name: str, pomodoros: int = 0) -> None:
method _find_workitem (line 93) | async def _find_workitem(self, name: str) -> None:
method _select_backlog (line 106) | async def _select_backlog(self, name: str) -> int:
method assert_actions_enabled (line 116) | def assert_actions_enabled(self, names: list[str]) -> None:
method assert_actions_disabled (line 120) | def assert_actions_disabled(self, names: list[str]) -> None:
method test_01_create_backlogs (line 124) | async def test_01_create_backlogs(self):
method test_02_actions_visibility (line 224) | async def test_02_actions_visibility(self):
method test_03_renames (line 400) | async def test_03_renames(self):
FILE: src/fk/e2e/screenshot.py
class Screenshot (line 31) | class Screenshot:
method __init__ (line 35) | def __init__(self):
method _check_method (line 57) | def _check_method(method: Callable[[str], None]) -> bool:
method take_screen (line 74) | def take_screen(self, name: str) -> str:
method take_window (line 82) | def take_window(self, name: str, window: QWidget) -> str:
method _take_scrot (line 97) | def _take_scrot(filename: str) -> None:
method _take_imagemagick1 (line 104) | def _take_imagemagick1(filename: str) -> None:
method _take_imagemagick2 (line 111) | def _take_imagemagick2(filename: str) -> None:
method _take_gnome_screenshot (line 119) | def _take_gnome_screenshot(filename: str) -> None:
method _take_flameshot (line 125) | def _take_flameshot(filename: str) -> None:
method _take_xwd (line 132) | def _take_xwd(filename: str) -> None:
method _take_xfce4_screenshooter (line 142) | def _take_xfce4_screenshooter(filename: str) -> None:
method _take_ksnip (line 149) | def _take_ksnip(filename: str) -> None:
method _take_spectacle (line 156) | def _take_spectacle(filename: str) -> None:
method _take_spectacle_active (line 165) | def _take_spectacle_active(filename: str) -> None:
method _take_screencapture (line 174) | def _take_screencapture(filename: str) -> None:
method _take_nircmd (line 179) | def _take_nircmd(filename: str) -> None:
method _take_powershell (line 185) | def _take_powershell(filename: str, window_id: int | None = None) -> N...
FILE: src/fk/e2e/screenshots_e2e.py
class ScreenshotE2eTest (line 28) | class ScreenshotE2eTest(AbstractE2eTest):
method __init__ (line 29) | def __init__(self, app: Application):
method setup (line 32) | def setup(self) -> None:
method custom_settings (line 40) | def custom_settings(self) -> dict[str, str]:
method teardown (line 70) | def teardown(self) -> None:
method _new_backlog (line 74) | async def _new_backlog(self, name: str) -> None:
method _start_pomodoro (line 81) | async def _start_pomodoro(self) -> None:
method _wait_pomodoro_complete (line 85) | async def _wait_pomodoro_complete(self) -> None:
method _wait_mid_pomodoro (line 90) | async def _wait_mid_pomodoro(self) -> None:
method _wait_long_pomodoro (line 93) | async def _wait_long_pomodoro(self) -> None:
method _complete_workitem (line 96) | async def _complete_workitem(self, name: str) -> None:
method _void_pomodoro (line 103) | async def _void_pomodoro(self, name: str) -> None:
method _stop_tracking (line 111) | async def _stop_tracking(self) -> None:
method _add_pomodoro (line 115) | async def _add_pomodoro(self) -> None:
method _remove_pomodoro (line 119) | async def _remove_pomodoro(self) -> None:
method _new_workitem (line 123) | async def _new_workitem(self, name: str, pomodoros: int = 0) -> None:
method _find_workitem (line 132) | async def _find_workitem(self, name: str) -> None:
method _select_backlog (line 145) | async def _select_backlog(self, name: str) -> int:
method _select_tag (line 156) | async def _select_tag(self, name: str) -> bool:
method test_01_screenshots (line 166) | async def test_01_screenshots(self):
method _generate_pomodoros_for_stats (line 481) | def _generate_pomodoros_for_stats(self):
method _emulate_year (line 491) | def _emulate_year(self, workitem: Workitem, start_date: datetime.datet...
method _emulate_day (line 497) | def _emulate_day(self, workitem: Workitem, start_date: datetime.dateti...
FILE: src/fk/qt/about_window.py
class AboutWindow (line 29) | class AboutWindow(QObject):
method __init__ (line 35) | def __init__(self, parent: QWidget | None):
method show (line 47) | def show(self):
method _handle_tick (line 92) | def _handle_tick(self, params: dict | None, when: datetime.datetime | ...
FILE: src/fk/qt/abstract_drop_model.py
class DropPlaceholderItem (line 35) | class DropPlaceholderItem(QStandardItem):
method __init__ (line 36) | def __init__(self, original: AbstractDataItem, original_display: str, ...
class AbstractDropModel (line 48) | class AbstractDropModel(QStandardItemModel):
method __init__ (line 52) | def __init__(self,
method supportedDropActions (line 60) | def supportedDropActions(self) -> Qt.DropAction:
method supportedDragActions (line 63) | def supportedDragActions(self) -> Qt.DropAction:
method move_drop_placeholder (line 66) | def move_drop_placeholder(self, index: QModelIndex | None):
method restore_order (line 91) | def restore_order(self) -> int:
method dropMimeData (line 100) | def dropMimeData(self, data: QMimeData, action: Qt.DropAction, row: in...
method get_primary_type (line 124) | def get_primary_type(self) -> str:
method get_secondary_type (line 127) | def get_secondary_type(self) -> str | None:
method reorder (line 131) | def reorder(self, to_index: int, uid: str):
method adopt_foreign_item (line 134) | def adopt_foreign_item(self, target: AbstractDataItem, uid: str) -> bool:
method handle_rename (line 137) | def handle_rename(self, item: QStandardItem, strategy_class: type[Abst...
method canDropMimeData (line 155) | def canDropMimeData(self, data: QMimeData, action: Qt.DropAction, row:...
method mimeTypes (line 158) | def mimeTypes(self):
method mimeData (line 162) | def mimeData(self, indexes):
method item_for_object (line 173) | def item_for_object(self, obj: AbstractDataItem) -> list[QStandardItem]:
FILE: src/fk/qt/abstract_item_delegate.py
function get_padding (line 22) | def get_padding(option: QStyleOptionViewItem) -> int:
class AbstractItemDelegate (line 26) | class AbstractItemDelegate(QItemDelegate):
method __init__ (line 31) | def __init__(self,
method paint_background (line 41) | def paint_background(self, painter: QPainter, option: QStyleOptionView...
FILE: src/fk/qt/abstract_tableview.py
class AbstractTableView (line 41) | class AbstractTableView(QTableView, AbstractEventEmitter, Generic[TUpstr...
method __init__ (line 52) | def __init__(self,
method _on_setting_changed (line 100) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _update_row_height (line 104) | def _update_row_height(self, new_height: int):
method _on_source_changed (line 108) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method _on_data_loaded (line 114) | def _on_data_loaded(self, event: str, source: AbstractEventSource) -> ...
method define_actions (line 120) | def define_actions(actions: Actions):
method upstream_selected (line 123) | def upstream_selected(self, upstream: TUpstream | None) -> None:
method get_current (line 132) | def get_current(self) -> TDownstream | None:
method update_actions (line 138) | def update_actions(self, selected: TDownstream | None) -> None:
method _on_current_changed (line 141) | def _on_current_changed(self, selected: QModelIndex | None, deselected...
method paintEvent (line 158) | def paintEvent(self, e):
method select (line 185) | def select(self, data: TDownstream) -> QModelIndex:
method deselect (line 199) | def deselect(self) -> None:
method dragLeaveEvent (line 210) | def dragLeaveEvent(self, event: QDragLeaveEvent):
method dragEnterEvent (line 217) | def dragEnterEvent(self, event: QDragEnterEvent):
method dragMoveEvent (line 225) | def dragMoveEvent(self, event: QDragMoveEvent):
FILE: src/fk/qt/actions.py
function update_toggle_action_icon (line 28) | def update_toggle_action_icon(icon1: str, icon2: str, action: QAction) -...
class Actions (line 35) | class Actions:
method __init__ (line 43) | def __init__(self, window: QWidget, settings: AbstractSettings):
method update_from_settings (line 51) | def update_from_settings(self, serialized: str):
method add (line 59) | def add(self,
method _call (line 99) | def _call(self, name: str, member: Callable, checked: bool = None):
method bind (line 109) | def bind(self, domain: str, obj: object):
method __getitem__ (line 112) | def __getitem__(self, name: str) -> QAction:
method __contains__ (line 115) | def __contains__(self, name: str) -> bool:
method __iter__ (line 118) | def __iter__(self) -> Iterable[str]:
method __len__ (line 121) | def __len__(self) -> int:
method values (line 124) | def values(self) -> Iterable[QAction]:
method keys (line 127) | def keys(self) -> Iterable[str]:
method get_settings (line 130) | def get_settings(self):
method all_actions_defined (line 133) | def all_actions_defined(self) -> None:
method _on_theme_change (line 140) | def _on_theme_change(self, icon1: str, icon2: str, action: QAction):
FILE: src/fk/qt/app_version.py
function get_current_version (line 31) | def get_current_version() -> Version:
function _success (line 44) | def _success(reply: QNetworkReply, callback: Callable[[Version, str], No...
function _error (line 60) | def _error(err: QNetworkReply.NetworkError, callback: Callable[[Version,...
function get_latest_version (line 65) | def get_latest_version(parent: QObject, callback: Callable[[Version], No...
FILE: src/fk/qt/audio_player.py
class AudioPlayer (line 34) | class AudioPlayer(QObject):
method __init__ (line 40) | def __init__(self,
method _on_source_changed (line 51) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method _on_setting_changed (line 59) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _reset (line 71) | def _reset(self):
method _set_volume (line 93) | def _set_volume(self, setting: str):
method _play_audio (line 109) | def _play_audio(self, event: str, pomodoro: Pomodoro, timer: TimerData...
method _start_ticking (line 141) | def _start_ticking(self, event: str = None, **kwargs) -> None:
method _seek_when_ready (line 156) | def _seek_when_ready(self, elapsed_ms: int = 0):
method _start_rest_sound (line 173) | def _start_rest_sound(self, pomodoro: Pomodoro) -> None:
method _start_what_is_needed (line 192) | def _start_what_is_needed(self) -> None:
FILE: src/fk/qt/backlog_model.py
class BacklogItem (line 43) | class BacklogItem(QStandardItem):
method __init__ (line 46) | def __init__(self, backlog: Backlog):
method update_display (line 61) | def update_display(self):
method update_font (line 64) | def update_font(self):
method __lt__ (line 68) | def __lt__(self, other: BacklogItem):
class BacklogModel (line 72) | class BacklogModel(AbstractDropModel):
method __init__ (line 75) | def __init__(self,
method _on_source_changed (line 84) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method _backlog_added (line 91) | def _backlog_added(self, backlog: Backlog, **kwargs) -> None:
method _backlog_removed (line 94) | def _backlog_removed(self, backlog: Backlog, **kwargs) -> None:
method _backlog_renamed (line 101) | def _backlog_renamed(self, backlog: Backlog, **kwargs) -> None:
method _schedule_at_midnight (line 108) | def _schedule_at_midnight(self):
method _at_midnight (line 117) | def _at_midnight(self, params: dict | None, when: datetime.datetime | ...
method _backlog_reordered (line 123) | def _backlog_reordered(self, backlog: Backlog, new_index: int, carry: ...
method load (line 135) | def load(self, user: User | None) -> None:
method get_primary_type (line 142) | def get_primary_type(self) -> str:
method get_secondary_type (line 145) | def get_secondary_type(self) -> str:
method item_for_object (line 148) | def item_for_object(self, backlog: Backlog) -> list[QStandardItem]:
method reorder (line 151) | def reorder(self, to_index: int, uid: str):
method adopt_foreign_item (line 157) | def adopt_foreign_item(self, backlog: Backlog, workitem_uid: str) -> b...
FILE: src/fk/qt/backlog_tableview.py
class BacklogTableView (line 43) | class BacklogTableView(AbstractTableView[User, Backlog]):
method __init__ (line 47) | def __init__(self,
method _lock_ui (line 69) | def _lock_ui(self, event, after: int, last_received: datetime.datetime...
method _unlock_ui (line 72) | def _unlock_ui(self, event, ping: int) -> None:
method _on_source_changed (line 75) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method _init_menu (line 95) | def _init_menu(self, actions: Actions) -> QMenu:
method upstream_selected (line 109) | def upstream_selected(self, user: User) -> None:
method _update_actions_if_needed (line 114) | def _update_actions_if_needed(self, workitem: Workitem):
method update_actions (line 121) | def update_actions(self, selected: Backlog) -> None:
method _on_new_backlog (line 146) | def _on_new_backlog(self, backlog: Backlog, carry: any = None, **kwargs):
method _on_messages (line 153) | def _on_messages(self, event: str, source: AbstractEventSource) -> None:
method define_actions (line 161) | def define_actions(actions: Actions):
method create_backlog (line 190) | def create_backlog(self) -> str:
method create_backlog_from_incomplete (line 197) | def create_backlog_from_incomplete(self) -> str:
method rename_selected_backlog (line 225) | def rename_selected_backlog(self) -> None:
method delete_selected_backlog (line 231) | def delete_selected_backlog(self) -> None:
method dump_selected_backlog (line 243) | def dump_selected_backlog(self) -> None:
FILE: src/fk/qt/backlog_widget.py
class BacklogWidget (line 31) | class BacklogWidget(QWidget):
method __init__ (line 37) | def __init__(self,
method _on_setting_changed (line 73) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _on_selection (line 77) | def _on_selection(self, backlog_or_tag: Backlog | Tag):
method get_table (line 84) | def get_table(self) -> BacklogTableView:
method get_tags (line 87) | def get_tags(self) -> TagsWidget:
FILE: src/fk/qt/configurable_toolbar.py
class ConfigurableToolBar (line 25) | class ConfigurableToolBar(QToolBar):
method __init__ (line 28) | def __init__(self, parent: QWidget, actions: Actions, name: str):
method _on_setting_changed (line 38) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _hide (line 42) | def _hide(self, pos: QPoint):
method mousePressEvent (line 52) | def mousePressEvent(self, event: QMouseEvent) -> None:
method get_button_geometry (line 63) | def get_button_geometry(self, action_name: str) -> QRect | None:
FILE: src/fk/qt/connection_widget.py
class ConnectionWidget (line 29) | class ConnectionWidget(QToolButton):
method __init__ (line 33) | def __init__(self, parent: QWidget, application: Application):
method _update_connection_state (line 41) | def _update_connection_state(self, is_connected: bool) -> None:
method _on_source_changed (line 52) | def _on_source_changed(self, event: str, source: AbstractEventSource):
FILE: src/fk/qt/flow_layout.py
class FlowLayout (line 9) | class FlowLayout(QLayout):
method __init__ (line 10) | def __init__(self, parent=None):
method __del__ (line 14) | def __del__(self):
method addItem (line 19) | def addItem(self, item):
method count (line 22) | def count(self):
method itemAt (line 25) | def itemAt(self, index):
method takeAt (line 30) | def takeAt(self, index):
method removeWidget (line 35) | def removeWidget(self, widget: QWidget):
method widgets (line 43) | def widgets(self):
method expandingDirections (line 46) | def expandingDirections(self):
method hasHeightForWidth (line 49) | def hasHeightForWidth(self):
method heightForWidth (line 52) | def heightForWidth(self, width):
method setGeometry (line 56) | def setGeometry(self, rect):
method sizeHint (line 60) | def sizeHint(self):
method minimumSize (line 63) | def minimumSize(self):
method _do_layout (line 72) | def _do_layout(self, rect, test_only):
FILE: src/fk/qt/focus_widget.py
function complete_item (line 44) | def complete_item(item: Workitem, parent: QWidget, source: AbstractEvent...
class FocusWidget (line 60) | class FocusWidget(QWidget, AbstractTimerDisplay):
method __init__ (line 75) | def __init__(self,
method set_flavor (line 136) | def set_flavor(self, flavor):
method kill (line 216) | def kill(self):
method _initialize_hint_label (line 221) | def _initialize_hint_label(self) -> QLabel:
method update_fonts (line 227) | def update_fonts(self):
method define_actions (line 231) | def define_actions(actions: Actions):
method _create_button (line 238) | def _create_button(self,
method reset (line 251) | def reset(self, text: str = 'Idle', subtext: str = "It's time for the ...
method eye_candy (line 264) | def eye_candy(self):
method paintEvent (line 274) | def paintEvent(self, event):
method _update_colors (line 297) | def _update_colors(self):
method _on_setting_changed (line 303) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _on_fonts_changed (line 313) | def _on_fonts_changed(self, event, header_font, **kwargs):
method _void_pomodoro (line 316) | def _void_pomodoro(self) -> None:
method _interruption (line 341) | def _interruption(self) -> None:
method _finish_tracking (line 364) | def _finish_tracking(self) -> None:
method _next_pomodoro (line 368) | def _next_pomodoro(self) -> None:
method _complete_item (line 373) | def _complete_item(self) -> None:
method tick (line 377) | def tick(self, pomodoro: Pomodoro, state_text: str, my_value: float, m...
method mode_changed (line 381) | def mode_changed(self, old_mode: str, new_mode: str) -> None:
method _apply_size_policy (line 430) | def _apply_size_policy(self):
method _timer_clicked (line 437) | def _timer_clicked(self, pos: QPoint) -> None:
method mousePressEvent (line 457) | def mousePressEvent(self, event: QMouseEvent) -> None:
method mouseMoveEvent (line 460) | def mouseMoveEvent(self, event: QMouseEvent) -> None:
method mouseReleaseEvent (line 464) | def mouseReleaseEvent(self, event: QMouseEvent) -> None:
method mouseDoubleClickEvent (line 467) | def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
FILE: src/fk/qt/heartbeat.py
class Heartbeat (line 29) | class Heartbeat(AbstractEventEmitter):
method __init__ (line 41) | def __init__(self, source_holder: EventSourceHolder, every_ms: int, th...
method _reset (line 52) | def _reset(self) -> None:
method _on_source_changed (line 61) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method start (line 67) | def start(self, event) -> None:
method _send_ping (line 74) | def _send_ping(self, params: dict | None, when: datetime.datetime | No...
method _on_pong (line 89) | def _on_pong(self, event, uid, carry) -> None:
method stop (line 107) | def stop(self) -> None:
method is_online (line 110) | def is_online(self) -> bool:
method is_offline (line 113) | def is_offline(self) -> bool:
method get_last_ping (line 117) | def get_last_ping(self) -> int:
FILE: src/fk/qt/info_overlay.py
class InfoOverlay (line 23) | class InfoOverlay(QFrame):
method __init__ (line 28) | def __init__(self,
method eventFilter (line 99) | def eventFilter(self, watched: QObject, event: QEvent) -> bool:
method get_text (line 105) | def get_text(self):
method mousePressEvent (line 108) | def mousePressEvent(self, event: QMouseEvent) -> None:
method close (line 111) | def close(self):
class InfoOverlayContent (line 123) | class InfoOverlayContent(QWidget):
method __init__ (line 124) | def __init__(self,
function show_info_overlay (line 193) | def show_info_overlay(parent,
function show_tutorial (line 214) | def show_tutorial(parent,
function show_tutorial_overlay (line 248) | def show_tutorial_overlay(parent,
FILE: src/fk/qt/oauth.py
class AuthenticationRecord (line 37) | class AuthenticationRecord:
method __str__ (line 44) | def __str__(self):
function _fix_parameters (line 52) | def _fix_parameters(stage, parameters):
function authenticate (line 64) | def authenticate(parent: QObject, callback: Callable[[AuthenticationReco...
function get_id_token (line 69) | def get_id_token(parent: QObject, callback: Callable[[AuthenticationReco...
function open_url (line 74) | def open_url(url: QUrl | str) -> None:
function _perform_flow (line 81) | def _perform_flow(parent: QObject, callback: Callable[[AuthenticationRec...
function _extract_email (line 106) | def _extract_email(id_token: str) -> str:
function _error (line 113) | def _error(err, flow: QOAuth2AuthorizationCodeFlow, callback: Callable[[...
function _granted (line 117) | def _granted(flow: QOAuth2AuthorizationCodeFlow, callback: Callable[[Aut...
FILE: src/fk/qt/pomodoro_delegate.py
class PomodoroDelegate (line 36) | class PomodoroDelegate(AbstractItemDelegate):
method _get_renderer (line 43) | def _get_renderer(self, name):
method __init__ (line 48) | def __init__(self,
method paint (line 66) | def paint(self, painter: QPainter, option: QStyleOptionViewItem, index...
FILE: src/fk/qt/progress_widget.py
class ProgressWidget (line 26) | class ProgressWidget(QWidget):
method __init__ (line 29) | def __init__(self,
method _on_source_changed (line 43) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method update_progress (line 51) | def update_progress(self, backlog_or_tag: Backlog | Tag | None) -> None:
FILE: src/fk/qt/qt_filesystem_watcher.py
class QtFilesystemWatcher (line 23) | class QtFilesystemWatcher(AbstractFilesystemWatcher):
method __init__ (line 27) | def __init__(self):
method watch (line 32) | def watch(self, filename: str, callback: Callable[[str], None]):
method unwatch (line 38) | def unwatch(self, filename: str) -> None:
method unwatch_all (line 43) | def unwatch_all(self) -> None:
method _on_file_change (line 47) | def _on_file_change(self, filename: str) -> None:
FILE: src/fk/qt/qt_invoker.py
class InvokeEvent (line 19) | class InvokeEvent(QEvent):
method __init__ (line 22) | def __init__(self, fn, **kwargs):
class Invoker (line 28) | class Invoker(QObject):
method event (line 29) | def event(self, e):
function invoke_in_main_thread (line 37) | def invoke_in_main_thread(fn, **kwargs):
FILE: src/fk/qt/qt_settings.py
function _check_keyring (line 37) | def _check_keyring() -> bool:
class QtSettings (line 41) | class QtSettings(AbstractSettings):
method __init__ (line 47) | def __init__(self, app_name: str = 'flowkeeper-desktop'):
method _display_warning_if_needed (line 77) | def _display_warning_if_needed(self) -> None:
method _disable_connected_sources (line 97) | def _disable_connected_sources(self) -> None:
method _disable_secrets (line 107) | def _disable_secrets(self) -> None:
method set (line 117) | def set(self, values: dict[str, str], force_fire=False) -> None:
method load_secret (line 153) | def load_secret(self) -> dict[str, str]:
method get (line 157) | def get(self, name: str) -> str:
method is_set (line 172) | def is_set(self, name: str) -> bool:
method location (line 182) | def location(self) -> str:
method clear (line 185) | def clear(self) -> None:
method is_keyring_enabled (line 193) | def is_keyring_enabled(self) -> bool:
method get_auto_theme (line 196) | def get_auto_theme(self) -> str:
method init_audio_outputs (line 203) | def init_audio_outputs(self):
method init_gradients (line 219) | def init_gradients(self):
method init_fonts (line 238) | def init_fonts(self):
method init_appearance (line 245) | def init_appearance(self):
method init_network_access (line 258) | def init_network_access(self):
FILE: src/fk/qt/qt_timer.py
class QExtendedTimer (line 28) | class QExtendedTimer(QTimer):
method __init__ (line 29) | def __init__(self, parent: QObject | None = None):
method customEvent (line 32) | def customEvent(self, event: QEvent) -> None:
method schedule_start (line 36) | def schedule_start(self, ms: float) -> None:
class QtTimer (line 45) | class QtTimer(AbstractTimer):
method __init__ (line 52) | def __init__(self, name: str, parent: QObject | None = None):
method _call (line 59) | def _call(self) -> None:
method schedule (line 66) | def schedule(self,
method cancel (line 79) | def cancel(self):
FILE: src/fk/qt/render/abstract_timer_renderer.py
function rotate_point (line 28) | def rotate_point(x: float, y: float, cx: float, cy: float, phi: float) -...
class AbstractTimerRenderer (line 35) | class AbstractTimerRenderer(QObject):
method __init__ (line 47) | def __init__(self,
method set_colors (line 63) | def set_colors(self, bg_color: QColor, fg_color: QColor):
method reset (line 68) | def reset(self) -> None:
method set_values (line 75) | def set_values(self,
method paint (line 88) | def paint(self, painter: QPainter, rect: QRect) -> None:
method has_idle_display (line 92) | def has_idle_display(self) -> bool:
method has_next_display (line 96) | def has_next_display(self) -> bool:
method eventFilter (line 99) | def eventFilter(self, widget: QtWidgets.QWidget, event: QtCore.QEvent)...
method repaint (line 109) | def repaint(self, painter: QPainter = None, rect: QRect = None) -> None:
method get_mode (line 115) | def get_mode(self):
FILE: src/fk/qt/render/classic_timer_renderer.py
class ClassicTimerRenderer (line 25) | class ClassicTimerRenderer(AbstractTimerRenderer):
method __init__ (line 26) | def __init__(self,
method clip (line 34) | def clip(self, painter: QtGui.QPainter, rect: QtCore.QRectF | None, en...
method clear_pie_outline (line 44) | def clear_pie_outline(self, painter: QtGui.QPainter, rect: QtCore.QRec...
method draw_sector (line 50) | def draw_sector(self,
method draw_points (line 72) | def draw_points(self,
method has_idle_display (line 86) | def has_idle_display(self) -> bool:
method has_next_display (line 89) | def has_next_display(self) -> bool:
method paint (line 92) | def paint(self, painter: QtGui.QPainter, rect: QtCore.QRectF) -> None:
FILE: src/fk/qt/render/minimal_timer_renderer.py
class MinimalTimerRenderer (line 25) | class MinimalTimerRenderer(AbstractTimerRenderer):
method __init__ (line 26) | def __init__(self,
method _dial_pen (line 34) | def _dial_pen(self, th: float) -> QPen:
method _next_brush (line 61) | def _next_brush(self) -> QBrush:
method _hand_pen (line 72) | def _hand_pen(self, th: float) -> QPen:
method has_idle_display (line 77) | def has_idle_display(self) -> bool:
method has_next_display (line 80) | def has_next_display(self) -> bool:
method paint (line 83) | def paint(self, painter: QtGui.QPainter, rect: QtCore.QRect) -> None:
FILE: src/fk/qt/resize_event_filter.py
class ResizeEventFilter (line 24) | class ResizeEventFilter(QMainWindow):
method __init__ (line 32) | def __init__(self,
method resize_completed (line 50) | def resize_completed(self):
method eventFilter (line 65) | def eventFilter(self, widget: QObject, event: QEvent) -> bool:
method restore_size (line 77) | def restore_size(self) -> None:
method save_splitter_size (line 84) | def save_splitter_size(self, new_width: int, index: int) -> None:
FILE: src/fk/qt/search_completer.py
class SearchBar (line 29) | class SearchBar(QtWidgets.QLineEdit):
method __init__ (line 36) | def __init__(self,
method _on_source_changed (line 55) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method _select (line 59) | def _select(self, index: QModelIndex):
method show (line 70) | def show(self) -> None:
method eventFilter (line 93) | def eventFilter(self, widget: QtCore.QObject, event: QtCore.QEvent) ->...
method hide_completed (line 99) | def hide_completed(self, hide: bool) -> None:
FILE: src/fk/qt/tags_widget.py
class TagsWidget (line 31) | class TagsWidget(QFrame, AbstractEventEmitter):
method __init__ (line 36) | def __init__(self, parent: QWidget, application: Application):
method _add_tag (line 52) | def _add_tag(self, tag: Tag, event: str = None, carry: any = None) -> ...
method deselect (line 62) | def deselect(self) -> None:
method _on_tag_toggled (line 69) | def _on_tag_toggled(self, widget: QPushButton, is_checked: bool, tag: ...
method _delete_tag (line 99) | def _delete_tag(self, tag: Tag, event: str, carry: any = None) -> None:
method _find_tag (line 116) | def _find_tag(self, uid: str) -> Tag:
method update_visibility (line 119) | def update_visibility(self, from_settings: bool = None) -> None:
method _init_tags (line 130) | def _init_tags(self, source: AbstractEventSource, event: str = None) -...
method _on_source_changed (line 138) | def _on_source_changed(self, event: str, source: AbstractEventSource):
FILE: src/fk/qt/theme_change_event_filter.py
class ThemeChangeEventFilter (line 27) | class ThemeChangeEventFilter(QMainWindow):
method __init__ (line 35) | def __init__(self,
method eventFilter (line 43) | def eventFilter(self, widget: QObject, event: QEvent) -> bool:
FILE: src/fk/qt/threaded_event_source.py
class ThreadedEventSource (line 36) | class ThreadedEventSource(AbstractEventSource[TRoot]):
method __init__ (line 41) | def __init__(self, wrapped: AbstractEventSource[TRoot], app: 'Applicat...
method start (line 47) | def start(self, mute_events=True) -> None:
method get_data (line 62) | def get_data(self) -> TRoot:
method get_name (line 65) | def get_name(self) -> str:
method _append (line 68) | def _append(self, strategies: list[AbstractStrategy]) -> None:
method clone (line 71) | def clone(self, new_root: TRoot) -> ThreadedEventSource[TRoot]:
method on (line 74) | def on(self, event_pattern: str, callback: Callable, last: bool = Fals...
method unsubscribe (line 77) | def unsubscribe(self, callback: Callable) -> None:
method unsubscribe_one (line 80) | def unsubscribe_one(self, callback: Callable, event_pattern: str) -> N...
method cancel (line 83) | def cancel(self, event_pattern: str) -> None:
method unmute (line 86) | def unmute(self) -> None:
method mute (line 89) | def mute(self) -> None:
method get_config_parameter (line 92) | def get_config_parameter(self, name: str) -> str:
method set_config_parameters (line 95) | def set_config_parameters(self, values: dict[str, str]) -> None:
method execute (line 98) | def execute(self,
method execute_prepared_strategy (line 108) | def execute_prepared_strategy(self,
method users (line 114) | def users(self) -> Iterable[User]:
method backlogs (line 117) | def backlogs(self) -> Iterable[Backlog]:
method tags (line 120) | def tags(self) -> Iterable[Tag]:
method workitems (line 123) | def workitems(self) -> Iterable[Workitem]:
method pomodoros (line 126) | def pomodoros(self) -> Iterable[Pomodoro]:
method find_workitem (line 129) | def find_workitem(self, uid: str) -> Workitem | None:
method find_tag (line 132) | def find_tag(self, uid: str) -> Tag | None:
method find_backlog (line 135) | def find_backlog(self, uid: str) -> Backlog | None:
method find_user (line 138) | def find_user(self, identity: str) -> User | None:
method disconnect (line 141) | def disconnect(self):
method send_ping (line 144) | def send_ping(self) -> str | None:
method can_connect (line 147) | def can_connect(self):
method repair (line 150) | def repair(self) -> tuple[list[str], str | None]:
method compress (line 153) | def compress(self):
method get_last_sequence (line 156) | def get_last_sequence(self):
method get_init_strategy (line 159) | def get_init_strategy(self, emit: Callable[[str, dict[str, any], any],...
FILE: src/fk/qt/timer_widget.py
class TimerWidget (line 28) | class TimerWidget(QWidget):
method __init__ (line 36) | def __init__(self,
method _init_renderer (line 72) | def _init_renderer(self, flavor):
method _init_timer_display (line 87) | def _init_timer_display(self):
method fg_color (line 92) | def fg_color(self):
method bg_color (line 96) | def bg_color(self):
method fg_color (line 100) | def fg_color(self, new_fg_color):
method bg_color (line 106) | def bg_color(self, new_bg_color):
method reset (line 111) | def reset(self):
method set_values (line 115) | def set_values(self,
method get_last_values (line 131) | def get_last_values(self) -> dict():
method mousePressEvent (line 134) | def mousePressEvent(self, event: QMouseEvent) -> None:
FILE: src/fk/qt/tray_icon.py
class TrayIcon (line 32) | class TrayIcon(QSystemTrayIcon, AbstractTimerDisplay):
method __init__ (line 41) | def __init__(self,
method _initialize_menu (line 73) | def _initialize_menu(self):
method kill (line 90) | def kill(self):
method reset (line 94) | def reset(self):
method _tray_clicked (line 102) | def _tray_clicked(self) -> None:
method paint (line 111) | def paint(self) -> None:
method tick (line 120) | def tick(self, pomodoro: Pomodoro, state_text: str, my_value: float, m...
method mode_changed (line 125) | def mode_changed(self, old_mode: str, new_mode: str) -> None:
FILE: src/fk/qt/user_model.py
class UserModel (line 28) | class UserModel(QtGui.QStandardItemModel):
method __init__ (line 32) | def __init__(self, parent: QtCore.QObject, source_holder: EventSourceH...
method _on_source_changed (line 39) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method _on_messages (line 45) | def _on_messages(self, event: str, source: AbstractEventSource) -> None:
method _user_added (line 48) | def _user_added(self, event: str, user: User) -> None:
method _user_removed (line 53) | def _user_removed(self, event: str, user: User) -> None:
method _user_renamed (line 60) | def _user_renamed(self, event: str, user: User, old_name: str, new_nam...
method set_row (line 67) | def set_row(self, i: int, user: User) -> None:
method load (line 86) | def load(self, app: Tenant) -> None:
FILE: src/fk/qt/user_tableview.py
class UserTableView (line 29) | class UserTableView(AbstractTableView[Tenant, User]):
method __init__ (line 30) | def __init__(self,
method _on_source_changed (line 47) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method update_actions (line 53) | def update_actions(self, selected: User) -> None:
method define_actions (line 57) | def define_actions(actions: Actions):
method upstream_selected (line 60) | def upstream_selected(self, upstream: Tenant) -> None:
method _on_messages (line 64) | def _on_messages(self, event: str, source: AbstractEventSource) -> None:
FILE: src/fk/qt/websocket_event_source.py
class WebsocketEventSource (line 45) | class WebsocketEventSource(AbstractEventSource[TRoot]):
method __init__ (line 54) | def __init__(self,
method _on_error (line 79) | def _on_error(self, s: str, e: enum) -> None:
method _connection_lost (line 83) | def _connection_lost(self) -> None:
method connect (line 94) | def connect(self) -> None:
method start (line 108) | def start(self, mute_events=True) -> None:
method _on_message (line 113) | def _on_message(self, message: str) -> None:
method _authenticate_with_google_and_replay (line 159) | def _authenticate_with_google_and_replay(self) -> None:
method _replay_after_auth (line 163) | def _replay_after_auth(self, auth: AuthenticationRecord) -> None:
method replay (line 190) | def replay(self) -> None:
method _append (line 208) | def _append(self, strategies: list[AbstractStrategy]) -> None:
method get_name (line 215) | def get_name(self) -> str:
method get_data (line 218) | def get_data(self) -> TRoot:
method clone (line 221) | def clone(self, new_root: TRoot) -> WebsocketEventSource[TRoot]:
method disconnect (line 227) | def disconnect(self):
method send_ping (line 231) | def send_ping(self) -> str | None:
method can_connect (line 245) | def can_connect(self):
method repair (line 248) | def repair(self) -> tuple[list[str], str | None]:
FILE: src/fk/qt/workitem_model.py
class WorkitemPlanned (line 38) | class WorkitemPlanned(QStandardItem):
method __init__ (line 41) | def __init__(self, workitem: Workitem, font: QtGui.QFont):
method update_planned (line 52) | def update_planned(self):
method update_font (line 56) | def update_font(self, font: QtGui.QFont):
class WorkitemTitle (line 60) | class WorkitemTitle(QStandardItem):
method __init__ (line 63) | def __init__(self, workitem: Workitem, font: QtGui.QFont):
method update_display (line 72) | def update_display(self):
method update_flags (line 76) | def update_flags(self):
method update_font (line 84) | def update_font(self, font: QtGui.QFont):
function hhmm (line 88) | def hhmm(when: datetime.datetime) -> str:
class WorkitemPomodoro (line 92) | class WorkitemPomodoro(QStandardItem):
method __init__ (line 96) | def __init__(self, workitem: Workitem, row_height: int):
method _list_interruptions (line 107) | def _list_interruptions(self, pomodoro: Pomodoro, res: list[str]) -> N...
method _format_tooltip (line 113) | def _format_tooltip(self) -> str:
method update_display (line 150) | def update_display(self):
class WorkitemModel (line 170) | class WorkitemModel(AbstractDropModel):
method __init__ (line 178) | def __init__(self, parent: QtWidgets.QWidget, source_holder: EventSour...
method _on_setting_changed (line 193) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _update_row_height (line 197) | def _update_row_height(self, new_height: int):
method _on_source_changed (line 207) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method _workitem_belongs_here (line 221) | def _workitem_belongs_here(self, workitem: Workitem) -> bool:
method _add_workitem (line 226) | def _add_workitem(self, workitem: Workitem) -> None:
method _find_workitem (line 229) | def _find_workitem(self, workitem: Workitem) -> int:
method _remove_if_found (line 236) | def _remove_if_found(self, workitem: Workitem) -> None:
method _workitem_created (line 241) | def _workitem_created(self, workitem: Workitem, **kwargs) -> None:
method _workitem_deleted (line 245) | def _workitem_deleted(self, workitem: Workitem, **kwargs) -> None:
method _workitem_renamed (line 249) | def _workitem_renamed(self, workitem: Workitem, old_name: str, new_nam...
method _workitem_reordered (line 260) | def _workitem_reordered(self, workitem: Workitem, new_index: int, carr...
method _workitem_moved (line 271) | def _workitem_moved(self, workitem: Workitem, old_backlog: Backlog, ne...
method _workitem_changed (line 279) | def _workitem_changed(self, workitem: Workitem, **kwargs) -> None:
method get_row_height (line 300) | def get_row_height(self):
method load (line 303) | def load(self, backlog_or_tag: Backlog | Tag) -> None:
method hide_completed (line 321) | def hide_completed(self, hide: bool) -> None:
method get_backlog_or_tag (line 325) | def get_backlog_or_tag(self) -> Backlog | Tag | None:
method get_primary_type (line 328) | def get_primary_type(self) -> str:
method _get_font (line 331) | def _get_font(self, workitem: Workitem) -> QtGui.QFont:
method item_for_object (line 338) | def item_for_object(self, workitem: Workitem) -> list[QStandardItem]:
method reorder (line 346) | def reorder(self, to_index: int, uid: str):
method repaint_workitem (line 364) | def repaint_workitem(self, workitem: Workitem):
FILE: src/fk/qt/workitem_state_delegate.py
class WorkitemStateDelegate (line 25) | class WorkitemStateDelegate(AbstractItemDelegate):
method __init__ (line 28) | def __init__(self,
method paint (line 38) | def paint(self, painter: QPainter, option: QStyleOptionViewItem, index...
FILE: src/fk/qt/workitem_tableview.py
class WorkitemTableView (line 45) | class WorkitemTableView(AbstractTableView[Backlog | Tag, Workitem]):
method __init__ (line 49) | def __init__(self,
method _on_setting_changed (line 75) | def _on_setting_changed(self, event: str, old_values: dict[str, str], ...
method _is_tags_enabled (line 80) | def _is_tags_enabled(self) -> bool:
method _configure_delegate (line 83) | def _configure_delegate(self):
method _update_actions_if_needed (line 119) | def _update_actions_if_needed(self, workitem: Workitem):
method _on_source_changed (line 124) | def _on_source_changed(self, event: str, source: AbstractEventSource) ...
method _init_menu (line 137) | def _init_menu(self, actions: Actions) -> QMenu:
method define_actions (line 154) | def define_actions(actions: Actions):
method upstream_selected (line 170) | def upstream_selected(self, backlog_or_tag: Backlog | Tag | None) -> N...
method update_actions (line 176) | def update_actions(self, selected: Workitem | None) -> None:
method create_workitem (line 194) | def create_workitem(self) -> None:
method _on_new_workitem (line 215) | def _on_new_workitem(self, workitem: Workitem, **kwargs):
method rename_selected_workitem (line 220) | def rename_selected_workitem(self) -> None:
method delete_selected_workitem (line 226) | def delete_selected_workitem(self) -> None:
method start_selected_workitem (line 238) | def start_selected_workitem(self) -> None:
method complete_selected_workitem (line 244) | def complete_selected_workitem(self) -> None:
method add_pomodoro (line 248) | def add_pomodoro(self) -> None:
method remove_pomodoro (line 258) | def remove_pomodoro(self) -> None:
method _toggle_hide_completed_workitems (line 267) | def _toggle_hide_completed_workitems(self, checked: bool) -> None:
method _resize (line 272) | def _resize(self) -> None:
method _on_tick (line 281) | def _on_tick(self, timer: TimerData, counter: int, event: str) -> None:
FILE: src/fk/qt/workitem_text_delegate.py
class WorkitemTextDelegate (line 29) | class WorkitemTextDelegate(AbstractItemDelegate):
method __init__ (line 32) | def __init__(self,
method _format_html (line 41) | def _format_html(self, workitem: Workitem, is_placeholder: bool) -> str:
method paint (line 50) | def paint(self, painter: QPainter, option: QStyleOptionViewItem, index...
method sizeHint (line 66) | def sizeHint(self, option, index) -> QSize:
FILE: src/fk/qt/workitem_widget.py
class WorkitemWidget (line 34) | class WorkitemWidget(QWidget):
method __init__ (line 38) | def __init__(self,
method get_table (line 74) | def get_table(self) -> WorkitemTableView:
method upstream_selected (line 77) | def upstream_selected(self, backlog_or_tag: Backlog | Tag | None) -> N...
method on_setting_changed (line 80) | def on_setting_changed(self, event: str, old_values: dict[str, str], n...
FILE: src/fk/tests/abstract_test_case.py
class AbstractTestCase (line 23) | class AbstractTestCase(TestCase, abc.ABC):
method assert_events (line 24) | def assert_events(self,
FILE: src/fk/tests/data_generator.py
function lorem_ipsum (line 45) | def lorem_ipsum() -> str:
function lorem_ipsum_backlog (line 49) | def lorem_ipsum_backlog() -> str:
function emulate (line 53) | def emulate(days: int, user: str) -> Iterable[AbstractStrategy]:
FILE: src/fk/tests/test_backlogs.py
function _create_sample_backlog (line 38) | def _create_sample_backlog(existing: Tenant | None = None) -> Tenant:
class TestBacklogs (line 50) | class TestBacklogs(TestCase):
method setUp (line 56) | def setUp(self) -> None:
method tearDown (line 64) | def tearDown(self) -> None:
method test_initialize (line 67) | def test_initialize(self):
method _assert_backlog (line 72) | def _assert_backlog(self, backlog1: Backlog, user: User):
method test_create_backlogs (line 81) | def test_create_backlogs(self):
method test_execute_prepared (line 92) | def test_execute_prepared(self):
method test_create_duplicate_backlog_failure (line 104) | def test_create_duplicate_backlog_failure(self):
method test_rename_nonexistent_backlog_failure (line 109) | def test_rename_nonexistent_backlog_failure(self):
method test_rename_backlog (line 115) | def test_rename_backlog(self):
method test_delete_nonexistent_backlog_failure (line 121) | def test_delete_nonexistent_backlog_failure(self):
method test_delete_backlog (line 127) | def test_delete_backlog(self):
method test_today (line 135) | def test_today(self):
method test_events_create_backlog (line 146) | def test_events_create_backlog(self):
method test_events_delete_backlog (line 176) | def test_events_delete_backlog(self):
method test_events_rename_backlog (line 213) | def test_events_rename_backlog(self):
method test_create_backlog_strategy_basic (line 233) | def test_create_backlog_strategy_basic(self):
method test_create_backlog_strategy_already_exists (line 249) | def test_create_backlog_strategy_already_exists(self):
method test_create_backlog_strategy_same_name (line 254) | def test_create_backlog_strategy_same_name(self):
method test_create_backlog_independent_users_same_uid (line 268) | def test_create_backlog_independent_users_same_uid(self):
FILE: src/fk/tests/test_events.py
class TestEmitter (line 27) | class TestEmitter(AbstractEventEmitter):
method __init__ (line 30) | def __init__(self,
method action (line 38) | def action(self, value: str, carry: str = None):
method wrong_emit (line 43) | def wrong_emit(self):
class TestEvents (line 47) | class TestEvents(AbstractTestCase):
method setUp (line 48) | def setUp(self):
method test_events_basic (line 51) | def test_events_basic(self):
method test_callback_invoker (line 62) | def test_callback_invoker(self):
method test_cancel_one (line 79) | def test_cancel_one(self):
method test_unsubscribe (line 91) | def test_unsubscribe(self):
method test_unsubscribe_one (line 100) | def test_unsubscribe_one(self):
method test_firing_order (line 113) | def test_firing_order(self):
FILE: src/fk/tests/test_file_event_source.py
class FilteringSerializer (line 37) | class FilteringSerializer(AbstractSerializer):
method __init__ (line 41) | def __init__(self, another: AbstractSerializer, strategy_filter: Calla...
method serialize (line 46) | def serialize(self, s: AbstractStrategy) -> T:
method deserialize (line 49) | def deserialize(self, t: str) -> AbstractStrategy | None:
function _create_filtered_source (line 55) | def _create_filtered_source(strategy_filter: Callable[[AbstractStrategy]...
function _test_repair (line 69) | def _test_repair(strategy_filter: Callable[[AbstractStrategy], bool],
class TestFileEventSource (line 87) | class TestFileEventSource(TestCase):
method setUp (line 93) | def setUp(self) -> None:
method tearDown (line 101) | def tearDown(self) -> None:
method test_initialize (line 104) | def test_initialize(self):
method test_repair_strip_create_backlog (line 109) | def test_repair_strip_create_backlog(self):
method test_repair_strip_create_workitem (line 123) | def test_repair_strip_create_workitem(self):
method test_repair_no_op (line 146) | def test_repair_no_op(self):
FILE: src/fk/tests/test_import_export.py
function _skip_first (line 52) | def _skip_first(dump: str, skip_rows: int) -> str:
class TestImportExport (line 56) | class TestImportExport(TestCase):
method _init_source_temp (line 67) | def _init_source_temp(self):
method setUp (line 72) | def setUp(self) -> None:
method tearDown (line 87) | def tearDown(self) -> None:
method _register_source_producers (line 91) | def _register_source_producers(self):
method test_initialize (line 99) | def test_initialize(self):
method _execute_import (line 112) | def _execute_import(self, ignore_errors: bool, merge: bool, repair: bo...
method _execute_export (line 141) | def _execute_export(self, compress: bool, filename: str) -> (int, int):
method test_import_classic_ok (line 168) | def test_import_classic_ok(self):
method test_import_classic_twice_error (line 178) | def test_import_classic_twice_error(self):
method test_import_classic_twice_ignore_errors (line 182) | def test_import_classic_twice_ignore_errors(self):
method _compare_imported_and_original_dumps (line 205) | def _compare_imported_and_original_dumps(self):
method test_import_smart_ok (line 211) | def test_import_smart_ok(self):
method test_import_smart_twice_ok (line 216) | def test_import_smart_twice_ok(self):
method test_import_classic_in_halves_correct_order (line 221) | def test_import_classic_in_halves_correct_order(self):
method test_export_simple_ok (line 248) | def test_export_simple_ok(self):
method test_export_compressed_ok (line 260) | def test_export_compressed_ok(self):
method test_import_github_ok (line 272) | def test_import_github_ok(self):
method test_import_simple_ok (line 331) | def test_import_simple_ok(self):
method test_backlog_to_json (line 359) | def test_backlog_to_json(self):
FILE: src/fk/tests/test_pomodoros.py
class TestPomodoros (line 39) | class TestPomodoros(TestCase):
method setUp (line 45) | def setUp(self) -> None:
method tearDown (line 53) | def tearDown(self) -> None:
method _assert_workitem (line 56) | def _assert_workitem(self, workitem1: Workitem, user: User, backlog: B...
method _standard_backlog (line 68) | def _standard_backlog(self) -> (User, Backlog):
method _standard_workitem (line 74) | def _standard_workitem(self) -> (User, Backlog, Workitem):
method _standard_pomodoro (line 80) | def _standard_pomodoro(self, n: int, type: str = POMODORO_TYPE_NORMAL)...
method test_add_pomodoro (line 116) | def test_add_pomodoro(self):
method test_add_pomodoro_errors (line 148) | def test_add_pomodoro_errors(self):
method test_remove_pomodoro_errors (line 168) | def test_remove_pomodoro_errors(self):
method test_add_remove_stack (line 200) | def test_add_remove_stack(self):
method _assert_pomodoro_and_timer (line 223) | def _assert_pomodoro_and_timer(self,
method test_planned_unplanned_pomodoro (line 320) | def test_planned_unplanned_pomodoro(self):
method test_start_work_normal_ok (line 329) | def test_start_work_normal_ok(self):
method test_start_work_tracker_ok (line 338) | def test_start_work_tracker_ok(self):
method test_start_work_tracker_raises (line 347) | def test_start_work_tracker_raises(self):
method test_auto_seal_shortly_from_work (line 358) | def test_auto_seal_shortly_from_work(self):
method test_auto_seal_shortly_from_rest (line 367) | def test_auto_seal_shortly_from_rest(self):
method test_auto_seal_long_after_from_work (line 377) | def test_auto_seal_long_after_from_work(self):
method test_auto_seal_too_early (line 386) | def test_auto_seal_too_early(self):
method test_auto_seal_twice (line 394) | def test_auto_seal_twice(self):
method test_auto_seal_unneeded (line 405) | def test_auto_seal_unneeded(self):
method test_auto_seal_strategies (line 413) | def test_auto_seal_strategies(self):
method test_auto_seal_tracker (line 417) | def test_auto_seal_tracker(self):
method test_auto_seal_long_break (line 426) | def test_auto_seal_long_break(self):
FILE: src/fk/tests/test_settings.py
class TestSettings (line 28) | class TestSettings(TestCase):
method setUp (line 34) | def setUp(self) -> None:
method tearDown (line 42) | def tearDown(self) -> None:
method test_defaults (line 45) | def test_defaults(self):
method test_invalid_setting (line 51) | def test_invalid_setting(self):
method test_categories (line 55) | def test_categories(self):
method test_get_set (line 71) | def test_get_set(self):
method test_clear (line 77) | def test_clear(self):
method test_reset (line 85) | def test_reset(self):
method test_location (line 92) | def test_location(self):
method test_shortcuts (line 95) | def test_shortcuts(self):
method test_visibility (line 115) | def test_visibility(self):
FILE: src/fk/tests/test_tags.py
class TestTags (line 37) | class TestTags(TestCase):
method setUp (line 43) | def setUp(self) -> None:
method _standard_backlog (line 52) | def _standard_backlog(self) -> (User, Backlog):
method _add_workitem (line 58) | def _add_workitem(self, name: str, uid: str = 'w11') -> Workitem:
method _delete_workitem (line 63) | def _delete_workitem(self, uid: str) -> None:
method _rename_workitem (line 66) | def _rename_workitem(self, uid: str, new_name: str) -> None:
method tearDown (line 69) | def tearDown(self) -> None:
method test_create_workitem_without_tags (line 73) | def test_create_workitem_without_tags(self):
method test_create_workitem_with_tags (line 84) | def test_create_workitem_with_tags(self):
method test_delete_workitem (line 123) | def test_delete_workitem(self):
method test_rename_workitem (line 144) | def test_rename_workitem(self):
method test_event_source (line 186) | def test_event_source(self):
method test_workitem_accessors (line 210) | def test_workitem_accessors(self):
method test_find_tags (line 231) | def test_find_tags(self):
method test_different_users (line 246) | def test_different_users(self):
method test_file_event_source (line 313) | def test_file_event_source(self):
method test_tags_class (line 342) | def test_tags_class(self):
method test_tag_deleted_event (line 355) | def test_tag_deleted_event(self):
method test_tag_content_changed_event (line 386) | def test_tag_content_changed_event(self):
FILE: src/fk/tests/test_users.py
class TestUsers (line 33) | class TestUsers(TestCase):
method setUp (line 39) | def setUp(self) -> None:
method tearDown (line 47) | def tearDown(self) -> None:
method _assert_user (line 50) | def _assert_user(self, user: User):
method _create_standard_user (line 58) | def _create_standard_user(self):
method test_create_user (line 62) | def test_create_user(self):
method test_create_user_unauthorized_failure (line 67) | def test_create_user_unauthorized_failure(self):
method test_create_duplicate_user_failure (line 71) | def test_create_duplicate_user_failure(self):
method test_rename_nonexistent_user_failure (line 75) | def test_rename_nonexistent_user_failure(self):
method test_rename_user (line 81) | def test_rename_user(self):
method test_delete_nonexistent_user_failure (line 87) | def test_delete_nonexistent_user_failure(self):
method test_delete_user (line 93) | def test_delete_user(self):
method test_events_create_user (line 99) | def test_events_create_user(self):
method test_events_delete_user (line 121) | def test_events_delete_user(self):
method test_events_rename_user (line 148) | def test_events_rename_user(self):
FILE: src/fk/tests/test_utils.py
function check_timestamp (line 35) | def check_timestamp(t: datetime.datetime, n: int) -> bool:
function predefined_datetime (line 39) | def predefined_datetime(n: int) -> datetime.datetime:
function predefined_uid (line 43) | def predefined_uid(n: int) -> str:
function noop_emit (line 47) | def noop_emit(event: str, params: dict[str, any], carry: any) -> None:
function test_user (line 51) | def test_user(n: int) -> User:
function test_users (line 60) | def test_users() -> dict[str, User]:
function test_settings (line 68) | def test_settings(n: int) -> AbstractSettings:
function test_data (line 72) | def test_data() -> Tenant:
function epyc (line 80) | def epyc() -> datetime.datetime:
function one_of (line 87) | def one_of(seq: list[_T]) -> _T:
function randint (line 91) | def randint(a: int, b: int) -> int:
function random (line 95) | def random() -> float:
function rand_normal (line 104) | def rand_normal(a: int, b: int) -> int:
function shuffle (line 108) | def shuffle(seq: list[_T]) -> list[_T]:
FILE: src/fk/tests/test_workitems.py
class TestWorkitems (line 37) | class TestWorkitems(TestCase):
method setUp (line 43) | def setUp(self) -> None:
method tearDown (line 51) | def tearDown(self) -> None:
method _assert_workitem (line 54) | def _assert_workitem(self, workitem1: Workitem, user: User, backlog: B...
method _standard_backlog (line 66) | def _standard_backlog(self) -> (User, Backlog):
method test_create_workitems (line 72) | def test_create_workitems(self):
method test_create_workitems_with_tags (line 83) | def test_create_workitems_with_tags(self):
method test_execute_prepared (line 122) | def test_execute_prepared(self):
method test_create_duplicate_workitem_failure (line 134) | def test_create_duplicate_workitem_failure(self):
method test_rename_nonexistent_workitem_failure (line 140) | def test_rename_nonexistent_workitem_failure(self):
method test_rename_workitem (line 147) | def test_rename_workitem(self):
method test_delete_nonexistent_workitem_failure (line 153) | def test_delete_nonexistent_workitem_failure(self):
method test_delete_workitem (line 160) | def test_delete_workitem(self):
method test_complete_workitem_basic (line 169) | def test_complete_workitem_basic(self):
method test_complete_workitem_with_two_pomodoros (line 185) | def test_complete_workitem_with_two_pomodoros(self):
method test_complete_workitem_invalid_state (line 192) | def test_complete_workitem_invalid_state(self):
method test_complete_workitem_twice (line 198) | def test_complete_workitem_twice(self):
method test_rename_completed_workitem (line 205) | def test_rename_completed_workitem(self):
method test_add_pomodoro_to_completed_workitem (line 212) | def test_add_pomodoro_to_completed_workitem(self):
method test_delete_completed_workitem (line 219) | def test_delete_completed_workitem(self):
method test_start_completed_workitem (line 226) | def test_start_completed_workitem(self):
method test_events_create_workitem (line 242) | def test_events_create_workitem(self):
method test_events_delete_workitem (line 266) | def test_events_delete_workitem(self):
method test_events_complete_workitem (line 301) | def test_events_complete_workitem(self):
method test_events_rename_workitem (line 337) | def test_events_rename_workitem(self):
method _create_workitems_for_reorder_tests (line 365) | def _create_workitems_for_reorder_tests(self):
method _assert_workitem_order (line 373) | def _assert_workitem_order(self, backlog: Backlog, order: str):
method test_reorder_workitem_up_normal (line 376) | def test_reorder_workitem_up_normal(self):
method test_move_workitems_ok (line 380) | def test_move_workitems_ok(self):
FILE: src/fk/tools/cli.py
function strategy (line 35) | def strategy(cls: Type[AbstractStrategy],
function dump (line 43) | def dump(obj: object) -> None:
function list_backlogs (line 46) | def list_backlogs(source: AbstractEventSource[Tenant], uid: str | None, ...
function execute (line 61) | def execute(callback: Callable[[AbstractEventSource[Tenant]], None]) -> ...
function default (line 69) | def default(args) -> None:
function backlog (line 72) | def backlog(args) -> None:
FILE: src/fk/tools/minimal_common.py
class MinimalCommon (line 32) | class MinimalCommon:
method main_loop (line 41) | def main_loop(self):
method _on_messages (line 55) | def _on_messages(self, event: str, source: AbstractEventSource) -> None:
method _on_source_changed (line 59) | def _on_source_changed(self, event: str, source: AbstractEventSource):
method __init__ (line 65) | def __init__(self, callback: Callable = None, initialize_source: bool ...
method get_actions (line 78) | def get_actions(self):
method get_app (line 81) | def get_app(self):
method get_settings (line 84) | def get_settings(self):
method get_window (line 87) | def get_window(self):
method get_source (line 90) | def get_source(self):
FILE: src/fk/tools/minimal_tray.py
function tick (line 43) | def tick():
FILE: src/fk/tools/minimal_tutorial.py
function get_tutorial_step (line 23) | def get_tutorial_step(step: int, widget: QWidget) -> (str, QPoint, str):
FILE: src/fk/tools/minimal_update.py
function update (line 27) | def update(latest: Version, changelog: str):
FILE: src/fk/tools/minimal_users.py
function on_data (line 21) | def on_data(root: Tenant):
FILE: src/fk/tools/minimal_workitems.py
function select_first_backlog (line 21) | def select_first_backlog(data: Tenant):
Condensed preview — 219 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,551K chars).
[
{
"path": ".github/FUNDING.yml",
"chars": 70,
"preview": "# These are supported funding model platforms\n\ngithub: flowkeeper-org\n"
},
{
"path": ".github/workflows/build.yml",
"chars": 7488,
"preview": "name: Build subflow\n\non:\n workflow_call:\n inputs:\n os:\n required: true\n type: string\n compil"
},
{
"path": ".github/workflows/main.yml",
"chars": 1696,
"preview": "name: Tests and checks\n\non:\n pull_request:\n branches:\n - main\n push:\n branches:\n - main\n\npermissions:\n"
},
{
"path": ".github/workflows/manual-all.yml",
"chars": 535,
"preview": "name: Manual build - all\n\non:\n workflow_dispatch:\n\njobs:\n call-build:\n strategy:\n fail-fast: false\n matri"
},
{
"path": ".github/workflows/manual-one.yml",
"chars": 792,
"preview": "name: Manual build - one\n\non:\n workflow_dispatch:\n inputs:\n os:\n description: 'Operating system'\n "
},
{
"path": ".github/workflows/release.yml",
"chars": 1244,
"preview": "name: Release new version\n\non:\n push:\n tags:\n - \"v*.*.*\"\n\npermissions:\n contents: write\n checks: write\n\njobs:"
},
{
"path": ".gitignore",
"chars": 161,
"preview": ".venv\nvenv\nvenv*\n__pycache__\nbuild\ndist\n.coverage\n.DS_Store\nhtmlcov\nsrc/fk/desktop/resources.py\ntest-results\nFlowkeeper."
},
{
"path": ".idea/.gitignore",
"chars": 176,
"preview": "# Default ignored files\n/shelf/\n/workspace.xml\n# Editor-based HTTP Client requests\n/httpRequests/\n# Datasource local sto"
},
{
"path": ".idea/dictionaries/project.xml",
"chars": 171,
"preview": "<component name=\"ProjectDictionaryState\">\n <dictionary name=\"project\">\n <words>\n <w>flowkeeper</w>\n <w>wor"
},
{
"path": ".idea/flowkeeper-python.iml",
"chars": 945,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"PYTHON_MODULE\" version=\"4\">\n <component name=\"NewModuleRootManager"
},
{
"path": ".idea/inspectionProfiles/profiles_settings.xml",
"chars": 228,
"preview": "<component name=\"InspectionProjectProfileManager\">\n <settings>\n <option name=\"PROJECT_PROFILE\" value=\"Default\" />\n "
},
{
"path": ".idea/misc.xml",
"chars": 448,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"Black\">\n <option name=\"sdkName\" value"
},
{
"path": ".idea/modules.xml",
"chars": 286,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectModuleManager\">\n <modules>\n "
},
{
"path": ".idea/vcs.xml",
"chars": 167,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"VcsDirectoryMappings\">\n <mapping dire"
},
{
"path": ".idea/watcherTasks.xml",
"chars": 1011,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n <component name=\"ProjectTasksOptions\">\n <TaskOptions i"
},
{
"path": "LICENSE",
"chars": 35149,
"preview": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
},
{
"path": "README.md",
"chars": 4863,
"preview": "# Flowkeeper\n\n\n- ('"
},
{
"path": "doc/build-alpine.md",
"chars": 711,
"preview": "# Building for Alpine Linux\n\nFlowkeeper's CI pipeline runs PyInstaller on Ubuntu and thus generates binaries which rely "
},
{
"path": "doc/data-model.md",
"chars": 498,
"preview": "# Data model\n\nFlowkeeper data model is strictly hierarchical:\n\n- Tenant: AbstractDataContainer\n - User: AbstractDataCon"
},
{
"path": "doc/design.md",
"chars": 3180,
"preview": "# Flowkeeper design considerations\n\n## Client / server architecture\n\n1. Flowkeeper clients are \"fat\", and the backends a"
},
{
"path": "doc/events.md",
"chars": 3411,
"preview": "# Events\n\nWhenever anything changes in the underlying data model, Flowkeeper emits events. To emit an event, the class n"
},
{
"path": "doc/pipeline.md",
"chars": 111,
"preview": "## CI/CD Pipeline\n\n\n"
},
{
"path": "doc/release.md",
"chars": 4550,
"preview": "# Releasing Flowkeeper\n\n- Run unit and e2e tests in all available VMs.\n- Run the build pipeline and check Windows binari"
},
{
"path": "doc/strategies.md",
"chars": 6910,
"preview": "# Strategies\n\nYou may find those as _Strategies_ in the code. They correspond to the end-user actions /\ndata mutations. "
},
{
"path": "requirements-test.txt",
"chars": 78,
"preview": "-r requirements.txt\ncoverage\ncoveralls\nassertpy\nunittest-xml-reporting\npillow\n"
},
{
"path": "requirements.txt",
"chars": 195,
"preview": "# Stay at 6.7.0 due to a macOS bug in 6.7.1 and 6.7.2, which says \"No QtMultimedia backends found\".\n# 6.7.3 has another "
},
{
"path": "res/CHANGELOG.txt",
"chars": 15473,
"preview": "### v1.0.2 (26 September 2025)\n\nThis version addresses a couple of new bugs, one of which is quite annoying:\n\n- Can't vo"
},
{
"path": "res/CREDITS.txt",
"chars": 3344,
"preview": "Constantine Kulak <https://github.com/co-stig>: Flowkeeper author.\n\nMarco Sarti <marco@elogiclab.com>: AUR package maint"
},
{
"path": "res/LICENSE.txt",
"chars": 32070,
"preview": "GNU General Public License\n==========================\n\n_Version 3, 29 June 2007_\n_Copyright © 2007 Free Software Foundat"
},
{
"path": "res/about.ui",
"chars": 6026,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n ~ Flowkeeper - Pomodoro timer for power users and teams\n ~ Copyright (c) "
},
{
"path": "res/core.ui",
"chars": 6415,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n ~ Flowkeeper - Pomodoro timer for power users and teams\n ~ Copyright (c) "
},
{
"path": "res/icons/dark/index.theme",
"chars": 75,
"preview": "[Icon Theme]\nName=dark\nInherits=hicolor\nDirectories=24x24\n\n[24x24]\nSize=24\n"
},
{
"path": "res/icons/light/index.theme",
"chars": 76,
"preview": "[Icon Theme]\nName=light\nInherits=hicolor\nDirectories=24x24\n\n[24x24]\nSize=24\n"
},
{
"path": "res/icons/mixed/index.theme",
"chars": 76,
"preview": "[Icon Theme]\nName=mixed\nInherits=hicolor\nDirectories=24x24\n\n[24x24]\nSize=24\n"
},
{
"path": "res/stats.ui",
"chars": 7136,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n ~ Flowkeeper - Pomodoro timer for power users and teams\n ~ Copyright (c) "
},
{
"path": "res/style-beach.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"mixed\",\n\n \"FOCUS_TEXT_COLOR\": \"#ffffff\",\n \"FOCUS_BG_COLOR\": \"#F038FF\",\n \"FOCUS_BORDER_COLOR\": \"#F0"
},
{
"path": "res/style-dark.json",
"chars": 406,
"preview": "{\n \"ICON_THEME\": \"dark\",\n\n \"FOCUS_TEXT_COLOR\": \"#ffffff\",\n \"FOCUS_BG_COLOR\": \"#27282e\",\n \"FOCUS_BORDER_COLOR\": \"#000"
},
{
"path": "res/style-desert.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"mixed\",\n\n \"FOCUS_TEXT_COLOR\": \"#ffffff\",\n \"FOCUS_BG_COLOR\": \"#D52941\",\n \"FOCUS_BORDER_COLOR\": \"#FC"
},
{
"path": "res/style-highlight.json",
"chars": 406,
"preview": "{\n \"ICON_THEME\": \"dark\",\n\n \"FOCUS_TEXT_COLOR\": \"#FFFFFF\",\n \"FOCUS_BG_COLOR\": \"#EF6306\",\n \"FOCUS_BORDER_COLOR\": \"#000"
},
{
"path": "res/style-light.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"light\",\n\n \"FOCUS_TEXT_COLOR\": \"#000000\",\n \"FOCUS_BG_COLOR\": \"#f7f8fa\",\n \"FOCUS_BORDER_COLOR\": \"#d7"
},
{
"path": "res/style-lime.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"light\",\n\n \"FOCUS_TEXT_COLOR\": \"#000000\",\n \"FOCUS_BG_COLOR\": \"#E0FF4F\",\n \"FOCUS_BORDER_COLOR\": \"#A7"
},
{
"path": "res/style-mixed.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"mixed\",\n\n \"FOCUS_TEXT_COLOR\": \"#ffffff\",\n \"FOCUS_BG_COLOR\": \"#27282e\",\n \"FOCUS_BORDER_COLOR\": \"#53"
},
{
"path": "res/style-motel.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"mixed\",\n\n \"FOCUS_TEXT_COLOR\": \"#FFFFFF\",\n \"FOCUS_BG_COLOR\": \"#343233\",\n \"FOCUS_BORDER_COLOR\": \"#34"
},
{
"path": "res/style-purple.json",
"chars": 406,
"preview": "{\n \"ICON_THEME\": \"dark\",\n\n \"FOCUS_TEXT_COLOR\": \"#E9EDE9\",\n \"FOCUS_BG_COLOR\": \"#2D2327\",\n \"FOCUS_BORDER_COLOR\": \"#453"
},
{
"path": "res/style-resort.json",
"chars": 407,
"preview": "{\n \"ICON_THEME\": \"mixed\",\n\n \"FOCUS_TEXT_COLOR\": \"#FFFFFF\",\n \"FOCUS_BG_COLOR\": \"#F5AB00\",\n \"FOCUS_BORDER_COLOR\": \"#F5"
},
{
"path": "res/style-template.qss",
"chars": 3202,
"preview": "QWidget {\n font-family: $FONT_MAIN_FAMILY;\n font-size: $FONT_MAIN_SIZE;\n}\n\nQMainWindow, #StatsWindow, #FocusBackgr"
},
{
"path": "res/style-terra.json",
"chars": 406,
"preview": "{\n \"ICON_THEME\": \"dark\",\n\n \"FOCUS_TEXT_COLOR\": \"#ffffff\",\n \"FOCUS_BG_COLOR\": \"#2D381A\",\n \"FOCUS_BORDER_COLOR\": \"#2D3"
},
{
"path": "res/summary.ui",
"chars": 2136,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<ui version=\"4.0\">\n <class>WorkSummaryWindow</class>\n <widget class=\"QDialog\" nam"
},
{
"path": "run-tests.sh",
"chars": 990,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "run.sh",
"chars": 809,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/README.md",
"chars": 4658,
"preview": "# Scripts\n\n## Directory structure\n\n- `.github/` - GitHub Actions pipelines definitions, not packaged\n- `doc/` - Technica"
},
{
"path": "scripts/bsd/README.md",
"chars": 567,
"preview": "# Building for FreeBSD\n\nWe successfully built and tested Flowkeeper on FreeBSD:\n\n```\npkg install python3 git devel/pysid"
},
{
"path": "scripts/common/generate-resources.sh",
"chars": 1149,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/common/get-version.sh",
"chars": 940,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/common/pyinstaller/entitlements.plist",
"chars": 264,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
},
{
"path": "scripts/common/pyinstaller/normal.spec",
"chars": 2010,
"preview": "# -*- mode: python ; coding: utf-8 -*-\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Co"
},
{
"path": "scripts/common/pyinstaller/portable.spec",
"chars": 1708,
"preview": "# -*- mode: python ; coding: utf-8 -*-\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Co"
},
{
"path": "scripts/linux/appimage/install-appimage.sh",
"chars": 976,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/linux/appimage/package-appimage.sh",
"chars": 2471,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/linux/common/flowkeeper",
"chars": 838,
"preview": "#!/bin/bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This p"
},
{
"path": "scripts/linux/common/org.flowkeeper.Flowkeeper.desktop",
"chars": 216,
"preview": "[Desktop Entry]\nName=Flowkeeper\nComment=Pomodoro Technique desktop timer for power users\nExec=/usr/bin/flowkeeper $FK_AU"
},
{
"path": "scripts/linux/common/org.flowkeeper.Flowkeeper.metainfo.xml",
"chars": 2157,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<component type=\"desktop-application\">\n <id>org.flowkeeper.Flowkeeper</id>\n\n "
},
{
"path": "scripts/linux/debian/debian-control",
"chars": 175,
"preview": "Package: flowkeeper\nVersion: $FK_VERSION\nMaintainer: Constantine Kulak\nArchitecture: amd64\nDescription: Flowkeeper is a "
},
{
"path": "scripts/linux/debian/debian-control-min",
"chars": 653,
"preview": "Package: flowkeeper\nVersion: $FK_VERSION\nMaintainer: Constantine Kulak\nArchitecture: amd64\nDescription: Flowkeeper is a "
},
{
"path": "scripts/linux/debian/package-deb-min.sh",
"chars": 2440,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/linux/debian/package-deb.sh",
"chars": 2448,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/linux/flatpak/README.md",
"chars": 563,
"preview": "# Flowkeeper in Flatpak\n\n- End-user link: https://flathub.org/apps/org.flowkeeper.Flowkeeper\n- Fork repo: https://github"
},
{
"path": "scripts/linux/obs/README.md",
"chars": 305,
"preview": "# Updating OBS build\n\nStep 1: Download the latest tar.gz\n\nStep 2: Update `flowkeeper.spec` if needed\n\nStep 3: Update the"
},
{
"path": "scripts/linux/obs/obs.spec",
"chars": 3985,
"preview": "#\n# spec file for package flowkeeper\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Co"
},
{
"path": "scripts/linux/package-nuitka.sh",
"chars": 1096,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/linux/rpm/package-rpm-min.sh",
"chars": 2327,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/create-dmg.sh",
"chars": 1101,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/create-icons.sh",
"chars": 1671,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/install-certificates.sh",
"chars": 1676,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/install-create-dmg.sh",
"chars": 898,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/notarize-dmg.sh",
"chars": 1272,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/package-macos-pkg.sh",
"chars": 1075,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/macos/package-nuitka.sh",
"chars": 2075,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/windows/generate-resources-windows.sh",
"chars": 934,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/windows/install-innosetup.sh",
"chars": 1041,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/windows/package-installer.sh",
"chars": 892,
"preview": "#!/usr/bin/env bash\n\n#\n# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n"
},
{
"path": "scripts/windows/windows-installer.iss",
"chars": 1142,
"preview": "[Setup]\nAppName=Flowkeeper\nAppVersion={#GetEnv('FK_VERSION')}\nAppPublisher=flowkeeper.org\nAppPublisherURL=https://flowke"
},
{
"path": "src/fk/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/core/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/core/abstract_cryptograph.py",
"chars": 2223,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_data_container.py",
"chars": 3764,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_data_item.py",
"chars": 4164,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_event_emitter.py",
"chars": 5314,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_event_source.py",
"chars": 14579,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_filesystem_watcher.py",
"chars": 1099,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_serializer.py",
"chars": 1613,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_settings.py",
"chars": 26018,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_strategy.py",
"chars": 4326,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_timer.py",
"chars": 1154,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/abstract_timer_display.py",
"chars": 8804,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/backlog.py",
"chars": 2719,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/backlog_strategies.py",
"chars": 7475,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/ephemeral_event_source.py",
"chars": 3326,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/event_source_factory.py",
"chars": 2106,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/event_source_holder.py",
"chars": 3635,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/events.py",
"chars": 5180,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/fernet_cryptograph.py",
"chars": 2779,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/file_event_source.py",
"chars": 25895,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/import_export.py",
"chars": 29790,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/integration_executor.py",
"chars": 4184,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/interruption.py",
"chars": 2952,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/mock_settings.py",
"chars": 3979,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/no_cryptograph.py",
"chars": 1289,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/pomodoro.py",
"chars": 13366,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/pomodoro_strategies.py",
"chars": 8102,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/sandbox.py",
"chars": 1237,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/simple_serializer.py",
"chars": 4129,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/strategy_factory.py",
"chars": 1403,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/tag.py",
"chars": 1910,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/tags.py",
"chars": 1492,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/tenant.py",
"chars": 1847,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/timer.py",
"chars": 9250,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/timer_data.py",
"chars": 7957,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/timer_strategies.py",
"chars": 13318,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/user.py",
"chars": 2975,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/user_strategies.py",
"chars": 6981,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/workitem.py",
"chars": 8012,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/core/workitem_strategies.py",
"chars": 14750,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/desktop/application.py",
"chars": 37127,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/config_wizard.py",
"chars": 12345,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/desktop.py",
"chars": 27846,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/desktop_strategies.py",
"chars": 9300,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/export_wizard.py",
"chars": 8139,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/import_wizard.py",
"chars": 20599,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/interruption_dialog.py",
"chars": 3231,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/settings.py",
"chars": 25305,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/stats_window.py",
"chars": 16623,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/tutorial.py",
"chars": 16619,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/desktop/work_summary_window.py",
"chars": 20639,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/e2e/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/e2e/abstract_e2e_test.py",
"chars": 15604,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/e2e/all-tests.json",
"chars": 3965,
"preview": "{\n \"ubuntu-20.04-wayland\": [\"DEB package\", \"All-in-one binary\", \"Portable archive\", \"Debian build\"],\n \"ubuntu-20.04-xo"
},
{
"path": "src/fk/e2e/backlog_e2e.py",
"chars": 20415,
"preview": "import asyncio\nimport os\n\nfrom PySide6.QtCore import Qt\nfrom assertpy import assert_that\n\nfrom fk.core.workitem import W"
},
{
"path": "src/fk/e2e/screenshot.py",
"chars": 7411,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/e2e/screenshots_e2e.py",
"chars": 22074,
"preview": "import asyncio\nimport datetime\nimport os\n\nfrom PySide6.QtCore import Qt, QPoint, QSize\nfrom PySide6.QtWidgets import QTa"
},
{
"path": "src/fk/qt/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/qt/about_window.py",
"chars": 3941,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/abstract_drop_model.py",
"chars": 7420,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/abstract_item_delegate.py",
"chars": 2448,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/abstract_tableview.py",
"chars": 9479,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/actions.py",
"chars": 5210,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/app_version.py",
"chars": 2715,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/audio_player.py",
"chars": 9775,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/backlog_model.py",
"chars": 7085,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/backlog_tableview.py",
"chars": 11995,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/backlog_widget.py",
"chars": 3813,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/configurable_toolbar.py",
"chars": 2880,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/connection_widget.py",
"chars": 2969,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/flow_layout.py",
"chars": 3248,
"preview": "# PySide6 port of the widgets/layouts/flowlayout example from Qt v6.x\n# Copied with minor modifications from here:\n# htt"
},
{
"path": "src/fk/qt/focus_widget.py",
"chars": 21033,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/heartbeat.py",
"chars": 4670,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/info_overlay.py",
"chars": 10945,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/oauth.py",
"chars": 4870,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/pomodoro_delegate.py",
"chars": 5224,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/progress_widget.py",
"chars": 3205,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/qt_filesystem_watcher.py",
"chars": 1908,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/qt_invoker.py",
"chars": 1294,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/qt_settings.py",
"chars": 11509,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/qt_timer.py",
"chars": 2962,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/render/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/qt/render/abstract_timer_renderer.py",
"chars": 3773,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/render/classic_timer_renderer.py",
"chars": 6238,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/render/minimal_timer_renderer.py",
"chars": 6643,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/resize_event_filter.py",
"chars": 3719,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/search_completer.py",
"chars": 4148,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/tags_widget.py",
"chars": 5834,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/theme_change_event_filter.py",
"chars": 2209,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/threaded_event_source.py",
"chars": 5762,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/timer_widget.py",
"chars": 4725,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/tray_icon.py",
"chars": 6503,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/user_model.py",
"chars": 3827,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/user_tableview.py",
"chars": 2825,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/websocket_event_source.py",
"chars": 10674,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/workitem_model.py",
"chars": 16174,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/workitem_state_delegate.py",
"chars": 2209,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/workitem_tableview.py",
"chars": 14680,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/workitem_text_delegate.py",
"chars": 2773,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/qt/workitem_widget.py",
"chars": 3485,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/__init__.py",
"chars": 0,
"preview": ""
},
{
"path": "src/fk/tests/abstract_test_case.py",
"chars": 1775,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/data_generator.py",
"chars": 15096,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/fixtures/random-dump.txt",
"chars": 204453,
"preview": "- Class: User\n UID: user@local.host\n Owner: N/A\n Parent UID: 0\n Create date: 2025-05-05 14:38:00+00:00\n Last modifi"
},
{
"path": "src/fk/tests/fixtures/random.txt",
"chars": 140470,
"preview": "1, 2025-05-05 14:38:00+00:00, admin@local.host: CreateUser(\"user@local.host\", \"user@local.host\")\n2, 2025-05-05 14:38:27+"
},
{
"path": "src/fk/tests/fixtures/test-tags.txt",
"chars": 465,
"preview": "1, 2023-10-18 15:13:24.607620+00:00, admin@local.host: CreateUser(\"alice@flowkeeper.org\", \"Alice Cooper\")\n2, 2023-10-21 "
},
{
"path": "src/fk/tests/test_backlogs.py",
"chars": 14153,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_events.py",
"chars": 5427,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_file_event_source.py",
"chars": 7664,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_import_export.py",
"chars": 17654,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_pomodoros.py",
"chars": 22709,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_settings.py",
"chars": 6186,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_tags.py",
"chars": 15417,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
},
{
"path": "src/fk/tests/test_users.py",
"chars": 7824,
"preview": "# Flowkeeper - Pomodoro timer for power users and teams\n# Copyright (c) 2023 Constantine Kulak\n#\n# This program is fr"
}
]
// ... and 19 more files (download for full content)
About this extraction
This page contains the full source code of the flowkeeper-org/fk-desktop GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 219 files (1.4 MB), approximately 405.9k tokens, and a symbol index with 1603 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.