fix
This commit is contained in:
26
calamares/src/CMakeLists.txt
Normal file
26
calamares/src/CMakeLists.txt
Normal file
@@ -0,0 +1,26 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
include(CalamaresAddBrandingSubdirectory)
|
||||
include(CalamaresAddLibrary)
|
||||
include(CalamaresAddModuleSubdirectory)
|
||||
include(CalamaresAddPlugin)
|
||||
include(CalamaresAddTest)
|
||||
include(CalamaresAddTranslations)
|
||||
|
||||
# library
|
||||
add_subdirectory(libcalamares)
|
||||
add_subdirectory(libcalamaresui)
|
||||
|
||||
add_subdirectory(qml)
|
||||
|
||||
# application
|
||||
add_subdirectory(calamares)
|
||||
|
||||
# plugins
|
||||
add_subdirectory(modules)
|
||||
|
||||
# branding components
|
||||
add_subdirectory(branding)
|
||||
10
calamares/src/branding/CMakeLists.txt
Normal file
10
calamares/src/branding/CMakeLists.txt
Normal file
@@ -0,0 +1,10 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
|
||||
# Add branding components. Since there is only one, called "default",
|
||||
# add that one. For examples of other branding components, see
|
||||
# the calamares-extensions repository.
|
||||
calamares_add_branding_subdirectory( default )
|
||||
225
calamares/src/branding/README.md
Normal file
225
calamares/src/branding/README.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# Branding directory
|
||||
|
||||
<!-- SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
SPDX-FileCopyrightText: 2017 Adriaan de Groot <groot@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
-->
|
||||
|
||||
Branding components can go here, or they can be installed separately.
|
||||
|
||||
A branding component is a subdirectory with a `branding.desc` descriptor
|
||||
file, containing brand-specific strings in a key-value structure, plus
|
||||
brand-specific images or QML. Such a subdirectory, when placed here, is
|
||||
automatically picked up by CMake and made available to Calamares.
|
||||
|
||||
It is recommended to package branding separately, so as to avoid
|
||||
forking Calamares just for adding some files. Calamares installs
|
||||
CMake support macros to help create branding packages. See the
|
||||
calamares-branding repository for examples of stand-alone branding.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
There is one example of a branding component included with Calamares,
|
||||
so that it can be run directly from the build directory for testing purposes:
|
||||
|
||||
- `default/` is a sample brand for the Generic Linux distribution. It uses
|
||||
the default Calamares icons and a as start-page splash it provides a
|
||||
tag-cloud view of languages. The slideshow is a basic one with a few
|
||||
slides of text and a single image. Translations (done by hand, not via
|
||||
the usual mechanism of Calamares translations) in English, Arabic, Dutch
|
||||
and French are available.
|
||||
|
||||
Since the slideshow can be **any** QML, it is limited only by your designers
|
||||
imagination and your QML experience. For straightforward presentations,
|
||||
see the documentation below. There are more examples in the [calamares-branding][1]
|
||||
repository.
|
||||
|
||||
[1] https://codeberg.org/Calamares/calamares-branding
|
||||
|
||||
|
||||
## API Versions
|
||||
|
||||
In Calamares versions prior to 3.2.10, the QML slideshow was loaded
|
||||
synchronously when the installation page is shown. This can lead to
|
||||
noticeable lag when showing that page. The QML is written start when
|
||||
it is loaded, by responding to the `onComplete` signal.
|
||||
|
||||
Calamares 3.2.10 introduces an API versioning scheme which uses different
|
||||
loading mechanisms.
|
||||
|
||||
- **API version 1** Loads the QML slideshow synchronously, as before.
|
||||
- The QML can use `onComplete` to start timers, etc. for progress
|
||||
or animation.
|
||||
- Translations are supported through `qsTr()` and the language that is
|
||||
in use when the installation slideshow is loaded, will be used
|
||||
(once the installation part is running, it can't change anyway).
|
||||
- **API version 2** Loads the QML slideshow **a**synchronously, on
|
||||
startup (generally during the requirements-checking phase of Calamares)
|
||||
so that no compilation lag is seen.
|
||||
- The QML should **not** use `onComplete`, since the QML is loaded and
|
||||
instantiated at startup. Instead,
|
||||
- The QML should provide functions `onActivate()` and `onLeave()` in the
|
||||
root object of the slideshow. These are called when the slideshow
|
||||
should start (e.g. becomes visible) and stop.
|
||||
- Translations are supported through `qsTr()`. However, since the language
|
||||
can change after the QML is loaded, code should count on the bindings
|
||||
being re-evaluated on language change. Translation updates (e.g. change
|
||||
of language) is **only supported** with Qt 5.10 and later.
|
||||
|
||||
The setting *slideshowAPI* in `branding.desc` indicates which one to use
|
||||
for a given branding slideshow. Which API to use is really a function of
|
||||
the QML. Expect the version 1 API to be deprecated in the course of Calamares 3.3.
|
||||
|
||||
In Calamares 3.2.13 support for activation notification to the QML
|
||||
parts is improved:
|
||||
- If the root object has a property *activatedInCalamares* (the examples do),
|
||||
then that property is set to *true* when the slideshow becomes visible
|
||||
(activated) and is set to *false* when the slideshow is hidden (e.g.
|
||||
when the installation phase is done).
|
||||
- The *actvatedInCalamares* property can be used to set up timers also in V1.
|
||||
- The keyboard shortcuts in the example slideshow are enabled only while
|
||||
the slideshow is visible.
|
||||
|
||||
|
||||
## Translations
|
||||
|
||||
QML files in a branding component can be translated. Translations should
|
||||
be placed in a subdirectory `lang/` of the branding component directory.
|
||||
Qt translation files are supported (`.ts` sources which get compiled into
|
||||
`.qm`). Inside the `lang` subdirectory all translation files must be named
|
||||
according to the scheme `calamares-<component name>_<language>.ts`.
|
||||
|
||||
The example branding component, called *default*, therefore has translation
|
||||
files names `calamares-default_nl.ts` (similar for other languages than Dutch).
|
||||
|
||||
Text in your `show.qml` (or whatever *slideshow* is set to in the descriptor
|
||||
file) should be enclosed in this form for translations
|
||||
|
||||
```
|
||||
text: qsTr("This is an example text.")
|
||||
```
|
||||
|
||||
If you use CMake for preparing branding for packaging, the macro
|
||||
`calamares_add_branding_subdirectory()`` (see also *Project Layout*,
|
||||
below) will convert the source `.ts` files to their compiled form).
|
||||
If you are packaging the branding by hand, use
|
||||
```
|
||||
lrelease file_en.ts [file_en_GB.ts ..]
|
||||
```
|
||||
with all the language suffixes to *file*.
|
||||
|
||||
|
||||
## Presentation
|
||||
|
||||
The default QML classes provided by Calamares can be used for a simple
|
||||
and straightforward "slideshow" presentation with static text and
|
||||
pictures. To use the default slideshow classes, start with a `show.qml`
|
||||
file with the following content:
|
||||
|
||||
```
|
||||
import QtQuick 2.5;
|
||||
import calamares.slideshow 1.0;
|
||||
|
||||
Presentation
|
||||
{
|
||||
id: presentation
|
||||
}
|
||||
```
|
||||
|
||||
After the *id*, set properties of the presentation as a whole. These include:
|
||||
- *loopSlides* (default true) When set, clicking past the last slide
|
||||
returns to the very first slide.
|
||||
- *mouseNavigation*, *arrowNavigation*, *keyShortcutsEnabled* (all default
|
||||
true) enable different ways to navigate the slideshow.
|
||||
- *titleColor*, *textColor* change the look of the presentation.
|
||||
- *fontFamily*, *codeFontFamily* change the look of text in the presentation.
|
||||
|
||||
After setting properties, you can add elements to the presentation.
|
||||
Generally, you will add a few presentation-level elements first,
|
||||
then slides.
|
||||
- For visible navigation arrows, add elements of class *ForwardButton* and
|
||||
*BackwardButton*. Set the *source* property of each to a suitable
|
||||
image. See the `fancy/` example in the external branding-examples
|
||||
repository. It is recommended to turn off other
|
||||
kinds of navigation when visible navigation is used.
|
||||
- To indicate where the user is, add an element of class *SlideCounter*.
|
||||
This indicates in "n / total" form where the user is in the slideshow.
|
||||
- To automatically advance the presentation (for a fully passive slideshow),
|
||||
add a timer that calls the `goToNextSlide()` function of the presentation.
|
||||
See the `default/` example -- remember to start the timer when the
|
||||
presentation is completely loaded.
|
||||
|
||||
After setting the presentation elements, add one or more Slide elements.
|
||||
The presentation framework will make a slideshow out of the Slide
|
||||
elements, displaying only one at a time. Each slide is an element in itself,
|
||||
so you can put whatever visual elements you like in the slide. They have
|
||||
standard properties for a boring "static text" slideshow, though:
|
||||
- *title* is text to show as slide title
|
||||
- *centeredText* is displayed in a large-ish font
|
||||
- *writeInText* is displayed by "writing it in" to the slide,
|
||||
one letter at a time.
|
||||
- *content* is a list of things which are displayed as a bulleted list.
|
||||
|
||||
The presentation classes can be used to produce a fairly dry slideshow
|
||||
for the installation process; it is recommended to experiment with the
|
||||
visual effects and classes available in QtQuick.
|
||||
|
||||
|
||||
## Project Layout
|
||||
|
||||
A branding component that is created and installed outside of Calamares
|
||||
will have a top-level `CMakeLists.txt` that includes some boilerplate
|
||||
to find Calamares, and then adds a subdirectory which contains the
|
||||
actual branding component.
|
||||
|
||||
The file layout in a typical branding component repository is:
|
||||
|
||||
```
|
||||
/
|
||||
- CMakeLists.txt
|
||||
- componentname/
|
||||
- show.qml
|
||||
- image1.png
|
||||
...
|
||||
- lang/
|
||||
- calamares-componentname_en.ts
|
||||
- calamares-componentname_de.ts
|
||||
...
|
||||
```
|
||||
|
||||
Adding the subdirectory can be done as follows:
|
||||
|
||||
- If the directory contains files only, and optionally has a single
|
||||
subdirectory lang/ which contains the translation files for the
|
||||
component, then `calamares_add_branding_subdirectory()` can be
|
||||
used, which takes only the name of the subdirectory.
|
||||
- If the branding component has many files which are organized into
|
||||
subdirectories, use the SUBDIRECTORIES argument to the CMake function
|
||||
to additionally install files from those subdirectories. For example,
|
||||
if the component places all of its images in an `img/` subdirectory,
|
||||
then call `calamares_add_branding_subdirectory( ... SUBDIRECTORIES img)`.
|
||||
It is a bad idea to include `lang/` in the SUBDIRECTORIES list.
|
||||
- The `.ts` files from the `lang/` subdirectory need be be compiled
|
||||
to `.qm` files before being installed. The CMake macro's do this
|
||||
automatically. For manual packaging, use `lrelease` to compile
|
||||
the files.
|
||||
|
||||
## Global Storage keys
|
||||
|
||||
The following keys from the `branding.desc` file are copied into
|
||||
Global Storage under a *branding* parent key:
|
||||
"productName",
|
||||
"version",
|
||||
"shortVersion",
|
||||
"versionedName",
|
||||
"shortVersionedName",
|
||||
"shortProductName",
|
||||
"bootloaderEntryName",
|
||||
"productUrl",
|
||||
"supportUrl",
|
||||
"knownIssuesUrl",
|
||||
"releaseNotesUrl",
|
||||
"donateUrl"
|
||||
|
||||
<!-- see Branding::s_stringEntryStrings and Branding::setGlobals() -->
|
||||
BIN
calamares/src/branding/default/banner.png
Normal file
BIN
calamares/src/branding/default/banner.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
2
calamares/src/branding/default/banner.png.license
Normal file
2
calamares/src/branding/default/banner.png.license
Normal file
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
239
calamares/src/branding/default/branding.desc
Normal file
239
calamares/src/branding/default/branding.desc
Normal file
@@ -0,0 +1,239 @@
|
||||
# SPDX-FileCopyrightText: no
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
#
|
||||
# Product branding information. This influences some global
|
||||
# user-visible aspects of Calamares, such as the product
|
||||
# name, window behavior, and the slideshow during installation.
|
||||
#
|
||||
# Additional styling can be done using the stylesheet.qss
|
||||
# file, also in the branding directory.
|
||||
---
|
||||
componentName: default
|
||||
|
||||
|
||||
### WELCOME / OVERALL WORDING
|
||||
#
|
||||
# These settings affect some overall phrasing and looks,
|
||||
# which are most visible in the welcome page.
|
||||
|
||||
# This selects between different welcome texts. When false, uses
|
||||
# the traditional "Welcome to the %1 installer.", and when true,
|
||||
# uses "Welcome to the Calamares installer for %1." This allows
|
||||
# to distinguish this installer from other installers for the
|
||||
# same distribution.
|
||||
welcomeStyleCalamares: false
|
||||
|
||||
# Should the welcome image (productWelcome, below) be scaled
|
||||
# up beyond its natural size? If false, the image does not grow
|
||||
# with the window but remains the same size throughout (this
|
||||
# may have surprising effects on HiDPI monitors).
|
||||
welcomeExpandingLogo: true
|
||||
|
||||
### WINDOW CONFIGURATION
|
||||
#
|
||||
# The settings here affect the placement of the Calamares
|
||||
# window through hints to the window manager and initial
|
||||
# sizing of the Calamares window.
|
||||
|
||||
# Size and expansion policy for Calamares.
|
||||
# - "normal" or unset, expand as needed, use *windowSize*
|
||||
# - "fullscreen", start as large as possible, ignore *windowSize*
|
||||
# - "noexpand", don't expand automatically, use *windowSize*
|
||||
windowExpanding: normal
|
||||
|
||||
# Size of Calamares window, expressed as w,h. Both w and h
|
||||
# may be either pixels (suffix px) or font-units (suffix em).
|
||||
# e.g. "800px,600px"
|
||||
# "60em,480px"
|
||||
# This setting is ignored if "fullscreen" is selected for
|
||||
# *windowExpanding*, above. If not set, use constants defined
|
||||
# in CalamaresUtilsGui, 800x520.
|
||||
windowSize: 800px,520px
|
||||
|
||||
# Placement of Calamares window. Either "center" or "free".
|
||||
# Whether "center" actually works does depend on the window
|
||||
# manager in use (and only makes sense if you're not using
|
||||
# *windowExpanding* set to "fullscreen").
|
||||
windowPlacement: center
|
||||
|
||||
### PANELS CONFIGURATION
|
||||
#
|
||||
# Calamares has a main content area, and two panels (navigation
|
||||
# and progress / sidebar). The panels can be controlled individually,
|
||||
# or switched off. If both panels are switched off, the layout of
|
||||
# the main content area loses its margins, on the assumption that
|
||||
# you're doing something special.
|
||||
|
||||
# Kind of sidebar (panel on the left, showing progress).
|
||||
# - "widget" or unset, use traditional sidebar (logo, items)
|
||||
# - "none", hide it entirely
|
||||
# - "qml", use calamares-sidebar.qml from branding folder
|
||||
# In addition, you **may** specify a side, separated by a comma,
|
||||
# from the kind. Valid sides are:
|
||||
# - "left" (if not specified, uses this)
|
||||
# - "right"
|
||||
# - "top"
|
||||
# - "bottom"
|
||||
# For instance, "widget,right" is valid; so is "qml", which defaults
|
||||
# to putting the sidebar on the left. Also valid is "qml,top".
|
||||
# While "widget,top" is valid, the widgets code is **not** flexible
|
||||
# and results will be terrible.
|
||||
sidebar: widget
|
||||
|
||||
# Kind of navigation (button panel on the bottom).
|
||||
# - "widget" or unset, use traditional navigation
|
||||
# - "none", hide it entirely
|
||||
# - "qml", use calamares-navigation.qml from branding folder
|
||||
# In addition, you **may** specify a side, separated by a comma,
|
||||
# from the kind. The same sides are valid as for *sidebar*,
|
||||
# except the default is *bottom*.
|
||||
navigation: widget
|
||||
|
||||
|
||||
### STRINGS, IMAGES AND COLORS
|
||||
#
|
||||
# This section contains the "branding proper" of names
|
||||
# and images, rather than global-look settings.
|
||||
|
||||
# These are strings shown to the user in the user interface.
|
||||
# There is no provision for translating them -- since they
|
||||
# are names, the string is included as-is.
|
||||
#
|
||||
# The four Url strings are the Urls used by the buttons in
|
||||
# the welcome screen, and are not shown to the user. Clicking
|
||||
# on the "Support" button, for instance, opens the link supportUrl.
|
||||
# If a Url is empty, the corresponding button is not shown.
|
||||
#
|
||||
# bootloaderEntryName is how this installation / distro is named
|
||||
# in the boot loader (e.g. in the GRUB menu).
|
||||
#
|
||||
# These strings support substitution from /etc/os-release
|
||||
# if KDE Frameworks 5.58 are available at build-time. When
|
||||
# enabled, ${varname} is replaced by the equivalent value
|
||||
# from os-release. All the supported var-names are in all-caps,
|
||||
# and are listed on the FreeDesktop.org site,
|
||||
# https://www.freedesktop.org/software/systemd/man/os-release.html
|
||||
# Note that ANSI_COLOR and CPE_NAME don't make sense here, and
|
||||
# are not supported (the rest are). Remember to quote the string
|
||||
# if it contains substitutions, or you'll get YAML exceptions.
|
||||
#
|
||||
# The *Url* entries are used on the welcome page, and they
|
||||
# are visible as buttons there if the corresponding *show* keys
|
||||
# are set to "true" (they can also be overridden).
|
||||
strings:
|
||||
productName: "${NAME}"
|
||||
shortProductName: Generic
|
||||
version: 2023.3 LTS
|
||||
shortVersion: 2023.3
|
||||
versionedName: Fancy GNU/Linux 2023.3 LTS "Venomous Vole"
|
||||
shortVersionedName: FancyGL 2023.3
|
||||
bootloaderEntryName: FancyGL
|
||||
productUrl: https://calamares.io/
|
||||
supportUrl: https://codeberg.org/Calamares/calamares/wiki
|
||||
knownIssuesUrl: https://codeberg.org/Calamares/calamares/issues
|
||||
releaseNotesUrl: https://calamares.io/news/
|
||||
donateUrl: https://docs.codeberg.org/improving-codeberg/donate/
|
||||
|
||||
# These images are loaded from the branding module directory.
|
||||
#
|
||||
# productBanner is an optional image, which if present, will be shown
|
||||
# on the welcome page of the application, above the welcome text.
|
||||
# It is intended to have a width much greater than height.
|
||||
# It is displayed at 64px height (also on HiDPI).
|
||||
# Recommended size is 64px tall, and up to 460px wide.
|
||||
# productIcon is used as the window icon, and will (usually) be used
|
||||
# by the window manager to represent the application. This image
|
||||
# should be square, and may be displayed by the window manager
|
||||
# as small as 16x16 (but possibly larger).
|
||||
# productLogo is used as the logo at the top of the left-hand column
|
||||
# which shows the steps to be taken. The image should be square,
|
||||
# and is displayed at 80x80 pixels (also on HiDPI).
|
||||
# productWallpaper is an optional image, which if present, will replace
|
||||
# the normal solid background on every page of the application.
|
||||
# It can be any size and proportion,
|
||||
# and will be tiled to fit the entire window.
|
||||
# For a non-tiled wallpaper, the size should be the same as
|
||||
# the overall window, see *windowSize* above (800x520).
|
||||
# productWelcome is shown on the welcome page of the application in
|
||||
# the middle of the window, below the welcome text. It can be
|
||||
# any size and proportion, and will be scaled to fit inside
|
||||
# the window. Use `welcomeExpandingLogo` to make it non-scaled.
|
||||
# Recommended size is 320x150.
|
||||
#
|
||||
# These filenames can also use substitutions from os-release (see above).
|
||||
images:
|
||||
# productBanner: "banner.png"
|
||||
productIcon: "squid.png"
|
||||
productLogo: "squid.png"
|
||||
# productWallpaper: "wallpaper.png"
|
||||
productWelcome: "languages.png"
|
||||
|
||||
# Colors for text and background components.
|
||||
#
|
||||
# - SidebarBackground is the background of the sidebar
|
||||
# - SidebarText is the (foreground) text color
|
||||
# - SidebarBackgroundCurrent sets the background of the current step.
|
||||
# Optional, and defaults to the application palette.
|
||||
# - SidebarTextCurrent is the text color of the current step.
|
||||
#
|
||||
# These colors can **also** be set through the stylesheet, if the
|
||||
# branding component also ships a stylesheet.qss. Then they are
|
||||
# the corresponding CSS attributes of #sidebarApp.
|
||||
style:
|
||||
SidebarBackground: "#292F34"
|
||||
SidebarText: "#FFFFFF"
|
||||
SidebarTextCurrent: "#292F34"
|
||||
SidebarBackgroundCurrent: "#D35400"
|
||||
|
||||
### SLIDESHOW
|
||||
#
|
||||
# The slideshow is displayed during execution steps (e.g. when the
|
||||
# installer is actually writing to disk and doing other slow things).
|
||||
|
||||
# The slideshow can be a QML file (recommended) which can display
|
||||
# arbitrary things -- text, images, animations, or even play a game --
|
||||
# during the execution step. The QML **is** abruptly stopped when the
|
||||
# execution step is done, though, so maybe a game isn't a great idea.
|
||||
#
|
||||
# The slideshow can also be a sequence of images (not recommended unless
|
||||
# you don't want QML at all in your Calamares). The images are displayed
|
||||
# at a rate of 1 every 2 seconds during the execution step.
|
||||
#
|
||||
# To configure a QML file, list a single filename:
|
||||
# slideshow: "show.qml"
|
||||
# To configure images, like the filenames (here, as an inline list):
|
||||
# slideshow: [ "/etc/calamares/slideshow/0.png", "/etc/logo.png" ]
|
||||
slideshow: "show.qml"
|
||||
|
||||
# There are two available APIs for a QML slideshow:
|
||||
# - 1 (the default) loads the entire slideshow when the installation-
|
||||
# slideshow page is shown and starts the QML then. The QML
|
||||
# is never stopped (after installation is done, times etc.
|
||||
# continue to fire).
|
||||
# - 2 loads the slideshow on startup and calls onActivate() and
|
||||
# onLeave() in the root object. After the installation is done,
|
||||
# the show is stopped (first by calling onLeave(), then destroying
|
||||
# the QML components).
|
||||
#
|
||||
# An image slideshow does not need to have the API defined.
|
||||
slideshowAPI: 2
|
||||
|
||||
|
||||
# These options are to customize online uploading of logs to pastebins:
|
||||
# - type : Defines the kind of pastebin service to be used. Currently
|
||||
# it accepts two values:
|
||||
# - none : disables the pastebin functionality
|
||||
# - fiche : use fiche pastebin server
|
||||
# - url : Defines the address of pastebin service to be used.
|
||||
# Takes string as input. Important bits are the host and port,
|
||||
# the scheme is not used.
|
||||
# - sizeLimit : Defines maximum size limit (in KiB) of log file to be pasted.
|
||||
# The option must be set, to have the log option work.
|
||||
# Takes integer as input. If < 0, no limit will be forced,
|
||||
# else only last (approximately) 'n' KiB of log file will be pasted.
|
||||
# Please note that upload size may be slightly over the limit (due
|
||||
# to last minute logging), so provide a suitable value.
|
||||
uploadServer :
|
||||
type : "fiche"
|
||||
url : "http://termbin.com:9999"
|
||||
sizeLimit : -1
|
||||
17
calamares/src/branding/default/lang/calamares-default_ar.ts
Normal file
17
calamares/src/branding/default/lang/calamares-default_ar.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="ar">
|
||||
<context>
|
||||
<name>show</name>
|
||||
<message>
|
||||
<location filename="../show.qml" line="64"/>
|
||||
<source>This is a second Slide element.</source>
|
||||
<translation>عرض الثاني</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../show.qml" line="68"/>
|
||||
<source>This is a third Slide element.</source>
|
||||
<translation>عرض الثالث</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
17
calamares/src/branding/default/lang/calamares-default_en.ts
Normal file
17
calamares/src/branding/default/lang/calamares-default_en.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="en">
|
||||
<context>
|
||||
<name>show</name>
|
||||
<message>
|
||||
<location filename="../show.qml" line="64"/>
|
||||
<source>This is a second Slide element.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../show.qml" line="68"/>
|
||||
<source>This is a third Slide element.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
17
calamares/src/branding/default/lang/calamares-default_eo.ts
Normal file
17
calamares/src/branding/default/lang/calamares-default_eo.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="eo">
|
||||
<context>
|
||||
<name>show</name>
|
||||
<message>
|
||||
<location filename="../show.qml" line="64"/>
|
||||
<source>This is a second Slide element.</source>
|
||||
<translation>Ĉi tio estas la dua gliteja.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../show.qml" line="68"/>
|
||||
<source>This is a third Slide element.</source>
|
||||
<translation>Ĉi tio estas la tria gliteja.</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
17
calamares/src/branding/default/lang/calamares-default_fr.ts
Normal file
17
calamares/src/branding/default/lang/calamares-default_fr.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="fr">
|
||||
<context>
|
||||
<name>show</name>
|
||||
<message>
|
||||
<location filename="../show.qml" line="64"/>
|
||||
<source>This is a second Slide element.</source>
|
||||
<translation>Ceci est la deuxieme affiche.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../show.qml" line="68"/>
|
||||
<source>This is a third Slide element.</source>
|
||||
<translation>La troisième affice ce trouve ici.</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
17
calamares/src/branding/default/lang/calamares-default_nl.ts
Normal file
17
calamares/src/branding/default/lang/calamares-default_nl.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!DOCTYPE TS>
|
||||
<TS version="2.1" language="nl">
|
||||
<context>
|
||||
<name>show</name>
|
||||
<message>
|
||||
<location filename="../show.qml" line="64"/>
|
||||
<source>This is a second Slide element.</source>
|
||||
<translation>Dit is het tweede Dia element.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../show.qml" line="68"/>
|
||||
<source>This is a third Slide element.</source>
|
||||
<translation>Dit is het derde Dia element.</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
BIN
calamares/src/branding/default/languages.png
Normal file
BIN
calamares/src/branding/default/languages.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 84 KiB |
2
calamares/src/branding/default/languages.png.license
Normal file
2
calamares/src/branding/default/languages.png.license
Normal file
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
77
calamares/src/branding/default/show.qml
Normal file
77
calamares/src/branding/default/show.qml
Normal file
@@ -0,0 +1,77 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
import QtQuick 2.0;
|
||||
import calamares.slideshow 1.0;
|
||||
|
||||
Presentation
|
||||
{
|
||||
id: presentation
|
||||
|
||||
function nextSlide() {
|
||||
console.log("QML Component (default slideshow) Next slide");
|
||||
presentation.goToNextSlide();
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: advanceTimer
|
||||
interval: 1000
|
||||
running: presentation.activatedInCalamares
|
||||
repeat: true
|
||||
onTriggered: nextSlide()
|
||||
}
|
||||
|
||||
Slide {
|
||||
|
||||
Image {
|
||||
id: background
|
||||
source: "squid.png"
|
||||
width: 200; height: 200
|
||||
fillMode: Image.PreserveAspectFit
|
||||
anchors.centerIn: parent
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: background.horizontalCenter
|
||||
anchors.top: background.bottom
|
||||
text: "This is a customizable QML slideshow.<br/>"+
|
||||
"Distributions should provide their own slideshow and list it in <br/>"+
|
||||
"their custom branding.desc file.<br/>"+
|
||||
"To create a Calamares presentation in QML, import calamares.slideshow,<br/>"+
|
||||
"define a Presentation element with as many Slide elements as needed."
|
||||
wrapMode: Text.WordWrap
|
||||
width: presentation.width
|
||||
horizontalAlignment: Text.Center
|
||||
}
|
||||
}
|
||||
|
||||
Slide {
|
||||
centeredText: qsTr("This is a second Slide element.")
|
||||
}
|
||||
|
||||
Slide {
|
||||
centeredText: qsTr("This is a third Slide element.")
|
||||
}
|
||||
|
||||
// When this slideshow is loaded as a V1 slideshow, only
|
||||
// activatedInCalamares is set, which starts the timer (see above).
|
||||
//
|
||||
// In V2, also the onActivate() and onLeave() methods are called.
|
||||
// These example functions log a message (and re-start the slides
|
||||
// from the first).
|
||||
function onActivate() {
|
||||
console.log("QML Component (default slideshow) activated");
|
||||
presentation.currentSlide = 0;
|
||||
}
|
||||
|
||||
function onLeave() {
|
||||
console.log("QML Component (default slideshow) deactivated");
|
||||
}
|
||||
|
||||
}
|
||||
BIN
calamares/src/branding/default/squid.png
Normal file
BIN
calamares/src/branding/default/squid.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
2
calamares/src/branding/default/squid.png.license
Normal file
2
calamares/src/branding/default/squid.png.license
Normal file
@@ -0,0 +1,2 @@
|
||||
SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
96
calamares/src/branding/default/stylesheet.qss
Normal file
96
calamares/src/branding/default/stylesheet.qss
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: no
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*/
|
||||
|
||||
/*
|
||||
A branding component can ship a stylesheet (like this one)
|
||||
which is applied to parts of the Calamares user-interface.
|
||||
In principle, all parts can be styled through CSS.
|
||||
Missing parts should be filed as issues.
|
||||
|
||||
The IDs are based on the object names in the C++ code.
|
||||
You can use the Debug Dialog to find out object names:
|
||||
- Open the debug dialog
|
||||
- Choose tab *Tools*
|
||||
- Click *Widget Tree* button
|
||||
The list of object names is printed in the log.
|
||||
|
||||
Documentation for styling Qt Widgets through a stylesheet
|
||||
can be found at
|
||||
https://doc.qt.io/qt-5/stylesheet-examples.html
|
||||
https://doc.qt.io/qt-5/stylesheet-reference.html
|
||||
In Calamares, styling widget classes is supported (e.g.
|
||||
using `QComboBox` as a selector).
|
||||
|
||||
This example stylesheet has all the actual styling commented out.
|
||||
The examples are not exhaustive.
|
||||
|
||||
*/
|
||||
|
||||
/*** Generic Widgets.
|
||||
*
|
||||
* You can style **all** widgets of a given class by selecting
|
||||
* the class name. Some widgets have specialized sub-selectors.
|
||||
*/
|
||||
|
||||
/*
|
||||
QPushButton { background-color: green; }
|
||||
*/
|
||||
|
||||
/*** Main application window.
|
||||
*
|
||||
* The main application window has the sidebar, which in turn
|
||||
* contains a logo and a list of items -- note that the list
|
||||
* can **not** be styled, since it has its own custom C++
|
||||
* delegate code.
|
||||
*/
|
||||
|
||||
/*
|
||||
#mainApp { }
|
||||
#sidebarApp { }
|
||||
#logoApp { }
|
||||
*/
|
||||
|
||||
/*** Welcome module.
|
||||
*
|
||||
* There are plenty of parts, but the buttons are the most interesting
|
||||
* ones (donate, release notes, ...). The little icon image can be
|
||||
* styled through *qproperty-icon*, which is a little obscure.
|
||||
* URLs can reference the QRC paths of the Calamares application
|
||||
* or loaded via plugins or within the filesystem. There is no
|
||||
* comprehensive list of available icons, though.
|
||||
*/
|
||||
|
||||
/*
|
||||
QPushButton#aboutButton { qproperty-icon: url(:/data/images/release.svg); }
|
||||
#donateButton,
|
||||
#supportButton,
|
||||
#releaseNotesButton,
|
||||
#knownIssuesButton { qproperty-icon: url(:/data/images/help.svg); }
|
||||
*/
|
||||
|
||||
/*** Partitioning module.
|
||||
*
|
||||
* Many moving parts, which you will need to experiment with.
|
||||
*/
|
||||
|
||||
/*
|
||||
#bootInfoIcon { }
|
||||
#bootInfoLable { }
|
||||
#deviceInfoIcon { }
|
||||
#defineInfoLabel { }
|
||||
#scrollAreaWidgetContents { }
|
||||
#partitionBarView { }
|
||||
*/
|
||||
|
||||
/*** Licensing module.
|
||||
*
|
||||
* The licensing module paints individual widgets for each of
|
||||
* the licenses. The item can be collapsed or expanded.
|
||||
*/
|
||||
|
||||
/*
|
||||
#licenseItem { }
|
||||
#licenseItemFullText { }
|
||||
*/
|
||||
67
calamares/src/calamares/CMakeLists.txt
Normal file
67
calamares/src/calamares/CMakeLists.txt
Normal file
@@ -0,0 +1,67 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
|
||||
set(calamaresSources
|
||||
main.cpp
|
||||
CalamaresApplication.cpp
|
||||
CalamaresWindow.cpp
|
||||
DebugWindow.cpp
|
||||
VariantModel.cpp
|
||||
progresstree/ProgressTreeDelegate.cpp
|
||||
progresstree/ProgressTreeView.cpp
|
||||
)
|
||||
|
||||
include_directories(
|
||||
${CMAKE_SOURCE_DIR}/src/libcalamares
|
||||
${CMAKE_SOURCE_DIR}/src/libcalamaresui
|
||||
${CMAKE_BINARY_DIR}/src/libcalamares
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
### EXECUTABLE
|
||||
#
|
||||
# "calamares_bin" is the main application, not to be confused with
|
||||
# the target "calamares" which is the non-GUI library part.
|
||||
#
|
||||
# The calamares-i18n.cxx file -- full path in CALAMARES_TRANSLATIONS_SOURCE --
|
||||
# is created as a target in the lang/ directory. This is compiled to a
|
||||
# library (it's just the result of a QRC compile).
|
||||
add_executable(calamares_bin ${calamaresSources} calamares.qrc)
|
||||
target_include_directories(calamares_bin PRIVATE ${CMAKE_SOURCE_DIR})
|
||||
set_target_properties(calamares_bin PROPERTIES ENABLE_EXPORTS TRUE RUNTIME_OUTPUT_NAME calamares)
|
||||
calamares_automoc( calamares_bin )
|
||||
calamares_autouic( calamares_bin )
|
||||
calamares_autorcc( calamares_bin )
|
||||
|
||||
target_link_libraries(
|
||||
calamares_bin
|
||||
PRIVATE calamares calamaresui calamares-i18n kdsingleapplication ${qtname}::Core ${qtname}::Widgets
|
||||
)
|
||||
target_link_libraries(calamares_bin PRIVATE ${kfname}::CoreAddons)
|
||||
if(BUILD_CRASH_REPORTING)
|
||||
target_link_libraries(calamares_bin PRIVATE ${kfname}::Crash)
|
||||
target_compile_definitions(calamares_bin PRIVATE BUILD_CRASH_REPORTING)
|
||||
endif()
|
||||
|
||||
install(TARGETS calamares_bin BUNDLE DESTINATION . RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
install(
|
||||
FILES ${CMAKE_SOURCE_DIR}/data/images/squid.svg
|
||||
RENAME calamares.svg
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps
|
||||
)
|
||||
|
||||
### TESTS
|
||||
#
|
||||
#
|
||||
if(BUILD_TESTING)
|
||||
# Don't install, these are just for enable_testing
|
||||
add_executable(loadmodule testmain.cpp)
|
||||
target_link_libraries(loadmodule PRIVATE ${qtname}::Core ${qtname}::Widgets calamares calamaresui)
|
||||
|
||||
add_executable(test_conf test_conf.cpp)
|
||||
target_link_libraries(test_conf PUBLIC yamlcpp::yamlcpp ${qtname}::Core)
|
||||
endif()
|
||||
302
calamares/src/calamares/CalamaresApplication.cpp
Normal file
302
calamares/src/calamares/CalamaresApplication.cpp
Normal file
@@ -0,0 +1,302 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
#include "CalamaresApplication.h"
|
||||
|
||||
#include "CalamaresConfig.h"
|
||||
#include "CalamaresVersionX.h"
|
||||
#include "CalamaresWindow.h"
|
||||
#include "progresstree/ProgressTreeView.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "ViewManager.h"
|
||||
#include "locale/TranslationsModel.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
#include "utils/Dirs.h"
|
||||
#include "utils/Gui.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/System.h"
|
||||
#ifdef WITH_QML
|
||||
#include "utils/Qml.h"
|
||||
#endif
|
||||
#include "utils/Retranslator.h"
|
||||
#include "viewpages/ViewStep.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QScreen>
|
||||
#include <QTimer>
|
||||
|
||||
/// @brief Convenience for "are the settings in debug mode"
|
||||
static bool
|
||||
isDebug()
|
||||
{
|
||||
return Calamares::Settings::instance() && Calamares::Settings::instance()->debugMode();
|
||||
}
|
||||
|
||||
CalamaresApplication::CalamaresApplication( int& argc, char* argv[] )
|
||||
: QApplication( argc, argv )
|
||||
, m_mainwindow( nullptr )
|
||||
, m_moduleManager( nullptr )
|
||||
{
|
||||
// Setting the organization name makes the default cache
|
||||
// directory -- where Calamares stores logs, for instance --
|
||||
// <org>/<app>/, so we end up with ~/.cache/Calamares/calamares/
|
||||
// which is excessively squidly.
|
||||
//
|
||||
// setOrganizationName( QStringLiteral( CALAMARES_ORGANIZATION_NAME ) );
|
||||
setOrganizationDomain( QStringLiteral( CALAMARES_ORGANIZATION_DOMAIN ) );
|
||||
setApplicationName( QStringLiteral( CALAMARES_APPLICATION_NAME ) );
|
||||
setApplicationVersion( QStringLiteral( CALAMARES_VERSION ) );
|
||||
|
||||
QFont f = font();
|
||||
Calamares::setDefaultFontSize( f.pointSize() );
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::init()
|
||||
{
|
||||
Logger::setupLogfile();
|
||||
cDebug() << "Calamares version:" << CALAMARES_VERSION;
|
||||
cDebug() << Logger::SubEntry << "Using Qt version:" << qVersion();
|
||||
cDebug() << Logger::SubEntry << "Build type:" << CMAKE_BUILD_TYPE;
|
||||
#ifdef WITH_PYBIND11
|
||||
cDebug() << Logger::SubEntry << "Using PyBind11";
|
||||
#endif
|
||||
#ifdef WITH_BOOST_PYTHON
|
||||
cDebug() << Logger::SubEntry << "Using Boost Python";
|
||||
#endif
|
||||
cDebug() << Logger::SubEntry << "Using settings:" << Calamares::Settings::instance()->path();
|
||||
cDebug() << Logger::SubEntry << "Using log file:" << Logger::logFile();
|
||||
cDebug() << Logger::SubEntry << "Languages:" << Calamares::Locale::availableLanguages();
|
||||
|
||||
if ( !Calamares::Settings::instance() )
|
||||
{
|
||||
cError() << "Must create Calamares::Settings before the application.";
|
||||
::exit( 1 );
|
||||
}
|
||||
initQmlPath();
|
||||
initBranding();
|
||||
|
||||
Calamares::installTranslator();
|
||||
|
||||
setQuitOnLastWindowClosed( false );
|
||||
setWindowIcon( QIcon( Calamares::Branding::instance()->imagePath( Calamares::Branding::ProductIcon ) ) );
|
||||
|
||||
cDebug() << Logger::SubEntry << "STARTUP: initSettings, initQmlPath, initBranding done";
|
||||
|
||||
initModuleManager(); //also shows main window
|
||||
|
||||
cDebug() << Logger::SubEntry << "STARTUP: initModuleManager: module init started";
|
||||
}
|
||||
|
||||
CalamaresApplication::~CalamaresApplication()
|
||||
{
|
||||
Logger::CDebug( Logger::LOGVERBOSE ) << "Shutting down Calamares...";
|
||||
Logger::CDebug( Logger::LOGVERBOSE ) << Logger::SubEntry << "Finished shutdown.";
|
||||
}
|
||||
|
||||
CalamaresApplication*
|
||||
CalamaresApplication::instance()
|
||||
{
|
||||
return qobject_cast< CalamaresApplication* >( QApplication::instance() );
|
||||
}
|
||||
|
||||
CalamaresWindow*
|
||||
CalamaresApplication::mainWindow()
|
||||
{
|
||||
return m_mainwindow;
|
||||
}
|
||||
|
||||
static QStringList
|
||||
brandingFileCandidates( bool assumeBuilddir, const QString& brandingFilename )
|
||||
{
|
||||
QStringList brandingPaths;
|
||||
if ( Calamares::isAppDataDirOverridden() )
|
||||
{
|
||||
brandingPaths << Calamares::appDataDir().absoluteFilePath( brandingFilename );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( assumeBuilddir )
|
||||
{
|
||||
brandingPaths << ( QDir::currentPath() + QStringLiteral( "/src/" ) + brandingFilename );
|
||||
}
|
||||
if ( Calamares::haveExtraDirs() )
|
||||
{
|
||||
for ( auto s : Calamares::extraDataDirs() )
|
||||
{
|
||||
brandingPaths << ( s + brandingFilename );
|
||||
}
|
||||
}
|
||||
brandingPaths << QDir( CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/" ).absoluteFilePath( brandingFilename );
|
||||
brandingPaths << Calamares::appDataDir().absoluteFilePath( brandingFilename );
|
||||
}
|
||||
|
||||
return brandingPaths;
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initQmlPath()
|
||||
{
|
||||
#ifdef WITH_QML
|
||||
if ( !Calamares::initQmlModulesDir() )
|
||||
{
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initBranding()
|
||||
{
|
||||
QString brandingComponentName = Calamares::Settings::instance()->brandingComponentName();
|
||||
if ( brandingComponentName.simplified().isEmpty() )
|
||||
{
|
||||
cError() << "FATAL: branding component not set in settings.conf";
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
QString brandingDescriptorSubpath = QString( "branding/%1/branding.desc" ).arg( brandingComponentName );
|
||||
QStringList brandingFileCandidatesByPriority = brandingFileCandidates( isDebug(), brandingDescriptorSubpath );
|
||||
|
||||
QFileInfo brandingFile;
|
||||
bool found = false;
|
||||
|
||||
foreach ( const QString& path, brandingFileCandidatesByPriority )
|
||||
{
|
||||
QFileInfo pathFi( path );
|
||||
if ( pathFi.exists() && pathFi.isReadable() )
|
||||
{
|
||||
brandingFile = pathFi;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !found || !brandingFile.exists() || !brandingFile.isReadable() )
|
||||
{
|
||||
cError() << "Cowardly refusing to continue startup without branding."
|
||||
<< Logger::DebugList( brandingFileCandidatesByPriority );
|
||||
if ( Calamares::isAppDataDirOverridden() )
|
||||
{
|
||||
cError() << "FATAL: explicitly configured application data directory is missing" << brandingComponentName;
|
||||
}
|
||||
else
|
||||
{
|
||||
cError() << "FATAL: none of the expected branding descriptor file paths exist.";
|
||||
}
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
new Calamares::Branding( brandingFile.absoluteFilePath(), this, devicePixelRatio() );
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initModuleManager()
|
||||
{
|
||||
m_moduleManager = new Calamares::ModuleManager( Calamares::Settings::instance()->modulesSearchPaths(), this );
|
||||
connect( m_moduleManager, &Calamares::ModuleManager::initDone, this, &CalamaresApplication::initView );
|
||||
m_moduleManager->init();
|
||||
}
|
||||
|
||||
/** @brief centers the widget @p w on (a) screen
|
||||
*
|
||||
* This tries to duplicate the (deprecated) qApp->desktop()->availableGeometry()
|
||||
* placement by iterating over screens and putting Calamares in the first
|
||||
* one where it fits; this is *generally* the primary screen.
|
||||
*
|
||||
* With debugging, it would look something like this (2 screens attached,
|
||||
* primary at +1080+240 because I have a very strange X setup). Before
|
||||
* being mapped, the Calamares window is at +0+0 but does have a size.
|
||||
* The first screen's geometry includes the offset from the origin in
|
||||
* screen coordinates.
|
||||
*
|
||||
* Proposed window size: 1024 520
|
||||
* Window QRect(0,0 1024x520)
|
||||
* Screen QRect(1080,240 2560x1440)
|
||||
* Moving QPoint(1848,700)
|
||||
* Screen QRect(0,0 1080x1920)
|
||||
*
|
||||
*/
|
||||
static void
|
||||
centerWindowOnScreen( QWidget* w )
|
||||
{
|
||||
QList< QScreen* > screens = qApp->screens();
|
||||
QPoint windowCenter = w->rect().center();
|
||||
QSize windowSize = w->rect().size();
|
||||
|
||||
for ( const auto* screen : screens )
|
||||
{
|
||||
QSize screenSize = screen->availableGeometry().size();
|
||||
if ( ( screenSize.width() >= windowSize.width() ) && ( screenSize.height() >= windowSize.height() ) )
|
||||
{
|
||||
w->move( screen->availableGeometry().center() - windowCenter );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initView()
|
||||
{
|
||||
cDebug() << "STARTUP: initModuleManager: all modules init done";
|
||||
initJobQueue();
|
||||
cDebug() << "STARTUP: initJobQueue done";
|
||||
|
||||
m_mainwindow = new CalamaresWindow(); //also creates ViewManager
|
||||
|
||||
connect( m_moduleManager, &Calamares::ModuleManager::modulesLoaded, this, &CalamaresApplication::initViewSteps );
|
||||
connect( m_moduleManager, &Calamares::ModuleManager::modulesFailed, this, &CalamaresApplication::initFailed );
|
||||
|
||||
QTimer::singleShot( 0, m_moduleManager, &Calamares::ModuleManager::loadModules );
|
||||
|
||||
if ( Calamares::Branding::instance() && Calamares::Branding::instance()->windowPlacementCentered() )
|
||||
{
|
||||
centerWindowOnScreen( m_mainwindow );
|
||||
}
|
||||
cDebug() << "STARTUP: CalamaresWindow created; loadModules started";
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initViewSteps()
|
||||
{
|
||||
cDebug() << "STARTUP: loadModules for all modules done";
|
||||
m_moduleManager->checkRequirements();
|
||||
if ( Calamares::Branding::instance()->windowMaximize() )
|
||||
{
|
||||
m_mainwindow->setWindowFlag( Qt::FramelessWindowHint );
|
||||
m_mainwindow->showMaximized();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_mainwindow->show();
|
||||
}
|
||||
|
||||
cDebug() << "STARTUP: Window now visible and ProgressTreeView populated";
|
||||
cDebug() << Logger::SubEntry << Calamares::ViewManager::instance()->viewSteps().count() << "view steps loaded.";
|
||||
Calamares::ViewManager::instance()->onInitComplete();
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initFailed( const QStringList& l )
|
||||
{
|
||||
cError() << "STARTUP: failed modules are" << l;
|
||||
m_mainwindow->show();
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresApplication::initJobQueue()
|
||||
{
|
||||
Calamares::JobQueue* jobQueue = new Calamares::JobQueue( this );
|
||||
new Calamares::System( Calamares::Settings::instance()->doChroot(), this );
|
||||
Calamares::Branding::instance()->setGlobals( jobQueue->globalStorage() );
|
||||
}
|
||||
64
calamares/src/calamares/CalamaresApplication.h
Normal file
64
calamares/src/calamares/CalamaresApplication.h
Normal file
@@ -0,0 +1,64 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARESAPPLICATION_H
|
||||
#define CALAMARESAPPLICATION_H
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
class CalamaresWindow;
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
class ModuleManager;
|
||||
} // namespace Calamares
|
||||
|
||||
|
||||
/**
|
||||
* @brief The CalamaresApplication class extends QApplication to handle
|
||||
* Calamares startup and lifetime of main components.
|
||||
*/
|
||||
class CalamaresApplication : public QApplication
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalamaresApplication( int& argc, char* argv[] );
|
||||
~CalamaresApplication() override;
|
||||
|
||||
/**
|
||||
* @brief init handles the first part of Calamares application startup.
|
||||
* After the main window shows up, the latter part of the startup sequence
|
||||
* (including modules loading) happens asynchronously.
|
||||
*/
|
||||
void init();
|
||||
static CalamaresApplication* instance();
|
||||
|
||||
/**
|
||||
* @brief mainWindow returns the Calamares application main window.
|
||||
*/
|
||||
CalamaresWindow* mainWindow();
|
||||
|
||||
private slots:
|
||||
void initView();
|
||||
void initViewSteps();
|
||||
void initFailed( const QStringList& l );
|
||||
|
||||
private:
|
||||
// Initialization steps happen in this order
|
||||
void initQmlPath();
|
||||
void initBranding();
|
||||
void initModuleManager();
|
||||
void initJobQueue();
|
||||
|
||||
CalamaresWindow* m_mainwindow;
|
||||
Calamares::ModuleManager* m_moduleManager;
|
||||
};
|
||||
|
||||
#endif // CALAMARESAPPLICATION_H
|
||||
552
calamares/src/calamares/CalamaresWindow.cpp
Normal file
552
calamares/src/calamares/CalamaresWindow.cpp
Normal file
@@ -0,0 +1,552 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Raul Rodrigo Segura (raurodse)
|
||||
* SPDX-FileCopyrightText: 2019 Collabora Ltd <arnaud.ferraris@collabora.com>
|
||||
* SPDX-FileCopyrightText: 2020 Anubhav Choudhary <ac.10edu@gmail.com>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CalamaresWindow.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "CalamaresConfig.h"
|
||||
#include "DebugWindow.h"
|
||||
#include "Settings.h"
|
||||
#include "ViewManager.h"
|
||||
#include "progresstree/ProgressTreeView.h"
|
||||
#include "utils/Gui.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Qml.h"
|
||||
#include "utils/Retranslator.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBoxLayout>
|
||||
#include <QCloseEvent>
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
#include <QDesktopWidget>
|
||||
#endif
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#ifdef WITH_QML
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickItem>
|
||||
#include <QQuickWidget>
|
||||
#endif
|
||||
#include <QTreeView>
|
||||
|
||||
static QSize
|
||||
desktopSize( QWidget* w )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
return qApp->desktop()->availableGeometry( w ).size();
|
||||
#else
|
||||
return w->screen()->availableGeometry().size();
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline int
|
||||
windowDimensionToPixels( const Calamares::Branding::WindowDimension& u )
|
||||
{
|
||||
if ( !u.isValid() )
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Pixies )
|
||||
{
|
||||
return static_cast< int >( u.value() );
|
||||
}
|
||||
if ( u.unit() == Calamares::Branding::WindowDimensionUnit::Fonties )
|
||||
{
|
||||
return static_cast< int >( u.value() * Calamares::defaultFontHeight() );
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @brief Expected orientation of the panels, based on their side
|
||||
*
|
||||
* Panels on the left and right are expected to be "vertical" style,
|
||||
* top and bottom should be "horizontal bars". This function maps
|
||||
* the sides to expected orientation.
|
||||
*/
|
||||
static inline Qt::Orientation
|
||||
orientation( const Calamares::Branding::PanelSide s )
|
||||
{
|
||||
using Side = Calamares::Branding::PanelSide;
|
||||
return ( s == Side::Left || s == Side::Right ) ? Qt::Orientation::Vertical : Qt::Orientation::Horizontal;
|
||||
}
|
||||
|
||||
/** @brief Get a button-sized icon. */
|
||||
static inline QPixmap
|
||||
getButtonIcon( const QString& name )
|
||||
{
|
||||
return Calamares::Branding::instance()->image( name, QSize( 22, 22 ) );
|
||||
}
|
||||
|
||||
static inline void
|
||||
setButtonIcon( QPushButton* button, const QString& name )
|
||||
{
|
||||
auto icon = getButtonIcon( name );
|
||||
if ( button && !icon.isNull() )
|
||||
{
|
||||
button->setIcon( icon );
|
||||
}
|
||||
}
|
||||
|
||||
static QWidget*
|
||||
getWidgetSidebar( Calamares::DebugWindowManager* debug,
|
||||
Calamares::ViewManager* viewManager,
|
||||
QWidget* parent,
|
||||
Qt::Orientation,
|
||||
int desiredWidth )
|
||||
{
|
||||
const Calamares::Branding* const branding = Calamares::Branding::instance();
|
||||
|
||||
QWidget* sideBox = new QWidget( parent );
|
||||
sideBox->setObjectName( "sidebarApp" );
|
||||
|
||||
QBoxLayout* sideLayout = new QVBoxLayout;
|
||||
sideBox->setLayout( sideLayout );
|
||||
// Set this attribute into qss file
|
||||
sideBox->setFixedWidth( desiredWidth );
|
||||
sideBox->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding );
|
||||
|
||||
QHBoxLayout* logoLayout = new QHBoxLayout;
|
||||
sideLayout->addLayout( logoLayout );
|
||||
logoLayout->addStretch();
|
||||
QLabel* logoLabel = new QLabel( sideBox );
|
||||
logoLabel->setObjectName( "logoApp" );
|
||||
//Define all values into qss file
|
||||
{
|
||||
QPalette plt = sideBox->palette();
|
||||
sideBox->setAutoFillBackground( true );
|
||||
plt.setColor( sideBox->backgroundRole(), branding->styleString( Calamares::Branding::SidebarBackground ) );
|
||||
plt.setColor( sideBox->foregroundRole(), branding->styleString( Calamares::Branding::SidebarText ) );
|
||||
sideBox->setPalette( plt );
|
||||
logoLabel->setPalette( plt );
|
||||
}
|
||||
logoLabel->setAlignment( Qt::AlignCenter );
|
||||
logoLabel->setFixedSize( 80, 80 );
|
||||
logoLabel->setPixmap( branding->image( Calamares::Branding::ProductLogo, logoLabel->size() ) );
|
||||
logoLayout->addWidget( logoLabel );
|
||||
logoLayout->addStretch();
|
||||
|
||||
ProgressTreeView* tv = new ProgressTreeView( sideBox );
|
||||
tv->setModel( viewManager );
|
||||
tv->setFocusPolicy( Qt::NoFocus );
|
||||
sideLayout->addWidget( tv );
|
||||
|
||||
QHBoxLayout* extraButtons = new QHBoxLayout;
|
||||
sideLayout->addLayout( extraButtons );
|
||||
|
||||
const int defaultFontHeight = Calamares::defaultFontHeight();
|
||||
|
||||
if ( /* About-Calamares Button enabled */ true )
|
||||
{
|
||||
QPushButton* aboutDialog = new QPushButton;
|
||||
aboutDialog->setObjectName( "aboutButton" );
|
||||
aboutDialog->setIcon( Calamares::defaultPixmap(
|
||||
Calamares::Information, Calamares::Original, 2 * QSize( defaultFontHeight, defaultFontHeight ) ) );
|
||||
CALAMARES_RETRANSLATE_FOR(
|
||||
aboutDialog, aboutDialog->setText( QCoreApplication::translate( "calamares-sidebar", "About", "@button" ) );
|
||||
aboutDialog->setToolTip(
|
||||
QCoreApplication::translate( "calamares-sidebar", "Show information about Calamares", "@tooltip" ) ); );
|
||||
extraButtons->addWidget( aboutDialog );
|
||||
aboutDialog->setFlat( true );
|
||||
aboutDialog->setCheckable( true );
|
||||
QObject::connect( aboutDialog, &QPushButton::clicked, debug, &Calamares::DebugWindowManager::about );
|
||||
}
|
||||
if ( debug && debug->enabled() )
|
||||
{
|
||||
QPushButton* debugWindowBtn = new QPushButton;
|
||||
debugWindowBtn->setObjectName( "debugButton" );
|
||||
debugWindowBtn->setIcon( Calamares::defaultPixmap(
|
||||
Calamares::Bugs, Calamares::Original, 2 * QSize( defaultFontHeight, defaultFontHeight ) ) );
|
||||
CALAMARES_RETRANSLATE_FOR(
|
||||
debugWindowBtn,
|
||||
debugWindowBtn->setText( QCoreApplication::translate( "calamares-sidebar", "Debug", "@button" ) );
|
||||
debugWindowBtn->setToolTip(
|
||||
QCoreApplication::translate( "calamares-sidebar", "Show debug information", "@tooltip" ) ); );
|
||||
extraButtons->addWidget( debugWindowBtn );
|
||||
debugWindowBtn->setFlat( true );
|
||||
debugWindowBtn->setCheckable( true );
|
||||
QObject::connect( debugWindowBtn, &QPushButton::clicked, debug, &Calamares::DebugWindowManager::show );
|
||||
QObject::connect(
|
||||
debug, &Calamares::DebugWindowManager::visibleChanged, debugWindowBtn, &QPushButton::setChecked );
|
||||
}
|
||||
|
||||
Calamares::unmarginLayout( sideLayout );
|
||||
return sideBox;
|
||||
}
|
||||
|
||||
static QWidget*
|
||||
getWidgetNavigation( Calamares::DebugWindowManager*,
|
||||
Calamares::ViewManager* viewManager,
|
||||
QWidget* parent,
|
||||
Qt::Orientation,
|
||||
int )
|
||||
{
|
||||
QWidget* navigation = new QWidget( parent );
|
||||
QBoxLayout* bottomLayout = new QHBoxLayout;
|
||||
bottomLayout->addStretch();
|
||||
|
||||
// Create buttons and sets an initial icon; the icons may change
|
||||
{
|
||||
auto* back = new QPushButton(
|
||||
getButtonIcon( QStringLiteral( "go-previous" ) ),
|
||||
QCoreApplication::translate( CalamaresWindow::staticMetaObject.className(), "&Back", "@button" ),
|
||||
navigation );
|
||||
back->setObjectName( "view-button-back" );
|
||||
back->setEnabled( viewManager->backEnabled() );
|
||||
QObject::connect( back, &QPushButton::clicked, viewManager, &Calamares::ViewManager::back );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::backEnabledChanged, back, &QPushButton::setEnabled );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::backLabelChanged, back, &QPushButton::setText );
|
||||
QObject::connect(
|
||||
viewManager, &Calamares::ViewManager::backIconChanged, [ = ]( QString n ) { setButtonIcon( back, n ); } );
|
||||
QObject::connect(
|
||||
viewManager, &Calamares::ViewManager::backAndNextVisibleChanged, back, &QPushButton::setVisible );
|
||||
bottomLayout->addWidget( back );
|
||||
}
|
||||
{
|
||||
auto* next = new QPushButton(
|
||||
getButtonIcon( QStringLiteral( "go-next" ) ),
|
||||
QCoreApplication::translate( CalamaresWindow::staticMetaObject.className(), "&Next", "@button" ),
|
||||
navigation );
|
||||
next->setObjectName( "view-button-next" );
|
||||
next->setEnabled( viewManager->nextEnabled() );
|
||||
QObject::connect( next, &QPushButton::clicked, viewManager, &Calamares::ViewManager::next );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::nextEnabledChanged, next, &QPushButton::setEnabled );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::nextLabelChanged, next, &QPushButton::setText );
|
||||
QObject::connect(
|
||||
viewManager, &Calamares::ViewManager::nextIconChanged, [ = ]( QString n ) { setButtonIcon( next, n ); } );
|
||||
QObject::connect(
|
||||
viewManager, &Calamares::ViewManager::backAndNextVisibleChanged, next, &QPushButton::setVisible );
|
||||
bottomLayout->addWidget( next );
|
||||
}
|
||||
bottomLayout->addSpacing( 12 );
|
||||
{
|
||||
auto* quit = new QPushButton(
|
||||
getButtonIcon( QStringLiteral( "dialog-cancel" ) ),
|
||||
QCoreApplication::translate( CalamaresWindow::staticMetaObject.className(), "&Cancel", "@button" ),
|
||||
navigation );
|
||||
quit->setObjectName( "view-button-cancel" );
|
||||
QObject::connect( quit, &QPushButton::clicked, viewManager, &Calamares::ViewManager::quit );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::quitEnabledChanged, quit, &QPushButton::setEnabled );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::quitLabelChanged, quit, &QPushButton::setText );
|
||||
QObject::connect(
|
||||
viewManager, &Calamares::ViewManager::quitIconChanged, [ = ]( QString n ) { setButtonIcon( quit, n ); } );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::quitTooltipChanged, quit, &QPushButton::setToolTip );
|
||||
QObject::connect( viewManager, &Calamares::ViewManager::quitVisibleChanged, quit, &QPushButton::setVisible );
|
||||
bottomLayout->addWidget( quit );
|
||||
}
|
||||
|
||||
bottomLayout->setContentsMargins( 0, 0, 6, 6 );
|
||||
navigation->setLayout( bottomLayout );
|
||||
return navigation;
|
||||
}
|
||||
|
||||
#ifdef WITH_QML
|
||||
|
||||
static inline void
|
||||
setDimension( QQuickWidget* w, Qt::Orientation o, int desiredWidth )
|
||||
{
|
||||
w->setSizePolicy( o == Qt::Orientation::Vertical ? QSizePolicy::MinimumExpanding : QSizePolicy::Expanding,
|
||||
o == Qt::Orientation::Horizontal ? QSizePolicy::MinimumExpanding : QSizePolicy::Expanding );
|
||||
if ( o == Qt::Orientation::Vertical )
|
||||
{
|
||||
w->setFixedWidth( desiredWidth );
|
||||
}
|
||||
else
|
||||
{
|
||||
// If the QML itself sets a height, use that, otherwise go to 48 pixels
|
||||
// which seems to match what the widget navigation would use for height
|
||||
// (with *my* specific screen, style, etc. so YMMV).
|
||||
//
|
||||
// Bound between (16, 64) with a default of 48.
|
||||
qreal minimumHeight = qBound( qreal( 16 ), w->rootObject() ? w->rootObject()->height() : 48, qreal( 64 ) );
|
||||
w->setMinimumHeight( int( minimumHeight ) );
|
||||
w->setFixedHeight( int( minimumHeight ) );
|
||||
}
|
||||
w->setResizeMode( QQuickWidget::SizeRootObjectToView );
|
||||
}
|
||||
|
||||
static QWidget*
|
||||
getQmlSidebar( Calamares::DebugWindowManager* debug,
|
||||
Calamares::ViewManager*,
|
||||
QWidget* parent,
|
||||
Qt::Orientation o,
|
||||
int desiredWidth )
|
||||
{
|
||||
Calamares::registerQmlModels();
|
||||
QQuickWidget* w = new QQuickWidget( parent );
|
||||
if ( debug )
|
||||
{
|
||||
w->engine()->rootContext()->setContextProperty( "debug", debug );
|
||||
}
|
||||
|
||||
w->setSource(
|
||||
QUrl( Calamares::searchQmlFile( Calamares::QmlSearch::Both, QStringLiteral( "calamares-sidebar" ) ) ) );
|
||||
setDimension( w, o, desiredWidth );
|
||||
return w;
|
||||
}
|
||||
|
||||
static QWidget*
|
||||
getQmlNavigation( Calamares::DebugWindowManager* debug,
|
||||
Calamares::ViewManager*,
|
||||
QWidget* parent,
|
||||
Qt::Orientation o,
|
||||
int desiredWidth )
|
||||
{
|
||||
Calamares::registerQmlModels();
|
||||
QQuickWidget* w = new QQuickWidget( parent );
|
||||
if ( debug )
|
||||
{
|
||||
w->engine()->rootContext()->setContextProperty( "debug", debug );
|
||||
}
|
||||
w->setSource(
|
||||
QUrl( Calamares::searchQmlFile( Calamares::QmlSearch::Both, QStringLiteral( "calamares-navigation" ) ) ) );
|
||||
setDimension( w, o, desiredWidth );
|
||||
return w;
|
||||
}
|
||||
#else
|
||||
// Bogus to keep the linker happy
|
||||
//
|
||||
// Calls to flavoredWidget() still refer to these *names*
|
||||
// even if they are subsequently not used.
|
||||
static QWidget*
|
||||
getQmlSidebar( Calamares::DebugWindowManager*,
|
||||
Calamares::ViewManager*,
|
||||
QWidget* parent,
|
||||
Qt::Orientation,
|
||||
int desiredWidth )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
static QWidget*
|
||||
getQmlNavigation( Calamares::DebugWindowManager*,
|
||||
Calamares::ViewManager*,
|
||||
QWidget* parent,
|
||||
Qt::Orientation,
|
||||
int desiredWidth )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
/**@brief Picks one of two methods to call
|
||||
*
|
||||
* Calls method (member function) @p widget or @p qml with arguments @p a
|
||||
* on the given window, based on the flavor.
|
||||
*/
|
||||
template < typename widgetMaker, typename... args >
|
||||
QWidget*
|
||||
flavoredWidget( Calamares::Branding::PanelFlavor flavor,
|
||||
Qt::Orientation o,
|
||||
Calamares::DebugWindowManager* w,
|
||||
QWidget* parent,
|
||||
widgetMaker widget,
|
||||
widgetMaker qml, // Only if WITH_QML is on
|
||||
args... a )
|
||||
{
|
||||
#ifndef WITH_QML
|
||||
Q_UNUSED( qml )
|
||||
#endif
|
||||
auto* viewManager = Calamares::ViewManager::instance();
|
||||
switch ( flavor )
|
||||
{
|
||||
case Calamares::Branding::PanelFlavor::Widget:
|
||||
return widget( w, viewManager, parent, o, a... );
|
||||
#ifdef WITH_QML
|
||||
case Calamares::Branding::PanelFlavor::Qml:
|
||||
return qml( w, viewManager, parent, o, a... );
|
||||
#endif
|
||||
case Calamares::Branding::PanelFlavor::None:
|
||||
return nullptr;
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
/** @brief Adds widgets to @p layout if they belong on this @p side
|
||||
*/
|
||||
static inline void
|
||||
insertIf( QBoxLayout* layout,
|
||||
Calamares::Branding::PanelSide side,
|
||||
QWidget* first,
|
||||
Calamares::Branding::PanelSide firstSide )
|
||||
{
|
||||
if ( first && side == firstSide )
|
||||
{
|
||||
layout->addWidget( first );
|
||||
}
|
||||
}
|
||||
|
||||
CalamaresWindow::CalamaresWindow( QWidget* parent )
|
||||
: QWidget( parent )
|
||||
, m_debugManager( new Calamares::DebugWindowManager( this ) )
|
||||
, m_viewManager( nullptr )
|
||||
{
|
||||
installEventFilter( Calamares::Retranslator::instance() );
|
||||
|
||||
// If we can never cancel, don't show the window-close button
|
||||
if ( Calamares::Settings::instance()->disableCancel() )
|
||||
{
|
||||
setWindowFlag( Qt::WindowCloseButtonHint, false );
|
||||
}
|
||||
|
||||
// %1 is the distribution name
|
||||
CALAMARES_RETRANSLATE( const auto* branding = Calamares::Branding::instance();
|
||||
setWindowTitle( Calamares::Settings::instance()->isSetupMode()
|
||||
? tr( "%1 Setup Program" ).arg( branding->productName() )
|
||||
: tr( "%1 Installer" ).arg( branding->productName() ) ); );
|
||||
|
||||
const Calamares::Branding* const branding = Calamares::Branding::instance();
|
||||
using ImageEntry = Calamares::Branding::ImageEntry;
|
||||
|
||||
using Calamares::windowMinimumHeight;
|
||||
using Calamares::windowMinimumWidth;
|
||||
using Calamares::windowPreferredHeight;
|
||||
using Calamares::windowPreferredWidth;
|
||||
|
||||
using PanelSide = Calamares::Branding::PanelSide;
|
||||
|
||||
// Needs to match what's checked in DebugWindow
|
||||
this->setObjectName( "mainApp" );
|
||||
|
||||
QSize availableSize = desktopSize( this );
|
||||
QSize minimumSize( qBound( windowMinimumWidth, availableSize.width(), windowPreferredWidth ),
|
||||
qBound( windowMinimumHeight, availableSize.height(), windowPreferredHeight ) );
|
||||
setMinimumSize( minimumSize );
|
||||
|
||||
cDebug() << "Available desktop" << availableSize << "minimum size" << minimumSize;
|
||||
|
||||
auto brandingSizes = branding->windowSize();
|
||||
|
||||
int w = qBound( minimumSize.width(), windowDimensionToPixels( brandingSizes.first ), availableSize.width() );
|
||||
int h = qBound( minimumSize.height(), windowDimensionToPixels( brandingSizes.second ), availableSize.height() );
|
||||
|
||||
cDebug() << Logger::SubEntry << "Proposed window size:" << w << h;
|
||||
resize( w, h );
|
||||
|
||||
QWidget* baseWidget = this;
|
||||
if ( !( branding->imagePath( ImageEntry::ProductWallpaper ).isEmpty() ) )
|
||||
{
|
||||
QWidget* label = new QWidget( this );
|
||||
QVBoxLayout* l = new QVBoxLayout;
|
||||
Calamares::unmarginLayout( l );
|
||||
l->addWidget( label );
|
||||
setLayout( l );
|
||||
label->setObjectName( "backgroundWidget" );
|
||||
label->setStyleSheet(
|
||||
QStringLiteral( "#backgroundWidget { background-image: url(%1); background-repeat: repeat-xy; }" )
|
||||
.arg( branding->imagePath( ImageEntry::ProductWallpaper ) ) );
|
||||
|
||||
baseWidget = label;
|
||||
}
|
||||
|
||||
m_viewManager = Calamares::ViewManager::instance( baseWidget );
|
||||
if ( branding->windowExpands() )
|
||||
{
|
||||
connect( m_viewManager, &Calamares::ViewManager::ensureSize, this, &CalamaresWindow::ensureSize );
|
||||
}
|
||||
// NOTE: Although the ViewManager has a signal cancelEnabled() that
|
||||
// signals when the state of the cancel button changes (in
|
||||
// particular, to disable cancel during the exec phase),
|
||||
// we don't connect to it here. Changing the window flag
|
||||
// for the close button causes uncomfortable window flashing
|
||||
// and requires an extra show() (at least with KWin/X11) which
|
||||
// is too annoying. Instead, leave it up to ignoring-the-quit-
|
||||
// event, which is also the ViewManager's responsibility.
|
||||
|
||||
QBoxLayout* mainLayout = new QHBoxLayout;
|
||||
QBoxLayout* contentsLayout = new QVBoxLayout;
|
||||
contentsLayout->setSpacing( 0 );
|
||||
|
||||
QWidget* sideBox
|
||||
= flavoredWidget( branding->sidebarFlavor(),
|
||||
::orientation( branding->sidebarSide() ),
|
||||
m_debugManager,
|
||||
baseWidget,
|
||||
::getWidgetSidebar,
|
||||
::getQmlSidebar,
|
||||
qBound( 100, Calamares::defaultFontHeight() * 12, w < windowPreferredWidth ? 100 : 190 ) );
|
||||
QWidget* navigation = flavoredWidget( branding->navigationFlavor(),
|
||||
::orientation( branding->navigationSide() ),
|
||||
m_debugManager,
|
||||
baseWidget,
|
||||
::getWidgetNavigation,
|
||||
::getQmlNavigation,
|
||||
64 );
|
||||
|
||||
// Build up the contentsLayout (a VBox) top-to-bottom
|
||||
// .. note that the bottom is mirrored wrt. the top
|
||||
insertIf( contentsLayout, PanelSide::Top, sideBox, branding->sidebarSide() );
|
||||
insertIf( contentsLayout, PanelSide::Top, navigation, branding->navigationSide() );
|
||||
contentsLayout->addWidget( m_viewManager->centralWidget() );
|
||||
insertIf( contentsLayout, PanelSide::Bottom, navigation, branding->navigationSide() );
|
||||
insertIf( contentsLayout, PanelSide::Bottom, sideBox, branding->sidebarSide() );
|
||||
|
||||
// .. and then the mainLayout left-to-right
|
||||
insertIf( mainLayout, PanelSide::Left, sideBox, branding->sidebarSide() );
|
||||
insertIf( mainLayout, PanelSide::Left, navigation, branding->navigationSide() );
|
||||
mainLayout->addLayout( contentsLayout );
|
||||
insertIf( mainLayout, PanelSide::Right, navigation, branding->navigationSide() );
|
||||
insertIf( mainLayout, PanelSide::Right, sideBox, branding->sidebarSide() );
|
||||
|
||||
// layout->count() returns number of things in it; above we have put
|
||||
// at **least** the central widget, which comes from the view manager,
|
||||
// both vertically and horizontally -- so if there's a panel along
|
||||
// either axis, the count in that axis will be > 1.
|
||||
m_viewManager->setPanelSides(
|
||||
( contentsLayout->count() > 1 ? Qt::Orientations( Qt::Horizontal ) : Qt::Orientations() )
|
||||
| ( mainLayout->count() > 1 ? Qt::Orientations( Qt::Vertical ) : Qt::Orientations() ) );
|
||||
|
||||
Calamares::unmarginLayout( mainLayout );
|
||||
Calamares::unmarginLayout( contentsLayout );
|
||||
baseWidget->setLayout( mainLayout );
|
||||
setStyleSheet( Calamares::Branding::instance()->stylesheet() );
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresWindow::ensureSize( QSize size )
|
||||
{
|
||||
auto mainGeometry = this->geometry();
|
||||
QSize availableSize = desktopSize( this );
|
||||
|
||||
// We only care about vertical sizes that are big enough
|
||||
int embiggenment = qMax( 0, size.height() - m_viewManager->centralWidget()->size().height() );
|
||||
if ( embiggenment < 6 )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto h = qBound( 0, mainGeometry.height() + embiggenment, availableSize.height() );
|
||||
auto w = this->size().width();
|
||||
|
||||
resize( w, h );
|
||||
}
|
||||
|
||||
void
|
||||
CalamaresWindow::closeEvent( QCloseEvent* event )
|
||||
{
|
||||
if ( m_viewManager )
|
||||
{
|
||||
m_viewManager->quit();
|
||||
// If it didn't actually exit, eat the event to ignore close
|
||||
event->ignore();
|
||||
}
|
||||
else
|
||||
{
|
||||
event->accept();
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
QApplication::quit();
|
||||
#else
|
||||
QApplication::exit( EXIT_SUCCESS );
|
||||
#endif
|
||||
}
|
||||
}
|
||||
50
calamares/src/calamares/CalamaresWindow.h
Normal file
50
calamares/src/calamares/CalamaresWindow.h
Normal file
@@ -0,0 +1,50 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARESWINDOW_H
|
||||
#define CALAMARESWINDOW_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
class DebugWindowManager;
|
||||
class ViewManager;
|
||||
} // namespace Calamares
|
||||
|
||||
/**
|
||||
* @brief The CalamaresWindow class represents the main window of the Calamares UI.
|
||||
*/
|
||||
class CalamaresWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CalamaresWindow( QWidget* parent = nullptr );
|
||||
~CalamaresWindow() override {}
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
* This asks the main window to grow to accomodate @p size pixels, to accomodate
|
||||
* larger-than-expected window contents. The enlargement may be silently
|
||||
* ignored.
|
||||
*/
|
||||
void ensureSize( QSize size );
|
||||
|
||||
protected:
|
||||
virtual void closeEvent( QCloseEvent* e ) override;
|
||||
|
||||
private:
|
||||
Calamares::DebugWindowManager* m_debugManager = nullptr;
|
||||
Calamares::ViewManager* m_viewManager = nullptr;
|
||||
};
|
||||
|
||||
#endif // CALAMARESWINDOW_H
|
||||
257
calamares/src/calamares/DebugWindow.cpp
Normal file
257
calamares/src/calamares/DebugWindow.cpp
Normal file
@@ -0,0 +1,257 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015-2016 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "DebugWindow.h"
|
||||
#include "ui_DebugWindow.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "CalamaresAbout.h"
|
||||
#include "CalamaresVersion.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "Job.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "VariantModel.h"
|
||||
#include "modulesystem/Module.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
#include "utils/Gui.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Paste.h"
|
||||
#include "utils/Retranslator.h"
|
||||
#include "widgets/TranslationFix.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QSplitter>
|
||||
#include <QStringListModel>
|
||||
#include <QTreeView>
|
||||
#include <QWidget>
|
||||
|
||||
#include <signal.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/**
|
||||
* @brief crash makes Calamares crash immediately.
|
||||
*/
|
||||
static void
|
||||
crash()
|
||||
{
|
||||
kill( getpid(), SIGTRAP );
|
||||
}
|
||||
|
||||
/// @brief Print out the widget tree (names) in indented form.
|
||||
static void
|
||||
dumpWidgetTree( QDebug& deb, const QWidget* widget, int depth )
|
||||
{
|
||||
if ( !widget )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
deb << Logger::Continuation;
|
||||
for ( int i = 0; i < depth; ++i )
|
||||
{
|
||||
deb << ' ';
|
||||
}
|
||||
deb << widget->metaObject()->className() << widget->objectName();
|
||||
|
||||
for ( const auto* w : widget->findChildren< QWidget* >( QString(), Qt::FindDirectChildrenOnly ) )
|
||||
{
|
||||
dumpWidgetTree( deb, w, depth + 1 );
|
||||
}
|
||||
}
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
DebugWindow::DebugWindow()
|
||||
: QWidget( nullptr )
|
||||
, m_ui( new Ui::DebugWindow )
|
||||
, m_globals( JobQueue::instance()->globalStorage()->data() )
|
||||
, m_globals_model( std::make_unique< VariantModel >( &m_globals ) )
|
||||
, m_module_model( std::make_unique< VariantModel >( &m_module ) )
|
||||
{
|
||||
GlobalStorage* gs = JobQueue::instance()->globalStorage();
|
||||
|
||||
m_ui->setupUi( this );
|
||||
|
||||
m_ui->globalStorageView->setModel( m_globals_model.get() );
|
||||
m_ui->globalStorageView->expandAll();
|
||||
|
||||
// Do above when the GS changes, too
|
||||
connect( gs,
|
||||
&GlobalStorage::changed,
|
||||
this,
|
||||
[ = ]
|
||||
{
|
||||
m_globals = JobQueue::instance()->globalStorage()->data();
|
||||
m_globals_model->reload();
|
||||
m_ui->globalStorageView->expandAll();
|
||||
} );
|
||||
|
||||
// JobQueue page
|
||||
m_ui->jobQueueText->setReadOnly( true );
|
||||
connect( JobQueue::instance(),
|
||||
&JobQueue::queueChanged,
|
||||
this,
|
||||
[ this ]( const QStringList& jobs ) { m_ui->jobQueueText->setText( jobs.join( '\n' ) ); } );
|
||||
|
||||
// Modules page
|
||||
QStringList modulesKeys;
|
||||
for ( const auto& m : ModuleManager::instance()->loadedInstanceKeys() )
|
||||
{
|
||||
modulesKeys << m.toString();
|
||||
}
|
||||
|
||||
QStringListModel* modulesModel = new QStringListModel( modulesKeys );
|
||||
m_ui->modulesListView->setModel( modulesModel );
|
||||
m_ui->modulesListView->setSelectionMode( QAbstractItemView::SingleSelection );
|
||||
|
||||
m_ui->moduleConfigView->setModel( m_module_model.get() );
|
||||
|
||||
connect( m_ui->modulesListView->selectionModel(),
|
||||
&QItemSelectionModel::selectionChanged,
|
||||
this,
|
||||
[ this ]
|
||||
{
|
||||
QString moduleName = m_ui->modulesListView->currentIndex().data().toString();
|
||||
Module* module
|
||||
= ModuleManager::instance()->moduleInstance( ModuleSystem::InstanceKey::fromString( moduleName ) );
|
||||
if ( module )
|
||||
{
|
||||
m_module = module->configurationMap();
|
||||
m_module_model->reload();
|
||||
m_ui->moduleConfigView->expandAll();
|
||||
m_ui->moduleTypeLabel->setText( module->typeString() );
|
||||
m_ui->moduleInterfaceLabel->setText( module->interfaceString() );
|
||||
}
|
||||
} );
|
||||
|
||||
// Tools page
|
||||
connect( m_ui->crashButton, &QPushButton::clicked, this, [] { ::crash(); } );
|
||||
connect( m_ui->reloadStylesheetButton,
|
||||
&QPushButton::clicked,
|
||||
[]()
|
||||
{
|
||||
for ( auto* w : qApp->topLevelWidgets() )
|
||||
{
|
||||
// Needs to match what's set in CalamaresWindow
|
||||
if ( w->objectName() == QStringLiteral( "mainApp" ) )
|
||||
{
|
||||
w->setStyleSheet( Calamares::Branding::instance()->stylesheet() );
|
||||
}
|
||||
}
|
||||
} );
|
||||
connect( m_ui->widgetTreeButton,
|
||||
&QPushButton::clicked,
|
||||
[]()
|
||||
{
|
||||
for ( auto* w : qApp->topLevelWidgets() )
|
||||
{
|
||||
Logger::CDebug deb;
|
||||
dumpWidgetTree( deb, w, 0 );
|
||||
}
|
||||
} );
|
||||
|
||||
// Send Log button only if it would be useful
|
||||
m_ui->sendLogButton->setVisible( Calamares::Paste::isEnabled() );
|
||||
connect( m_ui->sendLogButton, &QPushButton::clicked, [ this ]() { Calamares::Paste::doLogUploadUI( this ); } );
|
||||
|
||||
CALAMARES_RETRANSLATE( m_ui->retranslateUi( this ); setWindowTitle( tr( "Debug Information", "@title" ) ); );
|
||||
}
|
||||
|
||||
void
|
||||
DebugWindow::closeEvent( QCloseEvent* e )
|
||||
{
|
||||
Q_UNUSED( e )
|
||||
emit closed();
|
||||
}
|
||||
|
||||
DebugWindowManager::DebugWindowManager( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
DebugWindowManager::enabled() const
|
||||
{
|
||||
const auto* s = Settings::instance();
|
||||
return ( Logger::logLevel() >= Logger::LOGVERBOSE ) || ( s ? s->debugMode() : false );
|
||||
}
|
||||
|
||||
void
|
||||
DebugWindowManager::show( bool visible )
|
||||
{
|
||||
if ( !enabled() )
|
||||
{
|
||||
visible = false;
|
||||
}
|
||||
if ( m_visible == visible )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if ( visible )
|
||||
{
|
||||
m_debugWindow = new Calamares::DebugWindow();
|
||||
m_debugWindow->show();
|
||||
connect( m_debugWindow.data(),
|
||||
&Calamares::DebugWindow::closed,
|
||||
this,
|
||||
[ = ]()
|
||||
{
|
||||
m_debugWindow->deleteLater();
|
||||
m_visible = false;
|
||||
emit visibleChanged( false );
|
||||
} );
|
||||
m_visible = true;
|
||||
emit visibleChanged( true );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( m_debugWindow )
|
||||
{
|
||||
m_debugWindow->deleteLater();
|
||||
}
|
||||
m_visible = false;
|
||||
emit visibleChanged( false );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DebugWindowManager::toggle()
|
||||
{
|
||||
show( !m_visible );
|
||||
}
|
||||
|
||||
void
|
||||
DebugWindowManager::about()
|
||||
{
|
||||
QString title = Calamares::Settings::instance()->isSetupMode()
|
||||
? QCoreApplication::translate( "WelcomePage", "About %1 Setup", "@title" )
|
||||
: QCoreApplication::translate( "WelcomePage", "About %1 Installer", "@title" );
|
||||
QMessageBox mb( QMessageBox::Information,
|
||||
title.arg( CALAMARES_APPLICATION_NAME ),
|
||||
Calamares::aboutString().arg( Calamares::Branding::instance()->versionedName() ),
|
||||
QMessageBox::Ok,
|
||||
nullptr );
|
||||
Calamares::fixButtonLabels( &mb );
|
||||
mb.setIconPixmap(
|
||||
Calamares::defaultPixmap( Calamares::Squid,
|
||||
Calamares::Original,
|
||||
QSize( Calamares::defaultFontHeight() * 6, Calamares::defaultFontHeight() * 6 ) ) );
|
||||
QGridLayout* layout = reinterpret_cast< QGridLayout* >( mb.layout() );
|
||||
if ( layout )
|
||||
{
|
||||
layout->setColumnMinimumWidth( 2, Calamares::defaultFontHeight() * 24 );
|
||||
}
|
||||
mb.exec();
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
96
calamares/src/calamares/DebugWindow.h
Normal file
96
calamares/src/calamares/DebugWindow.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_DEBUGWINDOW_H
|
||||
#define CALAMARES_DEBUGWINDOW_H
|
||||
|
||||
#include "VariantModel.h"
|
||||
|
||||
#include <QPointer>
|
||||
#include <QVariant>
|
||||
#include <QWidget>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
// From the .ui file
|
||||
namespace Ui
|
||||
{
|
||||
class DebugWindow;
|
||||
} // namespace Ui
|
||||
|
||||
class DebugWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit DebugWindow();
|
||||
|
||||
signals:
|
||||
void closed();
|
||||
|
||||
protected:
|
||||
void closeEvent( QCloseEvent* e ) override;
|
||||
|
||||
private:
|
||||
Ui::DebugWindow* m_ui;
|
||||
QVariant m_globals;
|
||||
QVariant m_module;
|
||||
std::unique_ptr< VariantModel > m_globals_model;
|
||||
std::unique_ptr< VariantModel > m_module_model;
|
||||
};
|
||||
|
||||
/** @brief Manager for meta-windows (Debug and About windows)
|
||||
*
|
||||
* Only one DebugWindow is expected to be around. This class manages
|
||||
* (exactly one) DebugWindow and can create and destroy it as needed.
|
||||
* It is available to the Calamares panels as object `DebugWindow`.
|
||||
*
|
||||
* The about() method shows a modal pop-up about Calamares.
|
||||
*/
|
||||
class DebugWindowManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/// @brief Proxy to Settings::debugMode() default @c false
|
||||
Q_PROPERTY( bool enabled READ enabled CONSTANT FINAL )
|
||||
|
||||
/** @brief Is the debug window visible?
|
||||
*
|
||||
* Writing @c true to this **may** make the debug window visible to
|
||||
* the user; only if debugMode() is on.
|
||||
*/
|
||||
Q_PROPERTY( bool visible READ visible WRITE show NOTIFY visibleChanged )
|
||||
|
||||
public:
|
||||
DebugWindowManager( QObject* parent = nullptr );
|
||||
virtual ~DebugWindowManager() override = default;
|
||||
|
||||
public Q_SLOTS:
|
||||
bool enabled() const;
|
||||
bool visible() const { return m_visible; }
|
||||
void show( bool visible );
|
||||
void toggle();
|
||||
|
||||
void about();
|
||||
|
||||
signals:
|
||||
void visibleChanged( bool visible );
|
||||
|
||||
private:
|
||||
QPointer< DebugWindow > m_debugWindow;
|
||||
bool m_visible = false;
|
||||
};
|
||||
|
||||
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
157
calamares/src/calamares/DebugWindow.ui
Normal file
157
calamares/src/calamares/DebugWindow.ui
Normal file
@@ -0,0 +1,157 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<author>
|
||||
SPDX-FileCopyrightText: 2015 Teo Mrnjavac <teo@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
</author>
|
||||
<class>Calamares::DebugWindow</class>
|
||||
<widget class="QWidget" name="DebugWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>962</width>
|
||||
<height>651</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string notr="true">Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="globalStorageTab">
|
||||
<attribute name="title">
|
||||
<string>GlobalStorage</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QTreeView" name="globalStorageView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="jobQueueTab">
|
||||
<attribute name="title">
|
||||
<string>JobQueue</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||
<item>
|
||||
<widget class="QTextEdit" name="jobQueueText"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="modulesTab">
|
||||
<attribute name="title">
|
||||
<string>Modules</string>
|
||||
</attribute>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QListView" name="modulesListView"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="modulesVerticalLayout">
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="typeLabel">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="moduleTypeLabel">
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="interfaceLabel">
|
||||
<property name="text">
|
||||
<string>Interface:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLabel" name="moduleInterfaceLabel">
|
||||
<property name="text">
|
||||
<string>none</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeView" name="moduleConfigView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QPushButton" name="crashButton">
|
||||
<property name="toolTip">
|
||||
<string>Crashes Calamares, so that Dr. Konqi can look at it.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string notr="true">Crash now</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="data-error"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="reloadStylesheetButton">
|
||||
<property name="toolTip">
|
||||
<string>Reloads the stylesheet from the branding directory.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Reload Stylesheet</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="preferences-web-browser-stylesheets"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="widgetTreeButton">
|
||||
<property name="toolTip">
|
||||
<string>Displays the tree of widget names in the log (for stylesheet debugging).</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Widget Tree</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="view-list-tree"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="sendLogButton">
|
||||
<property name="toolTip">
|
||||
<string>Uploads the session log to the configured pastebin.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Send Session Log</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="upload-media"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
285
calamares/src/calamares/VariantModel.cpp
Normal file
285
calamares/src/calamares/VariantModel.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "VariantModel.h"
|
||||
|
||||
#include "compat/Variant.h"
|
||||
|
||||
static bool
|
||||
isMapLike( const QVariant& item )
|
||||
{
|
||||
return item.canConvert< QVariantMap >();
|
||||
}
|
||||
|
||||
static bool
|
||||
isListLike( const QVariant& item )
|
||||
{
|
||||
return item.canConvert< QVariantList >() && !( Calamares::typeOf( item ) == Calamares::StringVariantType );
|
||||
}
|
||||
|
||||
static void
|
||||
overallLength( const QVariant& item, quintptr& c, quintptr parent, VariantModel::IndexVector* skiplist )
|
||||
{
|
||||
if ( skiplist )
|
||||
{
|
||||
skiplist->append( parent );
|
||||
}
|
||||
|
||||
parent = c++;
|
||||
if ( isMapLike( item ) )
|
||||
{
|
||||
for ( const auto& subitem : item.toMap() )
|
||||
{
|
||||
overallLength( subitem, c, parent, skiplist );
|
||||
}
|
||||
}
|
||||
else if ( isListLike( item ) )
|
||||
{
|
||||
for ( const auto& subitem : item.toList() )
|
||||
{
|
||||
overallLength( subitem, c, parent, skiplist );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static quintptr
|
||||
findNth( const VariantModel::IndexVector& skiplist, quintptr value, int n )
|
||||
{
|
||||
constexpr const quintptr invalid_index = static_cast< quintptr >( -1 );
|
||||
|
||||
if ( n < 0 )
|
||||
{
|
||||
return invalid_index;
|
||||
}
|
||||
|
||||
int index = static_cast< int >( value );
|
||||
while ( ( n >= 0 ) && ( index < skiplist.count() ) )
|
||||
{
|
||||
if ( skiplist[ index ] == value )
|
||||
{
|
||||
if ( --n < 0 )
|
||||
{
|
||||
// It's bigger than 0
|
||||
return static_cast< quintptr >( index );
|
||||
}
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return invalid_index;
|
||||
}
|
||||
|
||||
|
||||
VariantModel::VariantModel( const QVariant* p )
|
||||
: m_p( p )
|
||||
{
|
||||
reload();
|
||||
}
|
||||
|
||||
VariantModel::~VariantModel() {}
|
||||
|
||||
void
|
||||
VariantModel::reload()
|
||||
{
|
||||
constexpr const quintptr invalid_index = static_cast< quintptr >( -1 );
|
||||
|
||||
quintptr x = 0;
|
||||
m_rows.clear(); // Start over
|
||||
if ( m_rows.capacity() < 64 )
|
||||
{
|
||||
m_rows.reserve( 64 ); // Start reasonably-sized
|
||||
}
|
||||
overallLength( *m_p, x, invalid_index, &m_rows );
|
||||
}
|
||||
|
||||
int
|
||||
VariantModel::columnCount( const QModelIndex& ) const
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
int
|
||||
VariantModel::rowCount( const QModelIndex& index ) const
|
||||
{
|
||||
quintptr p = index.isValid() ? index.internalId() : 0;
|
||||
return m_rows.count( p );
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
VariantModel::index( int row, int column, const QModelIndex& parent ) const
|
||||
{
|
||||
quintptr p = 0;
|
||||
|
||||
if ( parent.isValid() )
|
||||
{
|
||||
if ( inRange( parent ) )
|
||||
{
|
||||
p = parent.internalId();
|
||||
}
|
||||
}
|
||||
|
||||
return createIndex( row, column, findNth( m_rows, p, row ) );
|
||||
}
|
||||
|
||||
static inline quintptr
|
||||
deref( const VariantModel::IndexVector& v, quintptr i )
|
||||
{
|
||||
return v[ static_cast< int >( i ) ];
|
||||
}
|
||||
|
||||
QModelIndex
|
||||
VariantModel::parent( const QModelIndex& index ) const
|
||||
{
|
||||
if ( !index.isValid() || !inRange( index ) )
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
quintptr p = deref( m_rows, index.internalId() );
|
||||
if ( p == 0 )
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
if ( !inRange( p ) )
|
||||
{
|
||||
return QModelIndex();
|
||||
}
|
||||
quintptr p_pid = deref( m_rows, p );
|
||||
int row = 0;
|
||||
for ( int i = static_cast< int >( p_pid ); i < static_cast< int >( p ); ++i )
|
||||
{
|
||||
if ( m_rows[ i ] == p_pid )
|
||||
{
|
||||
row++;
|
||||
}
|
||||
}
|
||||
|
||||
return createIndex( row, index.column(), p );
|
||||
}
|
||||
|
||||
QVariant
|
||||
VariantModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
if ( role != Qt::DisplayRole )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( ( index.column() < 0 ) || ( index.column() > 1 ) )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( !inRange( index ) )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const QVariant thing = underlying( parent( index ) );
|
||||
|
||||
if ( !thing.isValid() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( isMapLike( thing ) )
|
||||
{
|
||||
QVariantMap the_map = thing.toMap();
|
||||
const auto key = the_map.keys().at( index.row() );
|
||||
if ( index.column() == 0 )
|
||||
{
|
||||
return key;
|
||||
}
|
||||
else
|
||||
{
|
||||
return the_map[ key ];
|
||||
}
|
||||
}
|
||||
else if ( isListLike( thing ) )
|
||||
{
|
||||
if ( index.column() == 0 )
|
||||
{
|
||||
return index.row();
|
||||
}
|
||||
else
|
||||
{
|
||||
QVariantList the_list = thing.toList();
|
||||
return the_list.at( index.row() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( index.column() == 0 )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return thing;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
VariantModel::headerData( int section, Qt::Orientation orientation, int role ) const
|
||||
{
|
||||
if ( role != Qt::DisplayRole )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( orientation == Qt::Horizontal )
|
||||
{
|
||||
if ( section == 0 )
|
||||
{
|
||||
return tr( "Key", "Column header for key/value" );
|
||||
}
|
||||
else if ( section == 1 )
|
||||
{
|
||||
return tr( "Value", "Column header for key/value" );
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
const QVariant
|
||||
VariantModel::underlying( const QModelIndex& index ) const
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
return *m_p;
|
||||
}
|
||||
|
||||
const auto& thing = underlying( parent( index ) );
|
||||
if ( isMapLike( thing ) )
|
||||
{
|
||||
const auto& the_map = thing.toMap();
|
||||
return the_map[ the_map.keys()[ index.row() ] ];
|
||||
}
|
||||
else if ( isListLike( thing ) )
|
||||
{
|
||||
return thing.toList()[ index.row() ];
|
||||
}
|
||||
else
|
||||
{
|
||||
return thing;
|
||||
}
|
||||
}
|
||||
104
calamares/src/calamares/VariantModel.h
Normal file
104
calamares/src/calamares/VariantModel.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef VARIANTMODEL_H
|
||||
#define VARIANTMODEL_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QVariantMap>
|
||||
#include <QVector>
|
||||
|
||||
/** @brief A model that operates directly on a QVariant
|
||||
*
|
||||
* A VariantModel operates directly on an underlying
|
||||
* QVariant, treating QVariantMap and QVariantList as
|
||||
* nodes with multiple children. In general, putting
|
||||
* a QVariantMap into a QVariant and passing that into
|
||||
* the model will get you a tree-like model of the
|
||||
* VariantMap's data structure.
|
||||
*
|
||||
* Take care of object lifetimes and that the underlying
|
||||
* QVariant does not change during use. If the QVariant
|
||||
* **does** change, call reload() to re-build the internal
|
||||
* representation of the tree.
|
||||
*/
|
||||
class VariantModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/** @brief Auxiliary data
|
||||
*
|
||||
* The nodes of the tree are enumerated into a vector
|
||||
* (of length equal to the number of nodes in the tree + 1)
|
||||
* which are used to do index and parent calculations.
|
||||
*/
|
||||
using IndexVector = QVector< quintptr >;
|
||||
|
||||
/** @brief Constructor
|
||||
*
|
||||
* The QVariant's lifetime is **not** affected by the model,
|
||||
* so take care that the QVariant lives at least as long as
|
||||
* the model). Also, don't change the QVariant underneath the model.
|
||||
*/
|
||||
VariantModel( const QVariant* p );
|
||||
|
||||
~VariantModel() override;
|
||||
|
||||
/** @brief Re-build the internal tree
|
||||
*
|
||||
* Call this when the underlying variant is changed, which
|
||||
* might impact how the tree is laid out.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
int columnCount( const QModelIndex& index ) const override;
|
||||
int rowCount( const QModelIndex& index ) const override;
|
||||
|
||||
QModelIndex index( int row, int column, const QModelIndex& parent ) const override;
|
||||
QModelIndex parent( const QModelIndex& index ) const override;
|
||||
QVariant data( const QModelIndex& index, int role ) const override;
|
||||
QVariant headerData( int section, Qt::Orientation orientation, int role ) const override;
|
||||
|
||||
private:
|
||||
const QVariant* const m_p;
|
||||
|
||||
/** @brief Tree representation of the variant.
|
||||
*
|
||||
* At index 0 in the vector , we store -1 to indicate the root.
|
||||
*
|
||||
* Then we enumerate all the elements in the tree (by traversing
|
||||
* the variant and using QVariantMap and QVariantList as having
|
||||
* children, and everything else being a leaf node) and at the index
|
||||
* for a child, store the index of its parent. This means that direct
|
||||
* children of the root store a 0 in their indexes, children of the first
|
||||
* child of the root store a 1, and we can "pointer chase" from an index
|
||||
* through parents back to index 0.
|
||||
*
|
||||
* Because of this structure, the value stored at index i must be
|
||||
* less than i (except for index 0, which is special). This makes it
|
||||
* slightly easier to search for a given value *p*, because we can start
|
||||
* at index *p* (or even *p+1*).
|
||||
*
|
||||
* Given an index *i* into the vector corresponding to a child, we know the
|
||||
* parent, but can also count which row this child should have, by counting
|
||||
* *other* indexes before *i* with the same parent (and by the ordering
|
||||
* of values, we can start counting at index *parent-index*).
|
||||
*
|
||||
*/
|
||||
IndexVector m_rows;
|
||||
|
||||
/// @brief Implementation of walking an index through the variant-tree
|
||||
const QVariant underlying( const QModelIndex& index ) const;
|
||||
|
||||
/// @brief Helpers for range-checking
|
||||
inline bool inRange( quintptr p ) const { return p < static_cast< quintptr >( m_rows.count() ); }
|
||||
inline bool inRange( const QModelIndex& index ) const { return inRange( index.internalId() ); }
|
||||
};
|
||||
|
||||
#endif
|
||||
83
calamares/src/calamares/calamares-navigation.qml
Normal file
83
calamares/src/calamares/calamares-navigation.qml
Normal file
@@ -0,0 +1,83 @@
|
||||
/* Sample of QML navigation.
|
||||
|
||||
SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
The navigation panel is generally "horizontal" in layout, with
|
||||
buttons for next and previous; this particular one copies
|
||||
the layout and size of the widgets panel.
|
||||
*/
|
||||
import io.calamares.ui 1.0
|
||||
import io.calamares.core 1.0
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Rectangle {
|
||||
id: navigationBar;
|
||||
color: Branding.styleString( Branding.SidebarBackground );
|
||||
height: 48;
|
||||
|
||||
RowLayout {
|
||||
id: buttonBar
|
||||
anchors.fill: parent;
|
||||
|
||||
Item
|
||||
{
|
||||
Layout.fillWidth: true;
|
||||
}
|
||||
|
||||
Button
|
||||
{
|
||||
text: ViewManager.backLabel;
|
||||
icon.name: ViewManager.backIcon;
|
||||
|
||||
enabled: ViewManager.backEnabled;
|
||||
visible: ViewManager.backAndNextVisible;
|
||||
onClicked: { ViewManager.back(); }
|
||||
}
|
||||
Button
|
||||
{
|
||||
text: ViewManager.nextLabel;
|
||||
icon.name: ViewManager.nextIcon;
|
||||
|
||||
enabled: ViewManager.nextEnabled;
|
||||
visible: ViewManager.backAndNextVisible;
|
||||
onClicked: { ViewManager.next(); }
|
||||
// This margin goes in the "next" button, because the "quit"
|
||||
// button can vanish and we want to keep the margin to
|
||||
// the next-thing-in-the-navigation-panel around.
|
||||
Layout.rightMargin: 3 * buttonBar.spacing;
|
||||
}
|
||||
Button
|
||||
{
|
||||
Layout.rightMargin: 2 * buttonBar.spacing
|
||||
text: ViewManager.quitLabel;
|
||||
icon.name: ViewManager.quitIcon;
|
||||
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.timeout: 5000
|
||||
ToolTip.delay: 1000
|
||||
ToolTip.text: ViewManager.quitTooltip;
|
||||
|
||||
/*
|
||||
* The ViewManager has settings -- user-controlled via the
|
||||
* branding component, and party based on program state --
|
||||
* whether the quit button should be enabled and visible.
|
||||
*
|
||||
* QML navigation *should* follow this pattern, but can also
|
||||
* add other qualifications. For instance, you may have a
|
||||
* "finished" module that handles quit in its own way, and
|
||||
* want to hide the quit button then. The ViewManager has a
|
||||
* current step and a total count, so compare them:
|
||||
*
|
||||
* visible: ViewManager.quitVisible && ( ViewManager.currentStepIndex < ViewManager.rowCount()-1);
|
||||
*/
|
||||
enabled: ViewManager.quitEnabled;
|
||||
visible: ViewManager.quitVisible;
|
||||
onClicked: { ViewManager.quit(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
125
calamares/src/calamares/calamares-sidebar.qml
Normal file
125
calamares/src/calamares/calamares-sidebar.qml
Normal file
@@ -0,0 +1,125 @@
|
||||
/* Sample of QML progress tree.
|
||||
|
||||
SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
SPDX-FileCopyrightText: 2021 Anke Boersma <demm@kaosx.us>
|
||||
SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
|
||||
The progress tree (actually a list) is generally "vertical" in layout,
|
||||
with the steps going "down", but it could also be a more compact
|
||||
horizontal layout with suitable branding settings.
|
||||
|
||||
This example emulates the layout and size of the widgets progress tree.
|
||||
*/
|
||||
import io.calamares.ui 1.0
|
||||
import io.calamares.core 1.0
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Rectangle {
|
||||
id: sideBar;
|
||||
color: Branding.styleString( Branding.SidebarBackground );
|
||||
anchors.fill: parent;
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent;
|
||||
spacing: 0;
|
||||
|
||||
Image {
|
||||
Layout.topMargin: 12;
|
||||
Layout.bottomMargin: 12;
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
||||
id: logo;
|
||||
width: 80;
|
||||
height: width; // square
|
||||
source: "file:/" + Branding.imagePath(Branding.ProductLogo);
|
||||
sourceSize.width: width;
|
||||
sourceSize.height: height;
|
||||
}
|
||||
|
||||
Repeater {
|
||||
model: ViewManager
|
||||
Rectangle {
|
||||
Layout.leftMargin: 6;
|
||||
Layout.rightMargin: 6;
|
||||
Layout.fillWidth: true;
|
||||
height: 35;
|
||||
radius: 6;
|
||||
color: Branding.styleString( index == ViewManager.currentStepIndex ? Branding.SidebarBackgroundCurrent : Branding.SidebarBackground );
|
||||
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
color: Branding.styleString( index == ViewManager.currentStepIndex ? Branding.SidebarTextCurrent : Branding.SidebarText );
|
||||
text: display;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: metaArea
|
||||
Layout.fillWidth: true;
|
||||
height: 35
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
||||
color: Branding.styleString( Branding.SidebarBackground );
|
||||
visible: true;
|
||||
|
||||
Rectangle {
|
||||
id: aboutArea
|
||||
height: 35
|
||||
width: parent.width / 2;
|
||||
anchors.left: parent.left
|
||||
color: Branding.styleString( Branding.SidebarBackgroundCurrent );
|
||||
visible: true;
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaAbout
|
||||
anchors.fill: parent;
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
x: parent.x + 4;
|
||||
text: qsTr("About")
|
||||
color: Branding.styleString( Branding.SidebarTextCurrent );
|
||||
font.pointSize : 9
|
||||
}
|
||||
|
||||
onClicked: debug.about()
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: debugArea
|
||||
height: 35
|
||||
width: parent.width / 2;
|
||||
anchors.right: parent.right
|
||||
color: Branding.styleString( Branding.SidebarBackgroundCurrent );
|
||||
visible: debug.enabled
|
||||
|
||||
MouseArea {
|
||||
id: mouseAreaDebug
|
||||
anchors.fill: parent;
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
hoverEnabled: true
|
||||
Text {
|
||||
anchors.verticalCenter: parent.verticalCenter;
|
||||
anchors.horizontalCenter: parent.horizontalCenter;
|
||||
x: parent.x + 4;
|
||||
text: qsTr("Debug")
|
||||
color: Branding.styleString( Branding.SidebarTextCurrent );
|
||||
font.pointSize : 9
|
||||
}
|
||||
|
||||
onClicked: debug.toggle()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
calamares/src/calamares/calamares.qrc
Normal file
10
calamares/src/calamares/calamares.qrc
Normal file
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE RCC>
|
||||
<!-- SPDX-FileCopyrightText: no
|
||||
SPDX-License-Identifier: CC0-1.0
|
||||
-->
|
||||
<RCC version="1.0">
|
||||
<qresource>
|
||||
<file alias="calamares-sidebar.qml">calamares-sidebar.qml</file>
|
||||
<file alias="calamares-navigation.qml">calamares-navigation.qml</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
154
calamares/src/calamares/main.cpp
Normal file
154
calamares/src/calamares/main.cpp
Normal file
@@ -0,0 +1,154 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CalamaresApplication.h"
|
||||
|
||||
#include "Settings.h"
|
||||
#include "utils/Dirs.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Retranslator.h"
|
||||
|
||||
// From 3rdparty/
|
||||
#include "kdsingleapplication.h"
|
||||
|
||||
#include <KAboutData>
|
||||
#ifdef BUILD_CRASH_REPORTING
|
||||
#include <KCrash>
|
||||
#endif
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
|
||||
#include <memory>
|
||||
|
||||
/** @brief Gets debug-level from -D command-line-option
|
||||
*
|
||||
* If unset, use LOGERROR (corresponding to -D1), although
|
||||
* effectively -D2 is the lowest level you can set for
|
||||
* logging-to-the-console, and everything always gets
|
||||
* logged to the session file).
|
||||
*/
|
||||
static unsigned int
|
||||
debug_level( QCommandLineParser& parser, QCommandLineOption& levelOption )
|
||||
{
|
||||
if ( !parser.isSet( levelOption ) )
|
||||
{
|
||||
return Logger::LOGERROR;
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
int l = parser.value( levelOption ).toInt( &ok );
|
||||
if ( !ok || ( l < 0 ) )
|
||||
{
|
||||
return Logger::LOGVERBOSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast< unsigned int >( l ); // l >= 0
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Handles the command-line arguments
|
||||
*
|
||||
* Sets up internals for Calamares based on command-line arguments like `-D`,
|
||||
* `-d`, etc. Returns @c true if this is a *debug* run, i.e. if the `-d`
|
||||
* command-line flag is given, @c false otherwise.
|
||||
*/
|
||||
static bool
|
||||
handle_args( CalamaresApplication& a )
|
||||
{
|
||||
QCommandLineOption debugOption( QStringList { "d", "debug" },
|
||||
"Also look in current directory for configuration. Implies -D8." );
|
||||
QCommandLineOption debugLevelOption(
|
||||
QStringLiteral( "D" ), "Verbose output for debugging purposes (0-8).", "level" );
|
||||
QCommandLineOption debugTxOption( QStringList { "T", "debug-translation" },
|
||||
"Also look in the current directory for translation." );
|
||||
|
||||
QCommandLineOption configOption(
|
||||
QStringList { "c", "config" }, "Configuration directory to use, for testing purposes.", "config" );
|
||||
QCommandLineOption xdgOption( QStringList { "X", "xdg-config" }, "Use XDG_{CONFIG,DATA}_DIRS as well." );
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription( "Distribution-independent installer framework" );
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
parser.addOption( debugOption );
|
||||
parser.addOption( debugLevelOption );
|
||||
parser.addOption( configOption );
|
||||
parser.addOption( xdgOption );
|
||||
parser.addOption( debugTxOption );
|
||||
|
||||
parser.process( a );
|
||||
|
||||
Logger::setupLogLevel( parser.isSet( debugOption ) ? Logger::LOGVERBOSE : debug_level( parser, debugLevelOption ) );
|
||||
if ( parser.isSet( configOption ) )
|
||||
{
|
||||
Calamares::setAppDataDir( QDir( parser.value( configOption ) ) );
|
||||
}
|
||||
if ( parser.isSet( xdgOption ) )
|
||||
{
|
||||
Calamares::setXdgDirs();
|
||||
}
|
||||
Calamares::setAllowLocalTranslation( parser.isSet( debugOption ) || parser.isSet( debugTxOption ) );
|
||||
|
||||
return parser.isSet( debugOption );
|
||||
}
|
||||
|
||||
int
|
||||
main( int argc, char* argv[] )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
// Not needed in Qt6
|
||||
QApplication::setAttribute( Qt::AA_EnableHighDpiScaling );
|
||||
#endif
|
||||
CalamaresApplication a( argc, argv );
|
||||
|
||||
KAboutData aboutData( "calamares",
|
||||
"Calamares",
|
||||
a.applicationVersion(),
|
||||
"The universal system installer",
|
||||
KAboutLicense::GPL_V3,
|
||||
QString(),
|
||||
QString(),
|
||||
"https://calamares.io",
|
||||
"https://codeberg.org/Calamares/calamares/issues" );
|
||||
KAboutData::setApplicationData( aboutData );
|
||||
a.setApplicationDisplayName( QString() ); // To avoid putting an extra "Calamares/" into the log-file
|
||||
|
||||
#ifdef BUILD_CRASH_REPORTING
|
||||
KCrash::initialize();
|
||||
// KCrash::setCrashHandler();
|
||||
KCrash::setDrKonqiEnabled( true );
|
||||
KCrash::setFlags( KCrash::SaferDialog | KCrash::AlwaysDirectly );
|
||||
#endif
|
||||
|
||||
std::unique_ptr< KDSingleApplication > possiblyUnique;
|
||||
const bool is_debug = handle_args( a );
|
||||
if ( !is_debug )
|
||||
{
|
||||
possiblyUnique = std::make_unique< KDSingleApplication >();
|
||||
if ( !possiblyUnique->isPrimaryInstance() )
|
||||
{
|
||||
qCritical() << "Calamares is already running.";
|
||||
return 87; // EUSERS on Linux
|
||||
}
|
||||
}
|
||||
|
||||
Calamares::Settings::init( is_debug );
|
||||
if ( !Calamares::Settings::instance() || !Calamares::Settings::instance()->isValid() )
|
||||
{
|
||||
qCritical() << "Calamares has invalid settings, shutting down.";
|
||||
return 78; // EX_CONFIG on FreeBSD
|
||||
}
|
||||
a.init();
|
||||
return a.exec();
|
||||
}
|
||||
119
calamares/src/calamares/progresstree/ProgressTreeDelegate.cpp
Normal file
119
calamares/src/calamares/progresstree/ProgressTreeDelegate.cpp
Normal file
@@ -0,0 +1,119 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ProgressTreeDelegate.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "CalamaresApplication.h"
|
||||
#include "CalamaresWindow.h"
|
||||
#include "ViewManager.h"
|
||||
#include "utils/Gui.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
static constexpr int const item_margin = 8;
|
||||
static inline int
|
||||
item_fontsize()
|
||||
{
|
||||
return Calamares::defaultFontSize() + 4;
|
||||
}
|
||||
|
||||
static void
|
||||
paintViewStep( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index )
|
||||
{
|
||||
QRect textRect = option.rect.adjusted( item_margin, item_margin, -item_margin, -item_margin );
|
||||
QFont font = qApp->font();
|
||||
font.setPointSize( item_fontsize() );
|
||||
font.setBold( false );
|
||||
painter->setFont( font );
|
||||
|
||||
if ( index.row() == index.data( Calamares::ViewManager::ProgressTreeItemCurrentIndex ).toInt() )
|
||||
{
|
||||
painter->setPen( Calamares::Branding::instance()->styleString( Calamares::Branding::SidebarTextCurrent ) );
|
||||
QString textHighlight
|
||||
= Calamares::Branding::instance()->styleString( Calamares::Branding::SidebarBackgroundCurrent );
|
||||
if ( textHighlight.isEmpty() )
|
||||
{
|
||||
painter->setBrush( CalamaresApplication::instance()->mainWindow()->palette().window() );
|
||||
}
|
||||
else
|
||||
{
|
||||
painter->setBrush( QColor( textHighlight ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the text at least once. If it doesn't fit, then shrink the font
|
||||
// being used by 1 pt on each iteration, up to a maximum of maximumShrink
|
||||
// times. On each loop, we'll have to blank out the rectangle again, so this
|
||||
// is an expensive (in terms of drawing operations) thing to do.
|
||||
//
|
||||
// (The loop uses <= because the counter is incremented at the start).
|
||||
static constexpr int const maximumShrink = 4;
|
||||
int shrinkSteps = 0;
|
||||
do
|
||||
{
|
||||
painter->fillRect( option.rect, painter->brush().color() );
|
||||
shrinkSteps++;
|
||||
|
||||
QRectF boundingBox;
|
||||
painter->drawText(
|
||||
textRect, Qt::AlignHCenter | Qt::AlignVCenter | Qt::TextSingleLine, index.data().toString(), &boundingBox );
|
||||
|
||||
// The extra check here is to avoid the changing-font-size if we're not going to use
|
||||
// it in the next iteration of the loop anyway.
|
||||
if ( ( shrinkSteps <= maximumShrink ) && ( boundingBox.width() > textRect.width() ) )
|
||||
{
|
||||
font.setPointSize( item_fontsize() - shrinkSteps );
|
||||
painter->setFont( font );
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // It fits
|
||||
}
|
||||
} while ( shrinkSteps <= maximumShrink );
|
||||
}
|
||||
|
||||
QSize
|
||||
ProgressTreeDelegate::sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
||||
{
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
return option.rect.size();
|
||||
}
|
||||
|
||||
QFont font = qApp->font();
|
||||
|
||||
font.setPointSize( item_fontsize() );
|
||||
QFontMetrics fm( font );
|
||||
int height = fm.height();
|
||||
|
||||
height += 2 * item_margin;
|
||||
|
||||
return QSize( option.rect.width(), height );
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTreeDelegate::paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const
|
||||
{
|
||||
QStyleOptionViewItem opt = option;
|
||||
|
||||
painter->save();
|
||||
|
||||
initStyleOption( &opt, index );
|
||||
opt.text.clear();
|
||||
|
||||
painter->setBrush(
|
||||
QColor( Calamares::Branding::instance()->styleString( Calamares::Branding::SidebarBackground ) ) );
|
||||
painter->setPen( QColor( Calamares::Branding::instance()->styleString( Calamares::Branding::SidebarText ) ) );
|
||||
|
||||
paintViewStep( painter, opt, index );
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
31
calamares/src/calamares/progresstree/ProgressTreeDelegate.h
Normal file
31
calamares/src/calamares/progresstree/ProgressTreeDelegate.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROGRESSTREEDELEGATE_H
|
||||
#define PROGRESSTREEDELEGATE_H
|
||||
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
/**
|
||||
* @brief The ProgressTreeDelegate class customizes the look and feel of the
|
||||
* ProgressTreeView elements.
|
||||
* @see ProgressTreeView
|
||||
*/
|
||||
class ProgressTreeDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
using QStyledItemDelegate::QStyledItemDelegate;
|
||||
|
||||
protected:
|
||||
QSize sizeHint( const QStyleOptionViewItem& option, const QModelIndex& index ) const override;
|
||||
void paint( QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index ) const override;
|
||||
};
|
||||
|
||||
#endif // PROGRESSTREEDELEGATE_H
|
||||
61
calamares/src/calamares/progresstree/ProgressTreeView.cpp
Normal file
61
calamares/src/calamares/progresstree/ProgressTreeView.cpp
Normal file
@@ -0,0 +1,61 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ProgressTreeView.h"
|
||||
|
||||
#include "ProgressTreeDelegate.h"
|
||||
|
||||
#include "Branding.h"
|
||||
#include "ViewManager.h"
|
||||
|
||||
ProgressTreeView::ProgressTreeView( QWidget* parent )
|
||||
: QListView( parent )
|
||||
{
|
||||
this->setObjectName( "sidebarMenuApp" );
|
||||
setFrameShape( QFrame::NoFrame );
|
||||
setContentsMargins( 0, 0, 0, 0 );
|
||||
|
||||
setSelectionMode( QAbstractItemView::NoSelection );
|
||||
setDragDropMode( QAbstractItemView::NoDragDrop );
|
||||
setAcceptDrops( false );
|
||||
|
||||
setItemDelegate( new ProgressTreeDelegate( this ) );
|
||||
|
||||
QPalette plt = palette();
|
||||
plt.setColor( QPalette::Base,
|
||||
Calamares::Branding::instance()->styleString( Calamares::Branding::SidebarBackground ) );
|
||||
setPalette( plt );
|
||||
}
|
||||
|
||||
|
||||
ProgressTreeView::~ProgressTreeView() {}
|
||||
|
||||
|
||||
void
|
||||
ProgressTreeView::setModel( QAbstractItemModel* model )
|
||||
{
|
||||
if ( ProgressTreeView::model() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QListView::setModel( model );
|
||||
|
||||
connect( Calamares::ViewManager::instance(),
|
||||
&Calamares::ViewManager::currentStepChanged,
|
||||
this,
|
||||
&ProgressTreeView::update,
|
||||
Qt::UniqueConnection );
|
||||
}
|
||||
|
||||
void
|
||||
ProgressTreeView::update()
|
||||
{
|
||||
viewport()->update();
|
||||
}
|
||||
39
calamares/src/calamares/progresstree/ProgressTreeView.h
Normal file
39
calamares/src/calamares/progresstree/ProgressTreeView.h
Normal file
@@ -0,0 +1,39 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROGRESSTREEVIEW_H
|
||||
#define PROGRESSTREEVIEW_H
|
||||
|
||||
#include <QListView>
|
||||
|
||||
/**
|
||||
* @brief Displays progress through the list of (visible) steps
|
||||
*
|
||||
* The ProgressTreeView class is a modified QListView which displays the
|
||||
* available view steps and the user's progress through them.
|
||||
* Since Calamares doesn't support "sub steps", it isn't really a tree.
|
||||
*/
|
||||
class ProgressTreeView : public QListView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProgressTreeView( QWidget* parent = nullptr );
|
||||
~ProgressTreeView() override;
|
||||
|
||||
/**
|
||||
* @brief setModel assigns a model to this view.
|
||||
*/
|
||||
void setModel( QAbstractItemModel* model ) override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void update();
|
||||
};
|
||||
|
||||
#endif // PROGRESSTREEVIEW_H
|
||||
109
calamares/src/calamares/test_conf.cpp
Normal file
109
calamares/src/calamares/test_conf.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a test-application that just checks the YAML config-file
|
||||
* shipped with each module for correctness -- well, for parseability.
|
||||
*/
|
||||
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QFile>
|
||||
|
||||
using std::cerr;
|
||||
|
||||
static const char usage[] = "Usage: test_conf [-v] [-b] <file> ...\n";
|
||||
|
||||
int
|
||||
main( int argc, char** argv )
|
||||
{
|
||||
bool verbose = false;
|
||||
bool bytes = false;
|
||||
|
||||
int opt;
|
||||
while ( ( opt = getopt( argc, argv, "vb" ) ) != -1 )
|
||||
{
|
||||
switch ( opt )
|
||||
{
|
||||
case 'v':
|
||||
verbose = true;
|
||||
break;
|
||||
case 'b':
|
||||
bytes = true;
|
||||
break;
|
||||
default: /* '?' */
|
||||
cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if ( optind >= argc )
|
||||
{
|
||||
cerr << usage;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char* filename = argv[ optind ];
|
||||
try
|
||||
{
|
||||
YAML::Node doc;
|
||||
if ( bytes )
|
||||
{
|
||||
QFile f( filename );
|
||||
if ( f.open( QFile::ReadOnly | QFile::Text ) )
|
||||
{
|
||||
doc = YAML::Load( f.readAll().constData() );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
doc = YAML::LoadFile( filename );
|
||||
}
|
||||
|
||||
if ( doc.IsNull() )
|
||||
{
|
||||
// Special case: empty config files are valid,
|
||||
// but aren't a map. For the example configs,
|
||||
// this is still an error.
|
||||
cerr << "WARNING:" << filename << '\n';
|
||||
cerr << "WARNING: empty YAML\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( !doc.IsMap() )
|
||||
{
|
||||
cerr << "WARNING:" << filename << '\n';
|
||||
cerr << "WARNING: not-a-YAML-map (type=" << doc.Type() << ")\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( verbose )
|
||||
{
|
||||
cerr << "Keys:\n";
|
||||
for ( auto i = doc.begin(); i != doc.end(); ++i )
|
||||
{
|
||||
cerr << i->first.as< std::string >() << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( YAML::Exception& e )
|
||||
{
|
||||
cerr << "WARNING:" << filename << '\n';
|
||||
cerr << "WARNING: YAML parser error " << e.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
573
calamares/src/calamares/testmain.cpp
Normal file
573
calamares/src/calamares/testmain.cpp
Normal file
@@ -0,0 +1,573 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* This executable loads and runs a Calamares Python module
|
||||
* within a C++ application, in order to test the different
|
||||
* bindings.
|
||||
*/
|
||||
|
||||
#include "Branding.h"
|
||||
#include "CppJob.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "Job.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "ViewManager.h"
|
||||
#include "modulesystem/Module.h"
|
||||
#include "modulesystem/ModuleManager.h"
|
||||
#include "modulesystem/ViewModule.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Retranslator.h"
|
||||
#include "utils/System.h"
|
||||
#include "utils/Yaml.h"
|
||||
#include "viewpages/ExecutionViewStep.h"
|
||||
|
||||
// Optional features of Calamares
|
||||
// - Python support with pybind11
|
||||
// - Python support with older Boost implementation
|
||||
// - QML support
|
||||
#ifdef WITH_PYTHON
|
||||
#ifdef WITH_PYBIND11
|
||||
#include "pybind11/PythonJob.h"
|
||||
#else
|
||||
#include "pyboost/PythonJob.h"
|
||||
#endif
|
||||
#endif
|
||||
#ifdef WITH_QML
|
||||
#include "utils/Qml.h"
|
||||
#endif
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCommandLineOption>
|
||||
#include <QCommandLineParser>
|
||||
#include <QCoreApplication>
|
||||
#include <QFileInfo>
|
||||
#include <QLabel>
|
||||
#include <QMainWindow>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#include <memory>
|
||||
|
||||
struct ModuleConfig
|
||||
{
|
||||
QString moduleName() const { return m_module; }
|
||||
QString configFile() const { return m_jobConfig; }
|
||||
QString language() const { return m_language; }
|
||||
QString globalConfigFile() const { return m_globalConfig; }
|
||||
|
||||
QString m_module;
|
||||
QString m_jobConfig;
|
||||
QString m_globalConfig;
|
||||
QString m_settingsConfig;
|
||||
QString m_language;
|
||||
QString m_branding;
|
||||
bool m_ui;
|
||||
bool m_pythonInjection;
|
||||
};
|
||||
|
||||
static ModuleConfig
|
||||
handle_args( QCoreApplication& a )
|
||||
{
|
||||
QCommandLineOption debugLevelOption(
|
||||
QStringLiteral( "D" ), "Verbose output for debugging purposes (0-8), ignored.", "level" );
|
||||
QCommandLineOption settingsOption( { QStringLiteral( "S" ), QStringLiteral( "settings" ) },
|
||||
QStringLiteral( "Settings.conf document" ),
|
||||
QString( "settings.conf" ) );
|
||||
QCommandLineOption globalOption( { QStringLiteral( "g" ), QStringLiteral( "global" ) },
|
||||
QStringLiteral( "Global storage settings document" ),
|
||||
"global.yaml" );
|
||||
QCommandLineOption jobOption(
|
||||
{ QStringLiteral( "j" ), QStringLiteral( "job" ) }, QStringLiteral( "Job settings document" ), "job.yaml" );
|
||||
QCommandLineOption langOption( { QStringLiteral( "l" ), QStringLiteral( "language" ) },
|
||||
QStringLiteral( "Language (global)" ),
|
||||
"languagecode" );
|
||||
QCommandLineOption brandOption( { QStringLiteral( "b" ), QStringLiteral( "branding" ) },
|
||||
QStringLiteral( "Branding directory" ),
|
||||
"path/to/branding.desc",
|
||||
"src/branding/default/branding.desc" );
|
||||
QCommandLineOption uiOption( { QStringLiteral( "U" ), QStringLiteral( "ui" ) }, QStringLiteral( "Enable UI" ) );
|
||||
QCommandLineOption slideshowOption( { QStringLiteral( "s" ), QStringLiteral( "slideshow" ) },
|
||||
QStringLiteral( "Run slideshow module" ) );
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription( "Calamares module tester" );
|
||||
parser.addHelpOption();
|
||||
parser.addVersionOption();
|
||||
|
||||
parser.addOption( debugLevelOption );
|
||||
parser.addOption( settingsOption );
|
||||
parser.addOption( globalOption );
|
||||
parser.addOption( jobOption );
|
||||
parser.addOption( langOption );
|
||||
parser.addOption( brandOption );
|
||||
parser.addOption( uiOption );
|
||||
parser.addOption( slideshowOption );
|
||||
#ifdef WITH_PYTHON
|
||||
QCommandLineOption pythonOption( { QStringLiteral( "P" ), QStringLiteral( "no-injected-python" ) },
|
||||
QStringLiteral( "Do not disable potentially-harmful Python commands" ) );
|
||||
parser.addOption( pythonOption );
|
||||
#endif
|
||||
|
||||
parser.addPositionalArgument( "module", "Path or name of module to run." );
|
||||
parser.addPositionalArgument( "job.yaml", "Path of job settings document to use.", "[job.yaml]" );
|
||||
|
||||
parser.process( a );
|
||||
|
||||
const QStringList args = parser.positionalArguments();
|
||||
if ( args.isEmpty() && !parser.isSet( slideshowOption ) )
|
||||
{
|
||||
cError() << "Missing <module> path.\n";
|
||||
parser.showHelp();
|
||||
}
|
||||
else if ( args.size() > 2 )
|
||||
{
|
||||
cError() << "More than one <module> path.\n";
|
||||
parser.showHelp();
|
||||
}
|
||||
else
|
||||
{
|
||||
QString jobSettings( parser.value( jobOption ) );
|
||||
if ( jobSettings.isEmpty() && ( args.size() == 2 ) )
|
||||
{
|
||||
jobSettings = args.at( 1 );
|
||||
}
|
||||
|
||||
bool pythonInjection = true;
|
||||
#ifdef WITH_PYTHON
|
||||
if ( parser.isSet( pythonOption ) )
|
||||
{
|
||||
pythonInjection = false;
|
||||
}
|
||||
#endif
|
||||
return ModuleConfig { parser.isSet( slideshowOption ) ? QStringLiteral( "-" ) : args.first(),
|
||||
jobSettings,
|
||||
parser.value( globalOption ),
|
||||
parser.value( settingsOption ),
|
||||
parser.value( langOption ),
|
||||
parser.value( brandOption ),
|
||||
parser.isSet( slideshowOption ) || parser.isSet( uiOption ),
|
||||
pythonInjection };
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Bogus Job for --slideshow option
|
||||
*
|
||||
* Generally one would use DummyCppJob for this kind of dummy
|
||||
* job, but that class lives in a module so isn't available
|
||||
* in this test application.
|
||||
*
|
||||
* This bogus job just sleeps for 3.
|
||||
*/
|
||||
class ExecViewJob : public Calamares::CppJob
|
||||
{
|
||||
public:
|
||||
explicit ExecViewJob( const QString& name, unsigned long t = 3 )
|
||||
: m_name( name )
|
||||
, m_delay( t )
|
||||
{
|
||||
}
|
||||
~ExecViewJob() override;
|
||||
|
||||
QString prettyName() const override { return m_name; }
|
||||
|
||||
Calamares::JobResult exec() override
|
||||
{
|
||||
QThread::sleep( m_delay );
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
void setConfigurationMap( const QVariantMap& ) override {}
|
||||
|
||||
private:
|
||||
QString m_name;
|
||||
unsigned long m_delay;
|
||||
};
|
||||
|
||||
ExecViewJob::~ExecViewJob() {}
|
||||
|
||||
/** @brief Bogus module for --slideshow option
|
||||
*
|
||||
* Normally the slideshow -- displayed by ExecutionViewStep -- is not
|
||||
* associated with any particular module in the Calamares configuration.
|
||||
* It is added internally by the module manager. For the module-loader
|
||||
* testing application, we need something that pretends to be the
|
||||
* module for the ExecutionViewStep.
|
||||
*/
|
||||
class ExecViewModule : public Calamares::Module
|
||||
{
|
||||
public:
|
||||
ExecViewModule();
|
||||
~ExecViewModule() override;
|
||||
|
||||
void loadSelf() override;
|
||||
|
||||
virtual Calamares::ModuleSystem::Type type() const override;
|
||||
virtual Calamares::ModuleSystem::Interface interface() const override;
|
||||
|
||||
virtual Calamares::JobList jobs() const override;
|
||||
|
||||
protected:
|
||||
void initFrom( const Calamares::ModuleSystem::Descriptor& ) override;
|
||||
};
|
||||
|
||||
ExecViewModule::ExecViewModule()
|
||||
: Calamares::Module()
|
||||
{
|
||||
// Normally the module-loader gives the module an instance key
|
||||
// (out of the settings file, or the descriptor of the module).
|
||||
// We don't have one, so build one -- this gives us "execView@execView".
|
||||
QVariantMap m;
|
||||
const QString execView = QStringLiteral( "execView" );
|
||||
m.insert( "name", execView );
|
||||
Calamares::Module::initFrom( Calamares::ModuleSystem::Descriptor::fromDescriptorData( m, execView ), execView );
|
||||
}
|
||||
|
||||
ExecViewModule::~ExecViewModule() {}
|
||||
|
||||
void
|
||||
ExecViewModule::initFrom( const Calamares::ModuleSystem::Descriptor& )
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
ExecViewModule::loadSelf()
|
||||
{
|
||||
auto* viewStep = new Calamares::ExecutionViewStep();
|
||||
viewStep->setModuleInstanceKey( instanceKey() );
|
||||
viewStep->setConfigurationMap( m_configurationMap );
|
||||
viewStep->appendJobModuleInstanceKey( instanceKey() );
|
||||
Calamares::ViewManager::instance()->addViewStep( viewStep );
|
||||
m_loaded = true;
|
||||
}
|
||||
|
||||
Calamares::Module::Type
|
||||
ExecViewModule::type() const
|
||||
{
|
||||
return Module::Type::View;
|
||||
}
|
||||
|
||||
Calamares::Module::Interface
|
||||
ExecViewModule::interface() const
|
||||
{
|
||||
return Module::Interface::QtPlugin;
|
||||
}
|
||||
|
||||
Calamares::JobList
|
||||
ExecViewModule::jobs() const
|
||||
{
|
||||
Calamares::JobList l;
|
||||
const auto* gs = Calamares::JobQueue::instance()->globalStorage();
|
||||
if ( gs && gs->contains( "jobs" ) )
|
||||
{
|
||||
QVariantList joblist = gs->value( "jobs" ).toList();
|
||||
for ( const auto& jd : joblist )
|
||||
{
|
||||
QVariantMap jobdescription = jd.toMap();
|
||||
if ( jobdescription.contains( "name" ) && jobdescription.contains( "delay" ) )
|
||||
{
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( jobdescription.value( "name" ).toString(),
|
||||
jobdescription.value( "delay" ).toULongLong() ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( l.count() > 0 )
|
||||
{
|
||||
return l;
|
||||
}
|
||||
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( QStringLiteral( "step 1" ) ) ) );
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( QStringLiteral( "step two" ) ) ) );
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( QStringLiteral( "locking mutexes" ), 20 ) ) );
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( QStringLiteral( "unlocking mutexes" ), 1 ) ) );
|
||||
for ( const QString& s : QStringList { "Harder", "Better", "Faster", "Stronger" } )
|
||||
{
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( s, 0 ) ) );
|
||||
}
|
||||
l.append( Calamares::job_ptr( new ExecViewJob( QStringLiteral( "cleaning up" ), 20 ) ) );
|
||||
return l;
|
||||
}
|
||||
|
||||
static Calamares::Module*
|
||||
load_module( const ModuleConfig& moduleConfig )
|
||||
{
|
||||
QString moduleName = moduleConfig.moduleName();
|
||||
if ( moduleName == "-" )
|
||||
{
|
||||
return new ExecViewModule;
|
||||
}
|
||||
|
||||
QFileInfo fi; // This is kept around to hold the path of the module descriptor
|
||||
|
||||
bool ok = false;
|
||||
QVariantMap descriptor;
|
||||
|
||||
QStringList moduleDirectories { "./", "src/modules/", "modules/", CMAKE_INSTALL_FULL_LIBDIR "/calamares/modules/" };
|
||||
for ( const QString& prefix : std::as_const( moduleDirectories ) )
|
||||
{
|
||||
// Could be a complete path, eg. src/modules/dummycpp/module.desc
|
||||
fi = QFileInfo( prefix + moduleName );
|
||||
if ( fi.exists() && fi.isFile() )
|
||||
{
|
||||
descriptor = Calamares::YAML::load( fi, &ok );
|
||||
}
|
||||
if ( ok )
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Could be a path without module.desc
|
||||
fi = QFileInfo( prefix + moduleName );
|
||||
if ( fi.exists() && fi.isDir() )
|
||||
{
|
||||
fi = QFileInfo( prefix + moduleName + "/module.desc" );
|
||||
if ( fi.exists() && fi.isFile() )
|
||||
{
|
||||
descriptor = Calamares::YAML::load( fi, &ok );
|
||||
}
|
||||
if ( ok )
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( !fi.exists() )
|
||||
{
|
||||
cDebug() << "Expected a descriptor file" << fi.path();
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << "Read descriptor" << fi.path() << "and it was empty.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( !ok )
|
||||
{
|
||||
cWarning() << "No suitable module descriptor found in" << Logger::DebugList( moduleDirectories );
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString name = descriptor.value( "name" ).toString();
|
||||
if ( name.isEmpty() )
|
||||
{
|
||||
cWarning() << "No name found in module descriptor" << fi.absoluteFilePath();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QString moduleDirectory = fi.absolutePath();
|
||||
QString configFile( moduleConfig.configFile().isEmpty() ? moduleDirectory + '/' + name + ".conf"
|
||||
: moduleConfig.configFile() );
|
||||
|
||||
cDebug() << Logger::SubEntry << "Module" << moduleName << "job-configuration:" << configFile;
|
||||
|
||||
Calamares::Module* module = Calamares::moduleFromDescriptor(
|
||||
Calamares::ModuleSystem::Descriptor::fromDescriptorData( descriptor, fi.absoluteFilePath() ),
|
||||
name,
|
||||
configFile,
|
||||
moduleDirectory );
|
||||
|
||||
return module;
|
||||
}
|
||||
|
||||
static bool
|
||||
is_ui_option( const char* s )
|
||||
{
|
||||
return !qstrcmp( s, "--ui" ) || !qstrcmp( s, "-U" );
|
||||
}
|
||||
|
||||
static bool
|
||||
is_slideshow_option( const char* s )
|
||||
{
|
||||
return !qstrcmp( s, "--slideshow" ) || !qstrcmp( s, "-s" );
|
||||
}
|
||||
|
||||
/** @brief Create the right kind of QApplication
|
||||
*
|
||||
* Does primitive parsing of argv[] to find the --ui option and returns
|
||||
* a UI-enabled application if it does.
|
||||
*
|
||||
* @p argc must be a reference (to main's argc) because the QCoreApplication
|
||||
* constructors take a reference as well, and that would otherwise be a
|
||||
* reference to a temporary.
|
||||
*/
|
||||
QCoreApplication*
|
||||
createApplication( int& argc, char* argv[] )
|
||||
{
|
||||
for ( int i = 1; i < argc; ++i )
|
||||
{
|
||||
if ( is_slideshow_option( argv[ i ] ) || is_ui_option( argv[ i ] ) )
|
||||
{
|
||||
auto* aw = new QApplication( argc, argv );
|
||||
aw->setQuitOnLastWindowClosed( true );
|
||||
return aw;
|
||||
}
|
||||
}
|
||||
return new QCoreApplication( argc, argv );
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
static const char pythonPreScript[] = R"%(
|
||||
# This is Python code executed by Python modules *before* the
|
||||
# script file (e.g. main.py) is executed.
|
||||
#
|
||||
# Calls to suprocess methods that execute something are
|
||||
# suppressed and logged -- scripts should really be using libcalamares
|
||||
# methods instead.
|
||||
_calamares_subprocess = __import__("subprocess", globals(), locals(), [], 0)
|
||||
import sys
|
||||
import libcalamares
|
||||
class fake_subprocess(object):
|
||||
PIPE = object()
|
||||
STDOUT = object()
|
||||
STDERR = object()
|
||||
class CompletedProcess(object):
|
||||
returncode = 0
|
||||
stdout = ""
|
||||
stderr = ""
|
||||
@staticmethod
|
||||
def call(*args, **kwargs):
|
||||
libcalamares.utils.debug("subprocess.call(%r,%r) X ignored" % (args, kwargs))
|
||||
return 0
|
||||
@staticmethod
|
||||
def check_call(*args, **kwargs):
|
||||
libcalamares.utils.debug("subprocess.check_call(%r,%r) X ignored" % (args, kwargs))
|
||||
return 0
|
||||
# This is a 3.5-and-later method, is supposed to return a CompletedProcess
|
||||
@staticmethod
|
||||
def run(*args, **kwargs):
|
||||
libcalamares.utils.debug("subprocess.run(%r,%r) X ignored" % (args, kwargs))
|
||||
return fake_subprocess.CompletedProcess()
|
||||
for attr in ("CalledProcessError",):
|
||||
setattr(fake_subprocess,attr,getattr(_calamares_subprocess,attr))
|
||||
sys.modules["subprocess"] = fake_subprocess
|
||||
libcalamares.utils.debug('pre-script for testing purposes injected')
|
||||
|
||||
)%";
|
||||
#endif
|
||||
|
||||
int
|
||||
main( int argc, char* argv[] )
|
||||
{
|
||||
QCoreApplication* application = createApplication( argc, argv );
|
||||
|
||||
Logger::setupLogLevel( Logger::LOGVERBOSE );
|
||||
|
||||
ModuleConfig module = handle_args( *application );
|
||||
if ( module.moduleName().isEmpty() )
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::unique_ptr< Calamares::Settings > settings_p( Calamares::Settings::init( module.m_settingsConfig ) );
|
||||
std::unique_ptr< Calamares::JobQueue > jobqueue_p( new Calamares::JobQueue( nullptr ) );
|
||||
std::unique_ptr< Calamares::System > system_p( new Calamares::System( settings_p->doChroot() ) );
|
||||
|
||||
QMainWindow* mainWindow = nullptr;
|
||||
|
||||
auto* gs = jobqueue_p->globalStorage();
|
||||
if ( !module.globalConfigFile().isEmpty() )
|
||||
{
|
||||
gs->loadYaml( module.globalConfigFile() );
|
||||
}
|
||||
if ( !module.language().isEmpty() )
|
||||
{
|
||||
QVariantMap vm;
|
||||
vm.insert( "LANG", module.language() );
|
||||
gs->insert( "localeConf", vm );
|
||||
}
|
||||
|
||||
#ifdef WITH_PYTHON
|
||||
if ( module.m_pythonInjection )
|
||||
{
|
||||
#ifdef WITH_PYBIND11
|
||||
Calamares::Python::Job::setInjectedPreScript( pythonPreScript );
|
||||
#else
|
||||
// Old Boost approach
|
||||
Calamares::PythonJob::setInjectedPreScript( pythonPreScript );
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
#ifdef WITH_QML
|
||||
Calamares::initQmlModulesDir(); // don't care if failed
|
||||
#endif
|
||||
|
||||
cDebug() << "Calamares module-loader testing" << module.moduleName();
|
||||
std::unique_ptr<Calamares::Module> m( load_module( module ) );
|
||||
std::unique_ptr<Calamares::ModuleManager> modulemanager;
|
||||
if ( !m )
|
||||
{
|
||||
cError() << "Could not load module" << module.moduleName();
|
||||
return 1;
|
||||
}
|
||||
|
||||
cDebug() << Logger::SubEntry << "got" << m->name() << m->typeString() << m->interfaceString();
|
||||
if ( m->type() == Calamares::Module::Type::View )
|
||||
{
|
||||
// If we forgot the --ui, any ViewModule will core dump as it
|
||||
// tries to create the widget **which won't be used anyway**.
|
||||
//
|
||||
// To avoid that crash, re-create the QApplication, now with GUI
|
||||
if ( !qobject_cast< QApplication* >( application ) )
|
||||
{
|
||||
auto* replace_app = new QApplication( argc, argv );
|
||||
replace_app->setQuitOnLastWindowClosed( true );
|
||||
application = replace_app;
|
||||
}
|
||||
mainWindow = module.m_ui ? new QMainWindow() : nullptr;
|
||||
if ( mainWindow )
|
||||
{
|
||||
mainWindow->installEventFilter( Calamares::Retranslator::instance() );
|
||||
}
|
||||
|
||||
(void)new Calamares::Branding( module.m_branding );
|
||||
modulemanager = std::make_unique<Calamares::ModuleManager>( QStringList(), nullptr );
|
||||
(void)Calamares::ViewManager::instance( mainWindow );
|
||||
modulemanager->addModule( m.release() ); // Transfers ownership
|
||||
}
|
||||
|
||||
if ( !m->isLoaded() )
|
||||
{
|
||||
m->loadSelf();
|
||||
}
|
||||
|
||||
if ( !m->isLoaded() )
|
||||
{
|
||||
cError() << "Module" << module.moduleName() << "could not be loaded.";
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ( mainWindow )
|
||||
{
|
||||
auto* vm = Calamares::ViewManager::instance();
|
||||
vm->onInitComplete();
|
||||
QWidget* w = vm->currentStep()->widget();
|
||||
w->setParent( mainWindow );
|
||||
mainWindow->setCentralWidget( w );
|
||||
w->show();
|
||||
mainWindow->show();
|
||||
return application->exec();
|
||||
}
|
||||
|
||||
using TR = Logger::DebugRow< const char*, const QString >;
|
||||
|
||||
cDebug() << Logger::SubEntry << "Module metadata" << TR( "name", m->name() ) << TR( "type", m->typeString() )
|
||||
<< TR( "interface", m->interfaceString() );
|
||||
|
||||
Calamares::JobQueue::instance()->enqueue( 100, m->jobs() );
|
||||
|
||||
QObject::connect( Calamares::JobQueue::instance(),
|
||||
&Calamares::JobQueue::finished,
|
||||
[ application ]()
|
||||
{ QTimer::singleShot( std::chrono::seconds( 3 ), application, &QApplication::quit ); } );
|
||||
QTimer::singleShot( 0, []() { Calamares::JobQueue::instance()->start(); } );
|
||||
|
||||
return application->exec();
|
||||
}
|
||||
255
calamares/src/libcalamares/CMakeLists.txt
Normal file
255
calamares/src/libcalamares/CMakeLists.txt
Normal file
@@ -0,0 +1,255 @@
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
|
||||
#
|
||||
# libcalamares is the non-GUI part of Calamares, which includes handling
|
||||
# translations, configurations, logging, utilities, global storage, and
|
||||
# (non-GUI) jobs.
|
||||
#
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CalamaresConfig.h.in ${CMAKE_CURRENT_BINARY_DIR}/CalamaresConfig.h)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CalamaresVersion.h.in ${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersion.h)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CalamaresVersionX.h.in ${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersionX.h)
|
||||
|
||||
# Map the available translations names into a suitable constexpr list
|
||||
# of names in C++. This gets us Calamares::Locale::availableLanguages,
|
||||
# a QStringList of names.
|
||||
set(_names_tu
|
||||
"
|
||||
#ifndef CALAMARES_TRANSLATIONS_H
|
||||
#define CALAMARES_TRANSLATIONS_H
|
||||
#include <QStringList>
|
||||
namespace {
|
||||
static const QStringList availableLanguageList{
|
||||
"
|
||||
)
|
||||
foreach(l ${CALAMARES_TRANSLATION_LANGUAGES})
|
||||
string(APPEND _names_tu "\"${l}\",\n")
|
||||
endforeach()
|
||||
string(APPEND _names_tu "};\n} // namespace\n#endif\n\n")
|
||||
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/CalamaresTranslations.cc "${_names_tu}")
|
||||
|
||||
# Some implementation details are compiled twice, because
|
||||
# we want them in the library with visibility("hidden")
|
||||
# but also need them in tests.
|
||||
set(_geoip_src
|
||||
geoip/GeoIPFixed.cpp
|
||||
geoip/GeoIPJSON.cpp
|
||||
)
|
||||
|
||||
add_library(
|
||||
calamares
|
||||
SHARED
|
||||
CalamaresAbout.cpp
|
||||
CppJob.cpp
|
||||
GlobalStorage.cpp
|
||||
Job.cpp
|
||||
JobExample.cpp
|
||||
JobQueue.cpp
|
||||
ProcessJob.cpp
|
||||
Settings.cpp
|
||||
# GeoIP services
|
||||
geoip/Interface.cpp
|
||||
${_geoip_src}
|
||||
geoip/Handler.cpp
|
||||
# Locale-data service
|
||||
locale/Global.cpp
|
||||
locale/Lookup.cpp
|
||||
locale/TimeZone.cpp
|
||||
locale/TranslatableConfiguration.cpp
|
||||
locale/TranslatableString.cpp
|
||||
locale/Translation.cpp
|
||||
locale/TranslationsModel.cpp
|
||||
# Modules
|
||||
modulesystem/Config.cpp
|
||||
modulesystem/Descriptor.cpp
|
||||
modulesystem/InstanceKey.cpp
|
||||
modulesystem/Module.cpp
|
||||
modulesystem/Preset.cpp
|
||||
modulesystem/RequirementsChecker.cpp
|
||||
modulesystem/RequirementsModel.cpp
|
||||
# Network service
|
||||
network/Manager.cpp
|
||||
# Packages service
|
||||
packages/Globals.cpp
|
||||
# Partition service
|
||||
partition/Global.cpp
|
||||
partition/Mount.cpp
|
||||
partition/PartitionSize.cpp
|
||||
partition/Sync.cpp
|
||||
# Utility service
|
||||
utils/CommandList.cpp
|
||||
utils/Dirs.cpp
|
||||
utils/Entropy.cpp
|
||||
utils/Logger.cpp
|
||||
utils/Permissions.cpp
|
||||
utils/PluginFactory.cpp
|
||||
utils/Retranslator.cpp
|
||||
utils/Runner.cpp
|
||||
utils/String.cpp
|
||||
utils/StringExpander.cpp
|
||||
utils/System.cpp
|
||||
utils/UMask.cpp
|
||||
utils/Variant.cpp
|
||||
utils/Yaml.cpp
|
||||
)
|
||||
|
||||
set_target_properties(
|
||||
calamares
|
||||
PROPERTIES
|
||||
VERSION ${CALAMARES_VERSION_SHORT}
|
||||
SOVERSION ${CALAMARES_SOVERSION}
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
)
|
||||
target_link_libraries(calamares LINK_PUBLIC yamlcpp::yamlcpp ${qtname}::Core ${qtname}::Network)
|
||||
target_link_libraries(calamares LINK_PUBLIC ${kfname}::CoreAddons)
|
||||
|
||||
target_compile_definitions(calamares PRIVATE DLLEXPORT_PRO)
|
||||
target_include_directories(calamares PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
|
||||
target_include_directories(calamares PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<INSTALL_INTERFACE:include/libcalamares>
|
||||
)
|
||||
target_include_directories(calamares PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}>)
|
||||
|
||||
### OPTIONAL Automount support (requires dbus)
|
||||
#
|
||||
#
|
||||
if(TARGET ${qtname}::DBus)
|
||||
target_sources(calamares PRIVATE partition/AutoMount.cpp)
|
||||
target_link_libraries(calamares PRIVATE ${qtname}::DBus)
|
||||
endif()
|
||||
|
||||
### OPTIONAL Python support
|
||||
#
|
||||
#
|
||||
if(WITH_PYTHON)
|
||||
if(WITH_PYBIND11)
|
||||
target_include_directories(calamares PRIVATE pybind11)
|
||||
target_sources(calamares PRIVATE pybind11/Api.cpp pybind11/PythonJob.cpp)
|
||||
target_link_libraries(calamares PRIVATE Python::Python pybind11::headers)
|
||||
else()
|
||||
target_include_directories(calamares PRIVATE pyboost)
|
||||
target_sources(calamares PRIVATE pyboost/PythonHelper.cpp pyboost/PythonJob.cpp pyboost/PythonJobApi.cpp)
|
||||
target_link_libraries(calamares PRIVATE Python::Python Boost::python)
|
||||
endif()
|
||||
target_sources(calamares PRIVATE python/Api.cpp python/Variant.cpp)
|
||||
endif()
|
||||
|
||||
### OPTIONAL GeoIP XML support
|
||||
#
|
||||
#
|
||||
find_package(${qtname} ${QT_VERSION} COMPONENTS Xml)
|
||||
if(TARGET ${qtname}::Xml)
|
||||
target_sources(calamares PRIVATE geoip/GeoIPXML.cpp)
|
||||
target_link_libraries(calamares PRIVATE ${qtname}::Network ${qtname}::Xml)
|
||||
endif()
|
||||
|
||||
### OPTIONAL KPMcore support
|
||||
#
|
||||
#
|
||||
include(KPMcoreHelper)
|
||||
|
||||
if(KPMcore_FOUND)
|
||||
target_sources(
|
||||
calamares
|
||||
PRIVATE
|
||||
partition/FileSystem.cpp
|
||||
partition/KPMManager.cpp
|
||||
partition/PartitionIterator.cpp
|
||||
partition/PartitionQuery.cpp
|
||||
)
|
||||
endif()
|
||||
# Always, since this also handles the no-KPMcore case; we don't
|
||||
# call it calamares::kpmcore because that name exists only
|
||||
# when KPMcore is actually found.
|
||||
target_link_libraries(calamares PRIVATE calapmcore)
|
||||
|
||||
### LIBRARY
|
||||
#
|
||||
#
|
||||
calamares_automoc( calamares )
|
||||
add_library(Calamares::calamares ALIAS calamares)
|
||||
|
||||
### Installation
|
||||
#
|
||||
#
|
||||
install(
|
||||
TARGETS calamares
|
||||
EXPORT Calamares
|
||||
RUNTIME
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}
|
||||
LIBRARY
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
ARCHIVE
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
||||
)
|
||||
|
||||
# Make symlink lib/calamares/libcalamares.so to lib/libcalamares.so.VERSION so
|
||||
# lib/calamares can be used as module path for the Python interpreter.
|
||||
install(
|
||||
CODE
|
||||
"
|
||||
file( MAKE_DIRECTORY \"\$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_LIBDIR}/calamares\" )
|
||||
execute_process( COMMAND \"${CMAKE_COMMAND}\" -E create_symlink ../libcalamares.so.${CALAMARES_VERSION_SHORT} libcalamares.so WORKING_DIRECTORY \"\$ENV{DESTDIR}/${CMAKE_INSTALL_FULL_LIBDIR}/calamares\" )
|
||||
"
|
||||
)
|
||||
|
||||
# Install header files
|
||||
file(GLOB rootHeaders "*.h")
|
||||
install(
|
||||
FILES ${CMAKE_CURRENT_BINARY_DIR}/CalamaresConfig.h ${CMAKE_CURRENT_BINARY_DIR}/CalamaresVersion.h ${rootHeaders}
|
||||
DESTINATION include/libcalamares
|
||||
)
|
||||
# Install each subdir-worth of header files
|
||||
foreach(subdir geoip locale modulesystem network partition utils compat packages)
|
||||
file(GLOB subdir_headers "${subdir}/*.h")
|
||||
install(FILES ${subdir_headers} DESTINATION include/libcalamares/${subdir})
|
||||
endforeach()
|
||||
|
||||
### TRANSLATION TESTING
|
||||
#
|
||||
|
||||
calamares_qrc_translations( localetest OUTPUT_VARIABLE localetest_qrc SUBDIRECTORY testdata LANGUAGES nl )
|
||||
|
||||
### TESTING
|
||||
#
|
||||
#
|
||||
calamares_add_test(libcalamarestest SOURCES Tests.cpp)
|
||||
|
||||
calamares_add_test(libcalamaresgeoiptest SOURCES geoip/GeoIPTests.cpp ${_geoip_src})
|
||||
|
||||
calamares_add_test(libcalamareslocaletest SOURCES locale/Tests.cpp ${localetest_qrc})
|
||||
|
||||
calamares_add_test(libcalamaresmodulesystemtest SOURCES modulesystem/Tests.cpp)
|
||||
|
||||
calamares_add_test(libcalamaresnetworktest SOURCES network/Tests.cpp)
|
||||
|
||||
calamares_add_test(libcalamarespackagestest SOURCES packages/Tests.cpp)
|
||||
|
||||
if(KPMcore_FOUND)
|
||||
calamares_add_test(
|
||||
libcalamarespartitiontest
|
||||
SOURCES partition/Global.cpp partition/Tests.cpp
|
||||
LIBRARIES calamares::kpmcore
|
||||
)
|
||||
calamares_add_test(libcalamarespartitionkpmtest SOURCES partition/KPMTests.cpp LIBRARIES calamares::kpmcore)
|
||||
endif()
|
||||
|
||||
calamares_add_test(libcalamaresutilstest SOURCES utils/Tests.cpp utils/Permissions.cpp utils/Runner.cpp)
|
||||
|
||||
calamares_add_test(libcalamaresutilspathstest SOURCES utils/TestPaths.cpp)
|
||||
|
||||
# This is not an actual test, it's a test / demo application
|
||||
# for experimenting with GeoIP.
|
||||
add_executable(test_geoip geoip/test_geoip.cpp ${_geoip_src})
|
||||
target_link_libraries(test_geoip Calamares::calamares ${qtname}::Network yamlcpp::yamlcpp)
|
||||
calamares_automoc( test_geoip )
|
||||
|
||||
if(TARGET ${qtname}::DBus)
|
||||
add_executable(test_automount partition/calautomount.cpp)
|
||||
target_link_libraries(test_automount Calamares::calamares ${qtname}::DBus)
|
||||
endif()
|
||||
76
calamares/src/libcalamares/CalamaresAbout.cpp
Normal file
76
calamares/src/libcalamares/CalamaresAbout.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CalamaresAbout.h"
|
||||
|
||||
#include "CalamaresVersionX.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
static const char s_header[]
|
||||
= QT_TRANSLATE_NOOP( "AboutData", "<h1>%1</h1><br/><strong>%2<br/> for %3</strong><br/><br/>" );
|
||||
|
||||
static const char s_footer[]
|
||||
= QT_TRANSLATE_NOOP( "AboutData",
|
||||
"Thanks to <a href=\"https://calamares.io/team/\">the Calamares team</a> "
|
||||
"and the <a href=\"https://app.transifex.com/calamares/calamares/\">Calamares "
|
||||
"translators team</a>." );
|
||||
|
||||
struct Maintainer
|
||||
{
|
||||
unsigned int start;
|
||||
unsigned int end;
|
||||
const char* name;
|
||||
const char* email;
|
||||
QString text() const
|
||||
{
|
||||
//: Copyright year-year Name <email-address>
|
||||
return QCoreApplication::translate( "AboutData", "Copyright %1-%2 %3 <%4><br/>" )
|
||||
.arg( start )
|
||||
.arg( end )
|
||||
.arg( name )
|
||||
.arg( email );
|
||||
}
|
||||
};
|
||||
|
||||
static constexpr const Maintainer maintainers[] = {
|
||||
{ 2014, 2017, "Teo Mrnjavac", "teo@kde.org" },
|
||||
{ 2017, 2022, "Adriaan de Groot", "groot@kde.org" },
|
||||
{ 2022, 2024, "Adriaan de Groot (community)", "groot@kde.org" },
|
||||
};
|
||||
|
||||
static QString
|
||||
aboutMaintainers()
|
||||
{
|
||||
QStringList s;
|
||||
for ( const auto& m : maintainers )
|
||||
{
|
||||
s.append( m.text() );
|
||||
}
|
||||
return s.join( QString() );
|
||||
}
|
||||
|
||||
static QString
|
||||
substituteVersions( const QString& s )
|
||||
{
|
||||
return s.arg( CALAMARES_APPLICATION_NAME ).arg( CALAMARES_VERSION );
|
||||
}
|
||||
|
||||
const QString
|
||||
Calamares::aboutString()
|
||||
{
|
||||
return substituteVersions( QCoreApplication::translate( "AboutData", s_header ) ) + aboutMaintainers()
|
||||
+ QCoreApplication::translate( "AboutData", s_footer );
|
||||
}
|
||||
|
||||
const QString
|
||||
Calamares::aboutStringUntranslated()
|
||||
{
|
||||
return substituteVersions( QString( s_header ) ) + aboutMaintainers() + QString( s_footer );
|
||||
}
|
||||
31
calamares/src/libcalamares/CalamaresAbout.h
Normal file
31
calamares/src/libcalamares/CalamaresAbout.h
Normal file
@@ -0,0 +1,31 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2022 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_CALAMARESABOUT_H
|
||||
#define CALAMARES_CALAMARESABOUT_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
/** @brief Returns an about string for the application
|
||||
*
|
||||
* The about string includes a header-statement, a list of maintainer
|
||||
* addresses, and a thank-you to Blue Systems. There is on %-substitution
|
||||
* left, where you can fill in the name of the product (e.g. to say
|
||||
* "Calamares for Netrunner" or ".. for Manjaro").
|
||||
*/
|
||||
DLLEXPORT const QString aboutStringUntranslated();
|
||||
/// @brief As above, but translated in the current Calamares language
|
||||
DLLEXPORT const QString aboutString();
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
34
calamares/src/libcalamares/CalamaresConfig.h.in
Normal file
34
calamares/src/libcalamares/CalamaresConfig.h.in
Normal file
@@ -0,0 +1,34 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARESCONFIG_H
|
||||
#define CALAMARESCONFIG_H
|
||||
|
||||
#define CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}"
|
||||
#define CMAKE_INSTALL_FULL_LIBEXECDIR "${CMAKE_INSTALL_FULL_LIBEXECDIR}"
|
||||
#define CMAKE_INSTALL_LIBDIR "${CMAKE_INSTALL_LIBDIR}"
|
||||
#define CMAKE_INSTALL_FULL_LIBDIR "${CMAKE_INSTALL_FULL_LIBDIR}"
|
||||
#define CMAKE_INSTALL_FULL_DATADIR "${CMAKE_INSTALL_FULL_DATADIR}/calamares"
|
||||
#define CMAKE_INSTALL_FULL_SYSCONFDIR "${CMAKE_INSTALL_FULL_SYSCONFDIR}"
|
||||
#define CMAKE_BUILD_TYPE "${CMAKE_BUILD_TYPE}"
|
||||
|
||||
/*
|
||||
* These are feature-settings that affect consumers of Calamares
|
||||
* libraries as well; without Python-support in the libs, for instance,
|
||||
* there's no point in having a Python plugin.
|
||||
*
|
||||
* This list should match the one in CalamaresConfig.cmake
|
||||
* which is the CMake-time side of the same configuration.
|
||||
*/
|
||||
#cmakedefine WITH_PYTHON
|
||||
#cmakedefine WITH_PYBIND11
|
||||
#cmakedefine WITH_BOOST_PYTHON
|
||||
#cmakedefine WITH_QML
|
||||
#cmakedefine WITH_QT6
|
||||
|
||||
#endif // CALAMARESCONFIG_H
|
||||
17
calamares/src/libcalamares/CalamaresVersion.h.in
Normal file
17
calamares/src/libcalamares/CalamaresVersion.h.in
Normal file
@@ -0,0 +1,17 @@
|
||||
// SPDX-FileCopyrightText: no
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
#ifndef CALAMARES_VERSION_H
|
||||
#define CALAMARES_VERSION_H
|
||||
|
||||
#cmakedefine CALAMARES_ORGANIZATION_NAME "${CALAMARES_ORGANIZATION_NAME}"
|
||||
#cmakedefine CALAMARES_ORGANIZATION_DOMAIN "${CALAMARES_ORGANIZATION_DOMAIN}"
|
||||
#cmakedefine CALAMARES_APPLICATION_NAME "${CALAMARES_APPLICATION_NAME}"
|
||||
#cmakedefine CALAMARES_VERSION "${CALAMARES_VERSION_SHORT}"
|
||||
#cmakedefine CALAMARES_VERSION_SHORT "${CALAMARES_VERSION_SHORT}"
|
||||
|
||||
#cmakedefine CALAMARES_VERSION_MAJOR "${CALAMARES_VERSION_MAJOR}"
|
||||
#cmakedefine CALAMARES_VERSION_MINOR "${CALAMARES_VERSION_MINOR}"
|
||||
#cmakedefine CALAMARES_VERSION_PATCH "${CALAMARES_VERSION_PATCH}"
|
||||
#cmakedefine CALAMARES_VERSION_RC "${CALAMARES_VERSION_RC}"
|
||||
|
||||
#endif // CALAMARES_VERSION_H
|
||||
16
calamares/src/libcalamares/CalamaresVersionX.h.in
Normal file
16
calamares/src/libcalamares/CalamaresVersionX.h.in
Normal file
@@ -0,0 +1,16 @@
|
||||
// SPDX-FileCopyrightText: no
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
//
|
||||
// Same as CalamaresVersion.h, but with a full-git-extended VERSION
|
||||
// rather than the short (vM.m.p) semantic version.
|
||||
#ifndef CALAMARES_VERSION_H
|
||||
|
||||
// On purpose, do not define the guard, but let CalamaresVersion.h do it
|
||||
// #define CALAMARES_VERSION_H
|
||||
|
||||
#include "CalamaresVersion.h"
|
||||
|
||||
#undef CALAMARES_VERSION
|
||||
#cmakedefine CALAMARES_VERSION "${CALAMARES_VERSION_LONG}"
|
||||
|
||||
#endif // CALAMARES_VERSION_H
|
||||
38
calamares/src/libcalamares/CppJob.cpp
Normal file
38
calamares/src/libcalamares/CppJob.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2016 Kevin Kofler <kevin.kofler@chello.at>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CppJob.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
CppJob::CppJob( QObject* parent )
|
||||
: Job( parent )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
CppJob::~CppJob() {}
|
||||
|
||||
|
||||
void
|
||||
CppJob::setModuleInstanceKey( const Calamares::ModuleSystem::InstanceKey& instanceKey )
|
||||
{
|
||||
m_instanceKey = instanceKey;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
CppJob::setConfigurationMap( const QVariantMap& configurationMap )
|
||||
{
|
||||
Q_UNUSED( configurationMap )
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
44
calamares/src/libcalamares/CppJob.h
Normal file
44
calamares/src/libcalamares/CppJob.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2016 Kevin Kofler <kevin.kofler@chello.at>
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_CPPJOB_H
|
||||
#define CALAMARES_CPPJOB_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Job.h"
|
||||
|
||||
#include "modulesystem/InstanceKey.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
class DLLEXPORT CppJob : public Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit CppJob( QObject* parent = nullptr );
|
||||
~CppJob() override;
|
||||
|
||||
void setModuleInstanceKey( const Calamares::ModuleSystem::InstanceKey& instanceKey );
|
||||
Calamares::ModuleSystem::InstanceKey moduleInstanceKey() const { return m_instanceKey; }
|
||||
|
||||
virtual void setConfigurationMap( const QVariantMap& configurationMap );
|
||||
|
||||
protected:
|
||||
Calamares::ModuleSystem::InstanceKey m_instanceKey;
|
||||
};
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_CPPJOB_H
|
||||
68
calamares/src/libcalamares/DllMacro.h
Normal file
68
calamares/src/libcalamares/DllMacro.h
Normal file
@@ -0,0 +1,68 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef DLLMACRO_H
|
||||
#define DLLMACRO_H
|
||||
|
||||
#ifndef CALAMARES_EXPORT
|
||||
#define CALAMARES_EXPORT __attribute__( ( visibility( "default" ) ) )
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Mark symbols exported from Calamares non-GUI library with DLLEXPORT.
|
||||
* These are the public API of libcalamares.
|
||||
*/
|
||||
#ifndef DLLEXPORT
|
||||
#if defined( DLLEXPORT_PRO )
|
||||
#define DLLEXPORT CALAMARES_EXPORT
|
||||
#else
|
||||
#define DLLEXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Mark symbols exported from Calamares GUI library with DLLEXPORT.
|
||||
* These are the public API of libcalamaresui.
|
||||
*/
|
||||
#ifndef UIDLLEXPORT
|
||||
#if defined( UIDLLEXPORT_PRO )
|
||||
#define UIDLLEXPORT CALAMARES_EXPORT
|
||||
#else
|
||||
#define UIDLLEXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Mark symbols exported from Calamares C++ plugins with PLUGINDLLEXPORT.
|
||||
* These are the public API of the libraries (generally, the plugin
|
||||
* entry point)
|
||||
*/
|
||||
#ifndef PLUGINDLLEXPORT
|
||||
#if defined( PLUGINDLLEXPORT_PRO )
|
||||
#define PLUGINDLLEXPORT CALAMARES_EXPORT
|
||||
#else
|
||||
#define PLUGINDLLEXPORT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* For functions that should be static in production but also need to
|
||||
* be tested, use STATICTEST as linkage specifier. When built as part
|
||||
* of a test, the function will be given normal linkage.
|
||||
*/
|
||||
#ifndef STATICTEST
|
||||
#if defined( BUILD_AS_TEST )
|
||||
#define STATICTEST
|
||||
#else
|
||||
#define STATICTEST static
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
244
calamares/src/libcalamares/GlobalStorage.cpp
Normal file
244
calamares/src/libcalamares/GlobalStorage.cpp
Normal file
@@ -0,0 +1,244 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
|
||||
#include "compat/Mutex.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Units.h"
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QJsonDocument>
|
||||
|
||||
using namespace Calamares::Units;
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
class GlobalStorage::ReadLock : public MutexLocker
|
||||
{
|
||||
public:
|
||||
ReadLock( const GlobalStorage* gs )
|
||||
: MutexLocker( &gs->m_mutex )
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class GlobalStorage::WriteLock : public MutexLocker
|
||||
{
|
||||
public:
|
||||
WriteLock( GlobalStorage* gs )
|
||||
: MutexLocker( &gs->m_mutex )
|
||||
, m_gs( gs )
|
||||
{
|
||||
}
|
||||
~WriteLock() { m_gs->changed(); }
|
||||
|
||||
GlobalStorage* m_gs;
|
||||
};
|
||||
|
||||
GlobalStorage::GlobalStorage( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorage::contains( const QString& key ) const
|
||||
{
|
||||
ReadLock l( this );
|
||||
return m.contains( key );
|
||||
}
|
||||
|
||||
int
|
||||
GlobalStorage::count() const
|
||||
{
|
||||
ReadLock l( this );
|
||||
return m.count();
|
||||
}
|
||||
|
||||
void
|
||||
GlobalStorage::insert( const QString& key, const QVariant& value )
|
||||
{
|
||||
WriteLock l( this );
|
||||
m.insert( key, value );
|
||||
}
|
||||
|
||||
QStringList
|
||||
GlobalStorage::keys() const
|
||||
{
|
||||
ReadLock l( this );
|
||||
return m.keys();
|
||||
}
|
||||
|
||||
int
|
||||
GlobalStorage::remove( const QString& key )
|
||||
{
|
||||
WriteLock l( this );
|
||||
int nItems = m.remove( key );
|
||||
return nItems;
|
||||
}
|
||||
|
||||
void
|
||||
GlobalStorage::clear()
|
||||
{
|
||||
WriteLock l( this );
|
||||
m.clear();
|
||||
}
|
||||
|
||||
QVariant
|
||||
GlobalStorage::value( const QString& key ) const
|
||||
{
|
||||
ReadLock l( this );
|
||||
return m.value( key );
|
||||
}
|
||||
|
||||
void
|
||||
GlobalStorage::debugDump() const
|
||||
{
|
||||
ReadLock l( this );
|
||||
cDebug() << "GlobalStorage" << Logger::Pointer( this ) << m.count() << "items";
|
||||
for ( auto it = m.cbegin(); it != m.cend(); ++it )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << it.key() << '\t' << it.value();
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorage::saveJson( const QString& filename ) const
|
||||
{
|
||||
ReadLock l( this );
|
||||
QFile f( filename );
|
||||
if ( !f.open( QFile::WriteOnly ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
f.write( QJsonDocument::fromVariant( m ).toJson() );
|
||||
f.close();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorage::loadJson( const QString& filename )
|
||||
{
|
||||
QFile f( filename );
|
||||
if ( !f.open( QFile::ReadOnly ) )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QJsonParseError e;
|
||||
QJsonDocument d = QJsonDocument::fromJson( f.read( 1_MiB ), &e );
|
||||
if ( d.isNull() )
|
||||
{
|
||||
cWarning() << filename << e.errorString();
|
||||
}
|
||||
else if ( !d.isObject() )
|
||||
{
|
||||
cWarning() << filename << "Not suitable JSON.";
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteLock l( this );
|
||||
// Do **not** use method insert() here, because it would
|
||||
// recursively lock the mutex, leading to deadlock. Also,
|
||||
// that would emit changed() for each key.
|
||||
auto map = d.toVariant().toMap();
|
||||
for ( auto i = map.constBegin(); i != map.constEnd(); ++i )
|
||||
{
|
||||
m.insert( i.key(), *i );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorage::saveYaml( const QString& filename ) const
|
||||
{
|
||||
ReadLock l( this );
|
||||
return Calamares::YAML::save( filename, m );
|
||||
}
|
||||
|
||||
bool
|
||||
GlobalStorage::loadYaml( const QString& filename )
|
||||
{
|
||||
bool ok = false;
|
||||
auto map = Calamares::YAML::load( filename, &ok );
|
||||
if ( ok )
|
||||
{
|
||||
WriteLock l( this );
|
||||
// Do **not** use method insert() here, because it would
|
||||
// recursively lock the mutex, leading to deadlock. Also,
|
||||
// that would emit changed() for each key.
|
||||
for ( auto i = map.constBegin(); i != map.constEnd(); ++i )
|
||||
{
|
||||
m.insert( i.key(), *i );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
///@brief Implementation for recursively looking up dotted selector parts.
|
||||
static QVariant
|
||||
lookup( const QStringList& nestedKey, int index, const QVariant& v, bool& ok )
|
||||
{
|
||||
if ( !v.canConvert< QVariantMap >() )
|
||||
{
|
||||
// Mismatch: we're still looking for keys, but v is not a submap
|
||||
ok = false;
|
||||
return {};
|
||||
}
|
||||
if ( index >= nestedKey.length() )
|
||||
{
|
||||
cError() << "Recursion error looking at index" << index << "of" << nestedKey;
|
||||
ok = false;
|
||||
return {};
|
||||
}
|
||||
|
||||
const QVariantMap map = v.toMap();
|
||||
const QString& key = nestedKey.at( index );
|
||||
if ( index == nestedKey.length() - 1 )
|
||||
{
|
||||
ok = map.contains( key );
|
||||
return ok ? map.value( key ) : QVariant();
|
||||
}
|
||||
else
|
||||
{
|
||||
return lookup( nestedKey, index + 1, map.value( key ), ok );
|
||||
}
|
||||
}
|
||||
|
||||
QVariant
|
||||
lookup( const GlobalStorage* storage, const QString& nestedKey, bool& ok )
|
||||
{
|
||||
ok = false;
|
||||
if ( !storage )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if ( nestedKey.contains( '.' ) )
|
||||
{
|
||||
QStringList steps = nestedKey.split( '.' );
|
||||
return lookup( steps, 1, storage->value( steps.first() ), ok );
|
||||
}
|
||||
else
|
||||
{
|
||||
ok = storage->contains( nestedKey );
|
||||
return ok ? storage->value( nestedKey ) : QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
192
calamares/src/libcalamares/GlobalStorage.h
Normal file
192
calamares/src/libcalamares/GlobalStorage.h
Normal file
@@ -0,0 +1,192 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_GLOBALSTORAGE_H
|
||||
#define CALAMARES_GLOBALSTORAGE_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
/** @brief Storage for data that passes between Calamares modules.
|
||||
*
|
||||
* The Global Storage is global to the Calamares JobQueue and
|
||||
* everything that depends on that: all of its modules use the
|
||||
* same instance of the JobQueue, and so of the Global Storage.
|
||||
*
|
||||
* GS is used to pass data between modules; there is only convention
|
||||
* about what keys are used, and individual modules should document
|
||||
* what they put in to GS or what they expect to find in it.
|
||||
*
|
||||
* GS behaves as a basic key-value store, with a QVariantMap behind
|
||||
* it. Any QVariant can be put into the storage, and the signal
|
||||
* changed() is emitted when any data is modified.
|
||||
*
|
||||
* In general, see QVariantMap (possibly after calling data()) for details.
|
||||
*
|
||||
* This class is thread-safe -- most accesses go through JobQueue, which
|
||||
* handles threading itself, but because modules load in parallel and can
|
||||
* have asynchronous tasks like GeoIP lookups, the storage itself also
|
||||
* has locking. All methods are thread-safe, use data() to make a snapshot
|
||||
* copy for use outside of the thread-safe API.
|
||||
*/
|
||||
class DLLEXPORT GlobalStorage : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/** @brief Create a GS object
|
||||
*
|
||||
* **Generally** there is only one GS object (hence, "global") which
|
||||
* is owned by the JobQueue instance (which is a singleton). However,
|
||||
* it is possible to create more GS objects.
|
||||
*/
|
||||
explicit GlobalStorage( QObject* parent = nullptr );
|
||||
|
||||
/** @brief Insert a key and value into the store
|
||||
*
|
||||
* The @p value is added to the store with key @p key. If @p key
|
||||
* already exists in the store, its existing value is overwritten.
|
||||
* The changed() signal is emitted regardless.
|
||||
*/
|
||||
void insert( const QString& key, const QVariant& value );
|
||||
/** @brief Removes a key and its value
|
||||
*
|
||||
* The @p key is removed from the store. If the @p key does not
|
||||
* exist, nothing happens. changed() is emitted regardless.
|
||||
*
|
||||
* @return the number of keys remaining
|
||||
*/
|
||||
int remove( const QString& key );
|
||||
|
||||
/// @brief Clears all keys in this GS object
|
||||
void clear();
|
||||
|
||||
/** @brief dump keys and values to the debug log
|
||||
*
|
||||
* All the keys and their values are written to the debug log.
|
||||
* See save() for caveats: this can leak sensitive information.
|
||||
*/
|
||||
void debugDump() const;
|
||||
|
||||
/** @brief write as JSON to the given filename
|
||||
*
|
||||
* The file named @p filename is overwritten with a JSON representation
|
||||
* of the entire global storage (this may be structured, for instance
|
||||
* if maps or lists have been inserted).
|
||||
*
|
||||
* No tidying, sanitization, or censoring is done -- for instance,
|
||||
* the user module sets a slightly-obscured password in global storage,
|
||||
* and this JSON file will contain that password in-the-only-slightly-
|
||||
* obscured form.
|
||||
*/
|
||||
bool saveJson( const QString& filename ) const;
|
||||
|
||||
/** @brief Adds the keys from the given JSON file
|
||||
*
|
||||
* No tidying, sanitization, or censoring is done.
|
||||
* The JSON file is read and each key is added as a value to
|
||||
* the global storage. The storage is not cleared first: existing
|
||||
* keys will remain; keys that also occur in the JSON file are overwritten.
|
||||
*/
|
||||
bool loadJson( const QString& filename );
|
||||
|
||||
/** @brief write as YAML to the given filename
|
||||
*
|
||||
* See also save(), above.
|
||||
*/
|
||||
bool saveYaml( const QString& filename ) const;
|
||||
|
||||
/** @brief reads settings from the given filename
|
||||
*
|
||||
* See also load(), above.
|
||||
*/
|
||||
bool loadYaml( const QString& filename );
|
||||
|
||||
/** @brief Make a complete copy of the data
|
||||
*
|
||||
* Provides a snapshot of the data at a given time.
|
||||
*/
|
||||
QVariantMap data() const { return m; }
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Does the store contain the given key?
|
||||
*
|
||||
* This can distinguish an explicitly-inserted QVariant() from
|
||||
* a no-value-exists QVariant. See value() for details.
|
||||
*/
|
||||
bool contains( const QString& key ) const;
|
||||
/** @brief The number of keys in the store
|
||||
*
|
||||
* This should be unsigned, but the underlying QMap uses signed as well.
|
||||
* Equal to keys().length(), in theory.
|
||||
*/
|
||||
int count() const;
|
||||
/** @brief The keys in the store
|
||||
*
|
||||
* This makes a copy of all the keys currently in the store, which
|
||||
* could be used for iterating over all the values in the store.
|
||||
*/
|
||||
QStringList keys() const;
|
||||
/** @brief Gets a value from the store
|
||||
*
|
||||
* If a value has been previously inserted, returns that value.
|
||||
* If @p key does not exist in the store, returns a QVariant()
|
||||
* (an invalid QVariant, which boolean-converts to false). Since
|
||||
* QVariant() van also be inserted explicitly, use contains()
|
||||
* to check for the presence of a key if you need that.
|
||||
*/
|
||||
QVariant value( const QString& key ) const;
|
||||
|
||||
signals:
|
||||
/** @brief Emitted any time the store changes
|
||||
*
|
||||
* Also emitted sometimes when the store does not change, e.g.
|
||||
* when removing a non-existent key or inserting a value that
|
||||
* is already present.
|
||||
*/
|
||||
void changed();
|
||||
|
||||
private:
|
||||
class ReadLock;
|
||||
class WriteLock;
|
||||
QVariantMap m;
|
||||
mutable QMutex m_mutex;
|
||||
};
|
||||
|
||||
|
||||
/** @brief Gets a value from the store
|
||||
*
|
||||
* When @p nestedKey contains no '.' characters, equivalent
|
||||
* to `gs->value(nestedKey)`. Otherwise recursively looks up
|
||||
* the '.'-separated parts of @p nestedKey in successive sub-maps
|
||||
* of the store, returning the value in the innermost one.
|
||||
*
|
||||
* Example: `lookup(gs, "branding.name")` finds the value of the
|
||||
* 'name' key in the 'branding' submap of the store.
|
||||
*
|
||||
* Sets @p ok to @c true if a value was found. Returns the value
|
||||
* as a variant. If no value is found (e.g. the key is missing
|
||||
* or some prefix submap is missing) sets @p ok to @c false
|
||||
* and returns an invalid QVariant.
|
||||
*
|
||||
* @see GlobalStorage::value
|
||||
*/
|
||||
DLLEXPORT QVariant lookup( const GlobalStorage* gs, const QString& nestedKey, bool& ok );
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_GLOBALSTORAGE_H
|
||||
112
calamares/src/libcalamares/Job.cpp
Normal file
112
calamares/src/libcalamares/Job.cpp
Normal file
@@ -0,0 +1,112 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Job.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
JobResult::JobResult( JobResult&& rhs )
|
||||
: m_message( std::move( rhs.m_message ) )
|
||||
, m_details( std::move( rhs.m_details ) )
|
||||
, m_number( rhs.m_number )
|
||||
{
|
||||
}
|
||||
|
||||
JobResult::operator bool() const
|
||||
{
|
||||
return m_number == 0;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
JobResult::message() const
|
||||
{
|
||||
return m_message;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
JobResult::setMessage( const QString& message )
|
||||
{
|
||||
m_message = message;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
JobResult::details() const
|
||||
{
|
||||
return m_details;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
JobResult::setDetails( const QString& details )
|
||||
{
|
||||
m_details = details;
|
||||
}
|
||||
|
||||
JobResult
|
||||
JobResult::ok()
|
||||
{
|
||||
return JobResult( QString(), QString(), NoError );
|
||||
}
|
||||
|
||||
|
||||
JobResult
|
||||
JobResult::error( const QString& message, const QString& details )
|
||||
{
|
||||
return JobResult( message, details, GenericError );
|
||||
}
|
||||
|
||||
JobResult
|
||||
JobResult::internalError( const QString& message, const QString& details, int number )
|
||||
{
|
||||
return JobResult( message, details, number ? number : GenericError );
|
||||
}
|
||||
|
||||
JobResult::JobResult( const QString& message, const QString& details, int number )
|
||||
: m_message( message )
|
||||
, m_details( details )
|
||||
, m_number( number )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Job::Job( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
Job::~Job() {}
|
||||
|
||||
|
||||
int
|
||||
Job::getJobWeight() const
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
Job::prettyDescription() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
QString
|
||||
Job::prettyStatusMessage() const
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
|
||||
} // namespace Calamares
|
||||
175
calamares/src/libcalamares/Job.h
Normal file
175
calamares/src/libcalamares/Job.h
Normal file
@@ -0,0 +1,175 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_JOB_H
|
||||
#define CALAMARES_JOB_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QObject>
|
||||
#include <QSharedPointer>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
class DLLEXPORT JobResult
|
||||
{
|
||||
public:
|
||||
/** @brief Distinguish classes of errors
|
||||
*
|
||||
* All "ok result" have errorCode 0 (NoError).
|
||||
* Errors returned from job execution have values < 0.
|
||||
* Errors before job execution, or not returned by the job execution
|
||||
* itself, have values > 0.
|
||||
*/
|
||||
enum
|
||||
{
|
||||
NoError = 0,
|
||||
GenericError = -1,
|
||||
PythonUncaughtException = 1,
|
||||
InvalidConfiguration = 2,
|
||||
MissingRequirements = 3,
|
||||
};
|
||||
|
||||
// Can't copy, but you can keep a temporary
|
||||
JobResult( const JobResult& rhs ) = delete;
|
||||
JobResult( JobResult&& rhs );
|
||||
|
||||
virtual ~JobResult() {}
|
||||
|
||||
/** @brief Is this JobResult a success?
|
||||
*
|
||||
* Equivalent to errorCode() == 0, see succeeded().
|
||||
*/
|
||||
virtual operator bool() const;
|
||||
|
||||
virtual QString message() const;
|
||||
virtual void setMessage( const QString& message );
|
||||
|
||||
virtual QString details() const;
|
||||
virtual void setDetails( const QString& details );
|
||||
|
||||
int errorCode() const { return m_number; }
|
||||
/** @brief Is this JobResult a success?
|
||||
*
|
||||
* Equivalent to errorCode() == 0.
|
||||
*/
|
||||
bool succeeded() const { return this->operator bool(); }
|
||||
|
||||
/// @brief an "ok status" result
|
||||
static JobResult ok();
|
||||
/** @brief an "error" result resulting from the execution of the job
|
||||
*
|
||||
* The error code is set to GenericError.
|
||||
*/
|
||||
static JobResult error( const QString& message, const QString& details = QString() );
|
||||
/** @brief an "internal error" meaning the job itself has a problem (usually for python)
|
||||
*
|
||||
* Pass in a suitable error code; using 0 (which would normally mean "ok") instead
|
||||
* gives you a GenericError code.
|
||||
*/
|
||||
static JobResult internalError( const QString& message, const QString& details, int errorCode );
|
||||
|
||||
protected:
|
||||
explicit JobResult( const QString& message, const QString& details, int errorCode );
|
||||
|
||||
private:
|
||||
QString m_message;
|
||||
QString m_details;
|
||||
int m_number;
|
||||
};
|
||||
|
||||
class DLLEXPORT Job : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Job( QObject* parent = nullptr );
|
||||
~Job() override;
|
||||
|
||||
/** @brief The job's (relative) weight.
|
||||
*
|
||||
* The default implementation returns 1, which gives all jobs
|
||||
* the same weight, so they advance the overall progress the same
|
||||
* amount. This is nonsense, since some jobs take much longer than
|
||||
* others; it's up to the individual jobs to say something about
|
||||
* how much work is (relatively) done.
|
||||
*
|
||||
* Since jobs are caused by **modules** from the sequence, the
|
||||
* overall weight of the module is taken into account: its weight
|
||||
* is divided among the jobs based on each jobs relative weight.
|
||||
* This can be used in a module that runs a bunch of jobs to indicate
|
||||
* which of the jobs is "heavy" and which is not.
|
||||
*/
|
||||
virtual int getJobWeight() const;
|
||||
|
||||
/** @brief The human-readable name of this job
|
||||
*
|
||||
* This should be a very short statement of what the job does.
|
||||
* For status and state information, see prettyStatusMessage().
|
||||
*
|
||||
* The job's name may be similar to the status message, but this is
|
||||
* a name, and should not be an active verb phrase. The translation
|
||||
* should use context @c \@label .
|
||||
*
|
||||
* The name of the job is used as a **fallback** when the status
|
||||
* or descriptions are empty. If a job has no implementation of
|
||||
* those methods, it is OK to use other contexts, but it may look
|
||||
* strange in some places in the UI.
|
||||
*/
|
||||
virtual QString prettyName() const = 0;
|
||||
|
||||
/** @brief a longer human-readable description of what the job will do
|
||||
*
|
||||
* This **may** be used by view steps to fill in the summary
|
||||
* messages for the summary page; at present, only the *partition*
|
||||
* module does so.
|
||||
*
|
||||
* The default implementation returns an empty string.
|
||||
*
|
||||
* The translation should use context @c \@title .
|
||||
*/
|
||||
virtual QString prettyDescription() const;
|
||||
|
||||
/** @brief A human-readable status for progress reporting
|
||||
*
|
||||
* This is called from the JobQueue when progress is made, and should
|
||||
* return a not-too-long description of the job's status. This
|
||||
* is made visible in the progress bar of the execution view step.
|
||||
*
|
||||
* The job's status should say **what** the job is doing. It should be in
|
||||
* present active tense. Typically the translation uses tr() context
|
||||
* @c \@status . See prettyName() for examples.
|
||||
*/
|
||||
virtual QString prettyStatusMessage() const;
|
||||
|
||||
virtual JobResult exec() = 0;
|
||||
|
||||
bool isEmergency() const { return m_emergency; }
|
||||
void setEmergency( bool e ) { m_emergency = e; }
|
||||
|
||||
signals:
|
||||
/** @brief Signals that the job has made progress
|
||||
*
|
||||
* The parameter @p percent should be between 0 (0%) and 1 (100%).
|
||||
* Values outside of this range will be clamped.
|
||||
*/
|
||||
void progress( qreal percent );
|
||||
|
||||
private:
|
||||
bool m_emergency = false;
|
||||
};
|
||||
|
||||
using job_ptr = QSharedPointer< Job >;
|
||||
using JobList = QList< job_ptr >;
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_JOB_H
|
||||
33
calamares/src/libcalamares/JobExample.cpp
Normal file
33
calamares/src/libcalamares/JobExample.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
#include "JobExample.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
QString
|
||||
NamedJob::prettyName() const
|
||||
{
|
||||
return tr( "Example job (%1)" ).arg( m_name );
|
||||
}
|
||||
|
||||
JobResult
|
||||
GoodJob::exec()
|
||||
{
|
||||
return JobResult::ok();
|
||||
}
|
||||
|
||||
JobResult
|
||||
FailJob::exec()
|
||||
{
|
||||
return JobResult::error( tr( "Job failed (%1)" ).arg( m_name ),
|
||||
tr( "Programmed job failure was explicitly requested." ) );
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
69
calamares/src/libcalamares/JobExample.h
Normal file
69
calamares/src/libcalamares/JobExample.h
Normal file
@@ -0,0 +1,69 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_JOB_EXAMPLE_H
|
||||
#define CALAMARES_JOB_EXAMPLE_H
|
||||
|
||||
#include "Job.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
/** @brief A Job with a name
|
||||
*
|
||||
* This includes a default implementation of prettyName(),
|
||||
* but is only used as a base for FailJob and GoodJob,
|
||||
* which are support / bogus classes.
|
||||
*/
|
||||
class DLLEXPORT NamedJob : public Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit NamedJob( const QString& name, QObject* parent = nullptr )
|
||||
: Job( parent )
|
||||
, m_name( name )
|
||||
{
|
||||
}
|
||||
|
||||
virtual QString prettyName() const override;
|
||||
|
||||
protected:
|
||||
const QString m_name;
|
||||
};
|
||||
|
||||
/// @brief Job does nothing, always succeeds
|
||||
class DLLEXPORT GoodJob : public NamedJob
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GoodJob( const QString& name, QObject* parent = nullptr )
|
||||
: NamedJob( name, parent )
|
||||
{
|
||||
}
|
||||
|
||||
virtual JobResult exec() override;
|
||||
};
|
||||
|
||||
|
||||
/// @brief Job does nothing, always fails
|
||||
class DLLEXPORT FailJob : public NamedJob
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit FailJob( const QString& name, QObject* parent = nullptr )
|
||||
: NamedJob( name, parent )
|
||||
{
|
||||
}
|
||||
|
||||
virtual JobResult exec() override;
|
||||
};
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_JOB_EXAMPLE_H
|
||||
618
calamares/src/libcalamares/JobQueue.cpp
Normal file
618
calamares/src/libcalamares/JobQueue.cpp
Normal file
@@ -0,0 +1,618 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "JobQueue.h"
|
||||
|
||||
#include "CalamaresConfig.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "Job.h"
|
||||
#include "compat/Mutex.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusConnectionInterface>
|
||||
#include <QDBusMessage>
|
||||
#include <QDBusPendingCall>
|
||||
#include <QDBusPendingReply>
|
||||
#include <QMutex>
|
||||
#include <QThread>
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <unistd.h> // for close()
|
||||
|
||||
namespace
|
||||
{
|
||||
// This power-management code is largely cribbed from KDE Discover,
|
||||
// https://invent.kde.org/plasma/discover/-/blob/master/discover/PowerManagementInterface.cpp
|
||||
//
|
||||
// Upstream license text says:
|
||||
//
|
||||
// SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr>
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
|
||||
|
||||
/** @brief Class to manage sleep / suspend on inactivity (via fd.o Inhibit service on the user bus)
|
||||
*
|
||||
* Create an object of this class on the heap. Call inhibitSleep()
|
||||
* to (try to) stop system sleep / suspend. Call uninhibitSleep()
|
||||
* when the object is no longer needed. The object self-deletes
|
||||
* after uninhibitSleep() completes.
|
||||
*/
|
||||
class PowerManagementInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
PowerManagementInterface( QObject* parent = nullptr );
|
||||
~PowerManagementInterface() override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void inhibitSleep();
|
||||
void uninhibitSleep();
|
||||
|
||||
private Q_SLOTS:
|
||||
void hostSleepInhibitChanged();
|
||||
void inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher );
|
||||
void uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher );
|
||||
|
||||
private:
|
||||
uint m_inhibitSleepCookie = 0;
|
||||
bool m_inhibitedSleep = false;
|
||||
};
|
||||
|
||||
PowerManagementInterface::PowerManagementInterface( QObject* parent )
|
||||
: QObject( parent )
|
||||
{
|
||||
auto sessionBus = QDBusConnection::sessionBus();
|
||||
|
||||
sessionBus.connect( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ),
|
||||
QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "HasInhibitChanged" ),
|
||||
this,
|
||||
SLOT( hostSleepInhibitChanged() ) );
|
||||
}
|
||||
|
||||
PowerManagementInterface::~PowerManagementInterface() = default;
|
||||
|
||||
void
|
||||
PowerManagementInterface::hostSleepInhibitChanged()
|
||||
{
|
||||
// We don't actually care
|
||||
}
|
||||
|
||||
void
|
||||
PowerManagementInterface::inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher )
|
||||
{
|
||||
QDBusPendingReply< uint > reply = *aWatcher;
|
||||
if ( reply.isError() )
|
||||
{
|
||||
cError() << "Could not inhibit sleep:" << reply.error();
|
||||
// m_inhibitedSleep = false; // unchanged
|
||||
}
|
||||
else
|
||||
{
|
||||
m_inhibitSleepCookie = reply.argumentAt< 0 >();
|
||||
m_inhibitedSleep = true;
|
||||
cDebug() << "Sleep inhibited, cookie" << m_inhibitSleepCookie;
|
||||
}
|
||||
aWatcher->deleteLater();
|
||||
}
|
||||
|
||||
void
|
||||
PowerManagementInterface::uninhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher )
|
||||
{
|
||||
QDBusPendingReply<> reply = *aWatcher;
|
||||
if ( reply.isError() )
|
||||
{
|
||||
cError() << "Could not uninhibit sleep:" << reply.error();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_inhibitedSleep = false;
|
||||
m_inhibitSleepCookie = 0;
|
||||
cDebug() << "Sleep uninhibited.";
|
||||
}
|
||||
aWatcher->deleteLater();
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
void
|
||||
PowerManagementInterface::inhibitSleep()
|
||||
{
|
||||
if ( m_inhibitedSleep )
|
||||
{
|
||||
cDebug() << "Sleep is already inhibited.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto sessionBus = QDBusConnection::sessionBus();
|
||||
auto inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ),
|
||||
QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "Inhibit" ) );
|
||||
inhibitCall.setArguments( { { tr( "Calamares" ) }, { tr( "Installation in progress", "@status" ) } } );
|
||||
|
||||
auto asyncReply = sessionBus.asyncCall( inhibitCall );
|
||||
auto* replyWatcher = new QDBusPendingCallWatcher( asyncReply, this );
|
||||
QObject::connect(
|
||||
replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::inhibitDBusCallFinished );
|
||||
}
|
||||
|
||||
void
|
||||
PowerManagementInterface::uninhibitSleep()
|
||||
{
|
||||
if ( !m_inhibitedSleep )
|
||||
{
|
||||
cDebug() << "Sleep was never inhibited.";
|
||||
this->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
auto sessionBus = QDBusConnection::sessionBus();
|
||||
auto uninhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "/org/freedesktop/PowerManagement/Inhibit" ),
|
||||
QStringLiteral( "org.freedesktop.PowerManagement.Inhibit" ),
|
||||
QStringLiteral( "UnInhibit" ) );
|
||||
uninhibitCall.setArguments( { { m_inhibitSleepCookie } } );
|
||||
|
||||
auto asyncReply = sessionBus.asyncCall( uninhibitCall );
|
||||
auto replyWatcher = new QDBusPendingCallWatcher( asyncReply, this );
|
||||
QObject::connect(
|
||||
replyWatcher, &QDBusPendingCallWatcher::finished, this, &PowerManagementInterface::uninhibitDBusCallFinished );
|
||||
}
|
||||
|
||||
|
||||
/** @brief Class to manage sleep / suspend on inactivity (via logind/CK2 service on the system bus)
|
||||
*
|
||||
* Create an object of this class on the heap. Call inhibitSleep()
|
||||
* to (try to) stop system sleep / suspend. Call uninhibitSleep()
|
||||
* when the object is no longer needed. The object self-deletes
|
||||
* after uninhibitSleep() completes.
|
||||
*/
|
||||
class LoginManagerInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static LoginManagerInterface* makeForRegisteredService( QObject* parent = nullptr );
|
||||
~LoginManagerInterface() override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void inhibitSleep();
|
||||
void uninhibitSleep();
|
||||
|
||||
private Q_SLOTS:
|
||||
void inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher );
|
||||
|
||||
private:
|
||||
enum class Service
|
||||
{
|
||||
Logind,
|
||||
ConsoleKit,
|
||||
};
|
||||
LoginManagerInterface( Service service, QObject* parent = nullptr );
|
||||
|
||||
int m_inhibitFd = -1;
|
||||
Service m_service;
|
||||
};
|
||||
|
||||
LoginManagerInterface*
|
||||
LoginManagerInterface::makeForRegisteredService( QObject* parent )
|
||||
{
|
||||
if ( QDBusConnection::systemBus().interface()->isServiceRegistered( QStringLiteral( "org.freedesktop.login1" ) ) )
|
||||
{
|
||||
return new LoginManagerInterface( Service::Logind, parent );
|
||||
}
|
||||
else if ( QDBusConnection::systemBus().interface()->isServiceRegistered(
|
||||
QStringLiteral( "org.freedesktop.ConsoleKit" ) ) )
|
||||
{
|
||||
return new LoginManagerInterface( Service::ConsoleKit, parent );
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LoginManagerInterface::LoginManagerInterface( Service service, QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_service( service )
|
||||
{
|
||||
}
|
||||
|
||||
LoginManagerInterface::~LoginManagerInterface() = default;
|
||||
|
||||
void
|
||||
LoginManagerInterface::inhibitDBusCallFinished( QDBusPendingCallWatcher* aWatcher )
|
||||
{
|
||||
QDBusPendingReply< uint > reply = *aWatcher;
|
||||
if ( reply.isError() )
|
||||
{
|
||||
cError() << "Could not inhibit sleep:" << reply.error();
|
||||
// m_inhibitFd = -1; // unchanged
|
||||
}
|
||||
else
|
||||
{
|
||||
m_inhibitFd = reply.argumentAt< 0 >();
|
||||
cDebug() << "Sleep inhibited, file descriptor" << m_inhibitFd;
|
||||
}
|
||||
aWatcher->deleteLater();
|
||||
}
|
||||
|
||||
void
|
||||
LoginManagerInterface::inhibitSleep()
|
||||
{
|
||||
if ( m_inhibitFd == -1 )
|
||||
{
|
||||
cDebug() << "Sleep is already inhibited.";
|
||||
return;
|
||||
}
|
||||
|
||||
auto systemBus = QDBusConnection::systemBus();
|
||||
QDBusMessage inhibitCall;
|
||||
|
||||
if ( m_service == Service::Logind )
|
||||
{
|
||||
inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.login1" ),
|
||||
QStringLiteral( "/org/freedesktop/login1" ),
|
||||
QStringLiteral( "org.freedesktop.login1.Manager" ),
|
||||
QStringLiteral( "Inhibit" ) );
|
||||
}
|
||||
else if ( m_service == Service::ConsoleKit )
|
||||
{
|
||||
inhibitCall = QDBusMessage::createMethodCall( QStringLiteral( "org.freedesktop.ConsoleKit" ),
|
||||
QStringLiteral( "/org/freedesktop/ConsoleKit/Manager" ),
|
||||
QStringLiteral( "org.freedesktop.ConsoleKit.Manager" ),
|
||||
QStringLiteral( "Inhibit" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
cError() << "System sleep interface not supported.";
|
||||
return;
|
||||
}
|
||||
|
||||
inhibitCall.setArguments(
|
||||
{ { "sleep:shutdown" }, { tr( "Calamares" ) }, { tr( "Installation in progress", "@status" ) }, { "block" } } );
|
||||
|
||||
auto asyncReply = systemBus.asyncCall( inhibitCall );
|
||||
auto* replyWatcher = new QDBusPendingCallWatcher( asyncReply, this );
|
||||
QObject::connect(
|
||||
replyWatcher, &QDBusPendingCallWatcher::finished, this, &LoginManagerInterface::inhibitDBusCallFinished );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
LoginManagerInterface::uninhibitSleep()
|
||||
{
|
||||
if ( m_inhibitFd == -1 )
|
||||
{
|
||||
cDebug() << "Sleep was never inhibited.";
|
||||
this->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
if ( close( m_inhibitFd ) != 0 )
|
||||
{
|
||||
cError() << "Could not uninhibit sleep:" << strerror( errno );
|
||||
}
|
||||
this->deleteLater();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
SleepInhibitor::SleepInhibitor()
|
||||
{
|
||||
// Create a LoginManagerInterface object with intentionally no parent
|
||||
// so it is not destroyed along with this. Instead, when this
|
||||
// is destroyed, **start** the uninhibit-sleep call which will (later)
|
||||
// destroy the LoginManagerInterface object.
|
||||
if ( auto* l = LoginManagerInterface::makeForRegisteredService( nullptr ) )
|
||||
{
|
||||
l->inhibitSleep();
|
||||
connect( this, &QObject::destroyed, l, &LoginManagerInterface::uninhibitSleep );
|
||||
}
|
||||
// If no login manager service was present, try the same thing
|
||||
// with PowerManagementInterface.
|
||||
else
|
||||
{
|
||||
auto* p = new PowerManagementInterface( nullptr );
|
||||
p->inhibitSleep();
|
||||
connect( this, &QObject::destroyed, p, &PowerManagementInterface::uninhibitSleep );
|
||||
}
|
||||
}
|
||||
|
||||
SleepInhibitor::~SleepInhibitor() = default;
|
||||
|
||||
struct WeightedJob
|
||||
{
|
||||
/** @brief Cumulative weight **before** this job starts
|
||||
*
|
||||
* This is calculated as jobs come in.
|
||||
*/
|
||||
qreal cumulative = 0.0;
|
||||
/** @brief Weight of the job within the module's jobs
|
||||
*
|
||||
* When a list of jobs is added from a particular module,
|
||||
* the jobs are weighted relative to that module's overall weight
|
||||
* **and** the other jobs in the list, so that each job
|
||||
* gets its share:
|
||||
* ( job-weight / total-job-weight ) * module-weight
|
||||
*/
|
||||
qreal weight = 0.0;
|
||||
|
||||
job_ptr job;
|
||||
};
|
||||
using WeightedJobList = QList< WeightedJob >;
|
||||
|
||||
class JobThread : public QThread
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
JobThread( JobQueue* queue )
|
||||
: QThread( queue )
|
||||
, m_queue( queue )
|
||||
, m_jobIndex( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
~JobThread() override;
|
||||
|
||||
void finalize()
|
||||
{
|
||||
Q_ASSERT( m_runningJobs->isEmpty() );
|
||||
Calamares::MutexLocker qlock( &m_enqueMutex );
|
||||
Calamares::MutexLocker rlock( &m_runMutex );
|
||||
std::swap( m_runningJobs, m_queuedJobs );
|
||||
m_overallQueueWeight
|
||||
= m_runningJobs->isEmpty() ? 0.0 : ( m_runningJobs->last().cumulative + m_runningJobs->last().weight );
|
||||
if ( m_overallQueueWeight < 1 )
|
||||
{
|
||||
m_overallQueueWeight = 1.0;
|
||||
}
|
||||
|
||||
cDebug() << "There are" << m_runningJobs->count() << "jobs, total weight" << m_overallQueueWeight;
|
||||
int c = 0;
|
||||
for ( const auto& j : *m_runningJobs )
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "Job" << ( c + 1 ) << j.job->prettyName() << "+wt" << j.weight << "tot.wt"
|
||||
<< ( j.cumulative + j.weight );
|
||||
c++;
|
||||
}
|
||||
}
|
||||
|
||||
void enqueue( int moduleWeight, const JobList& jobs )
|
||||
{
|
||||
Calamares::MutexLocker qlock( &m_enqueMutex );
|
||||
|
||||
qreal cumulative
|
||||
= m_queuedJobs->isEmpty() ? 0.0 : ( m_queuedJobs->last().cumulative + m_queuedJobs->last().weight );
|
||||
|
||||
qreal totalJobWeight
|
||||
= std::accumulate( jobs.cbegin(),
|
||||
jobs.cend(),
|
||||
qreal( 0.0 ),
|
||||
[]( qreal total, const job_ptr& j ) { return total + j->getJobWeight(); } );
|
||||
if ( totalJobWeight < 1 )
|
||||
{
|
||||
totalJobWeight = 1.0;
|
||||
}
|
||||
|
||||
for ( const auto& j : jobs )
|
||||
{
|
||||
qreal jobContribution = ( j->getJobWeight() / totalJobWeight ) * moduleWeight;
|
||||
m_queuedJobs->append( WeightedJob { cumulative, jobContribution, j } );
|
||||
cumulative += jobContribution;
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
Calamares::MutexLocker rlock( &m_runMutex );
|
||||
bool failureEncountered = false;
|
||||
QString message; ///< Filled in with errors
|
||||
QString details;
|
||||
|
||||
Logger::Once o;
|
||||
m_jobIndex = 0;
|
||||
for ( const auto& jobitem : *m_runningJobs )
|
||||
{
|
||||
if ( failureEncountered && !jobitem.job->isEmergency() )
|
||||
{
|
||||
cDebug() << o << "Skipping non-emergency job" << jobitem.job->prettyName();
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << o << "Starting" << ( failureEncountered ? "EMERGENCY JOB" : "job" )
|
||||
<< jobitem.job->prettyName() << '(' << ( m_jobIndex + 1 ) << '/' << m_runningJobs->count()
|
||||
<< ')';
|
||||
o.refresh(); // So next time it shows the function header again
|
||||
emitProgress( 0.0 ); // 0% for *this job*
|
||||
connect( jobitem.job.data(), &Job::progress, this, &JobThread::emitProgress );
|
||||
auto result = jobitem.job->exec();
|
||||
if ( !failureEncountered && !result )
|
||||
{
|
||||
// so this is the first failure
|
||||
failureEncountered = true;
|
||||
message = result.message();
|
||||
details = result.details();
|
||||
}
|
||||
QThread::msleep( 16 ); // Very brief rest before reporting the job as complete
|
||||
emitProgress( 1.0 ); // 100% for *this job*
|
||||
}
|
||||
m_jobIndex++;
|
||||
}
|
||||
if ( failureEncountered )
|
||||
{
|
||||
QMetaObject::invokeMethod(
|
||||
m_queue, "failed", Qt::QueuedConnection, Q_ARG( QString, message ), Q_ARG( QString, details ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
emitProgress( 1.0 );
|
||||
}
|
||||
m_runningJobs->clear();
|
||||
QMetaObject::invokeMethod( m_queue, "finish", Qt::QueuedConnection );
|
||||
}
|
||||
|
||||
/** @brief The names of the queued (not running!) jobs.
|
||||
*/
|
||||
QStringList queuedJobs() const
|
||||
{
|
||||
Calamares::MutexLocker qlock( &m_enqueMutex );
|
||||
QStringList l;
|
||||
l.reserve( m_queuedJobs->count() );
|
||||
for ( const auto& j : *m_queuedJobs )
|
||||
{
|
||||
l << j.job->prettyName();
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
private:
|
||||
/* This is called **only** from run(), while m_runMutex is
|
||||
* already locked, so we can use the m_runningJobs member safely.
|
||||
*/
|
||||
void emitProgress( qreal percentage ) const
|
||||
{
|
||||
percentage = qBound( 0.0, percentage, 1.0 );
|
||||
|
||||
QString message;
|
||||
qreal progress = 0.0;
|
||||
if ( m_jobIndex < m_runningJobs->count() )
|
||||
{
|
||||
const auto& jobitem = m_runningJobs->at( m_jobIndex );
|
||||
progress = ( jobitem.cumulative + jobitem.weight * percentage ) / m_overallQueueWeight;
|
||||
message = jobitem.job->prettyStatusMessage();
|
||||
// In progress reports at the start of a job (e.g. when the queue
|
||||
// starts the job, or if the job itself reports 0.0) be more
|
||||
// accepting in what gets reported: jobs with no status fall
|
||||
// back to description and name, whichever is non-empty.
|
||||
//
|
||||
// Later calls (e.g. when percentage > 0) use the status unchanged.
|
||||
// It may be empty, but the ExecutionViewStep knows about empty
|
||||
// status messages and does not update the text in that case.
|
||||
//
|
||||
// This means that a Job can implement just prettyName() and get
|
||||
// a reasonable "status" message which will update only once.
|
||||
if ( percentage == 0.0 && message.isEmpty() )
|
||||
{
|
||||
message = jobitem.job->prettyDescription();
|
||||
if ( message.isEmpty() )
|
||||
{
|
||||
message = jobitem.job->prettyName();
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
progress = 1.0;
|
||||
message = tr( "Done" );
|
||||
}
|
||||
QMetaObject::invokeMethod(
|
||||
m_queue, "progress", Qt::QueuedConnection, Q_ARG( qreal, progress ), Q_ARG( QString, message ) );
|
||||
}
|
||||
|
||||
mutable QMutex m_runMutex;
|
||||
mutable QMutex m_enqueMutex;
|
||||
|
||||
std::unique_ptr< WeightedJobList > m_runningJobs = std::make_unique< WeightedJobList >();
|
||||
std::unique_ptr< WeightedJobList > m_queuedJobs = std::make_unique< WeightedJobList >();
|
||||
|
||||
JobQueue* m_queue;
|
||||
int m_jobIndex = 0; ///< Index into m_runningJobs
|
||||
qreal m_overallQueueWeight = 0.0; ///< cumulation when **all** the jobs are done
|
||||
};
|
||||
|
||||
JobThread::~JobThread() {}
|
||||
|
||||
|
||||
JobQueue* JobQueue::s_instance = nullptr;
|
||||
|
||||
JobQueue*
|
||||
JobQueue::instance()
|
||||
{
|
||||
if ( !s_instance )
|
||||
{
|
||||
cWarning() << "Getting nullptr JobQueue instance.";
|
||||
}
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
|
||||
JobQueue::JobQueue( QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_thread( new JobThread( this ) )
|
||||
, m_storage( new GlobalStorage( this ) )
|
||||
{
|
||||
Q_ASSERT( !s_instance );
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
|
||||
JobQueue::~JobQueue()
|
||||
{
|
||||
if ( m_thread->isRunning() )
|
||||
{
|
||||
m_thread->terminate();
|
||||
if ( !m_thread->wait( 300 ) )
|
||||
{
|
||||
cError() << "Could not terminate job thread (expect a crash now).";
|
||||
}
|
||||
delete m_thread;
|
||||
}
|
||||
|
||||
delete m_storage;
|
||||
s_instance = nullptr;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
JobQueue::start()
|
||||
{
|
||||
Q_ASSERT( !m_thread->isRunning() );
|
||||
m_thread->finalize();
|
||||
m_finished = false;
|
||||
m_thread->start();
|
||||
|
||||
auto* inhibitor = new PowerManagementInterface( this );
|
||||
inhibitor->inhibitSleep();
|
||||
connect( this, &JobQueue::finished, inhibitor, &PowerManagementInterface::uninhibitSleep );
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
JobQueue::enqueue( int moduleWeight, const JobList& jobs )
|
||||
{
|
||||
Q_ASSERT( !m_thread->isRunning() );
|
||||
m_thread->enqueue( moduleWeight, jobs );
|
||||
emit queueChanged( m_thread->queuedJobs() );
|
||||
}
|
||||
|
||||
void
|
||||
JobQueue::finish()
|
||||
{
|
||||
m_finished = true;
|
||||
emit finished();
|
||||
emit queueChanged( m_thread->queuedJobs() );
|
||||
}
|
||||
|
||||
GlobalStorage*
|
||||
JobQueue::globalStorage() const
|
||||
{
|
||||
return m_storage;
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
||||
#include "JobQueue.moc"
|
||||
122
calamares/src/libcalamares/JobQueue.h
Normal file
122
calamares/src/libcalamares/JobQueue.h
Normal file
@@ -0,0 +1,122 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_JOBQUEUE_H
|
||||
#define CALAMARES_JOBQUEUE_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Job.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
class GlobalStorage;
|
||||
class JobThread;
|
||||
|
||||
///@brief RAII class to suppress sleep / suspend during its lifetime
|
||||
class DLLEXPORT SleepInhibitor : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SleepInhibitor();
|
||||
~SleepInhibitor() override;
|
||||
};
|
||||
|
||||
class DLLEXPORT JobQueue : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit JobQueue( QObject* parent = nullptr );
|
||||
~JobQueue() override;
|
||||
|
||||
/** @brief Returns the most-recently-created instance.
|
||||
*
|
||||
* It is possible for instance() to be @c nullptr, since you must
|
||||
* call the constructor explicitly first.
|
||||
*/
|
||||
static JobQueue* instance();
|
||||
/* @brief Returns the GlobalStorage object for the instance.
|
||||
*
|
||||
* It is possible for instanceGlobalStorage() to be @c nullptr,
|
||||
* since there might not be an instance to begin with.
|
||||
*/
|
||||
static GlobalStorage* instanceGlobalStorage()
|
||||
{
|
||||
auto* jq = instance();
|
||||
return jq ? jq->globalStorage() : nullptr;
|
||||
}
|
||||
|
||||
GlobalStorage* globalStorage() const;
|
||||
|
||||
/** @brief Queues up jobs from a single module source
|
||||
*
|
||||
* The total weight of the jobs is spread out to fill the weight
|
||||
* of the module.
|
||||
*/
|
||||
void enqueue( int moduleWeight, const JobList& jobs );
|
||||
/** @brief Starts all the jobs that are enqueued.
|
||||
*
|
||||
* After this, isRunning() returns @c true until
|
||||
* finished() is emitted.
|
||||
*/
|
||||
void start();
|
||||
|
||||
bool isRunning() const { return !m_finished; }
|
||||
|
||||
signals:
|
||||
/** @brief Report progress of the whole queue, with a status message
|
||||
*
|
||||
* The @p percent is a value between 0.0 and 1.0 (100%) of the
|
||||
* overall queue progress (not of the current job), while
|
||||
* @p prettyName is the status message from the job -- often
|
||||
* just the name of the job, but some jobs include more information.
|
||||
*/
|
||||
void progress( qreal percent, const QString& prettyName );
|
||||
/** @brief Indicate that the queue is empty, after calling start()
|
||||
*
|
||||
* Emitted when the queue empties. The queue may also emit
|
||||
* failed(), if something went wrong, but finished() is always
|
||||
* the last one.
|
||||
*/
|
||||
void finished();
|
||||
/** @brief A job in the queue failed.
|
||||
*
|
||||
* Contains the (already-translated) text from the job describing
|
||||
* the failure.
|
||||
*/
|
||||
void failed( const QString& message, const QString& details );
|
||||
|
||||
/** @brief Reports the names of jobs in the queue.
|
||||
*
|
||||
* When jobs are added via enqueue(), or when the queue otherwise
|
||||
* changes, the **names** of the jobs are reported. This is
|
||||
* primarily for debugging purposes.
|
||||
*/
|
||||
void queueChanged( const QStringList& jobNames );
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Implementation detail
|
||||
*
|
||||
* This is a private implementation detail for the job thread,
|
||||
* which should not be called by other core.
|
||||
*/
|
||||
void finish();
|
||||
|
||||
private:
|
||||
static JobQueue* s_instance;
|
||||
|
||||
JobThread* m_thread;
|
||||
GlobalStorage* m_storage;
|
||||
bool m_finished = true; ///< Initially, not running
|
||||
};
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_JOBQUEUE_H
|
||||
65
calamares/src/libcalamares/ProcessJob.cpp
Normal file
65
calamares/src/libcalamares/ProcessJob.cpp
Normal file
@@ -0,0 +1,65 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ProcessJob.h"
|
||||
|
||||
#include "utils/CommandList.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QDir>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
ProcessJob::ProcessJob( const QString& command,
|
||||
const QString& workingPath,
|
||||
bool runInChroot,
|
||||
std::chrono::seconds secondsTimeout,
|
||||
QObject* parent )
|
||||
: Job( parent )
|
||||
, m_command( command )
|
||||
, m_workingPath( workingPath )
|
||||
, m_runInChroot( runInChroot )
|
||||
, m_timeoutSec( secondsTimeout )
|
||||
{
|
||||
}
|
||||
|
||||
ProcessJob::~ProcessJob() = default;
|
||||
|
||||
QString
|
||||
ProcessJob::prettyName() const
|
||||
{
|
||||
return ( m_runInChroot ? QStringLiteral( "Run command '%1' in target system" )
|
||||
: QStringLiteral( "Run command '%1'" ) )
|
||||
.arg( m_command );
|
||||
}
|
||||
|
||||
QString
|
||||
ProcessJob::prettyStatusMessage() const
|
||||
{
|
||||
if ( m_runInChroot )
|
||||
{
|
||||
return tr( "Running command %1 in target system…", "@status" ).arg( m_command );
|
||||
}
|
||||
else
|
||||
{
|
||||
return tr( "Running command %1…", "@status" ).arg( m_command );
|
||||
}
|
||||
}
|
||||
|
||||
JobResult
|
||||
ProcessJob::exec()
|
||||
{
|
||||
Calamares::CommandList l( m_runInChroot, m_timeoutSec );
|
||||
l.push_back( Calamares::CommandLine { m_command } );
|
||||
return l.run();
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
46
calamares/src/libcalamares/ProcessJob.h
Normal file
46
calamares/src/libcalamares/ProcessJob.h
Normal file
@@ -0,0 +1,46 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_PROCESSJOB_H
|
||||
#define CALAMARES_PROCESSJOB_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Job.h"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
class DLLEXPORT ProcessJob : public Job
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ProcessJob( const QString& command,
|
||||
const QString& workingPath,
|
||||
bool runInChroot = false,
|
||||
std::chrono::seconds secondsTimeout = std::chrono::seconds( 30 ),
|
||||
QObject* parent = nullptr );
|
||||
~ProcessJob() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
QString prettyStatusMessage() const override;
|
||||
JobResult exec() override;
|
||||
|
||||
private:
|
||||
QString m_command;
|
||||
QString m_workingPath;
|
||||
bool m_runInChroot;
|
||||
std::chrono::seconds m_timeoutSec;
|
||||
};
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // CALAMARES_PROCESSJOB_H
|
||||
483
calamares/src/libcalamares/Settings.cpp
Normal file
483
calamares/src/libcalamares/Settings.cpp
Normal file
@@ -0,0 +1,483 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Gabriel Craciunescu <crazy@frugalware.org>
|
||||
* SPDX-FileCopyrightText: 2019 Dominic Hayes <ferenosdev@outlook.com>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Settings.h"
|
||||
|
||||
#include "CalamaresConfig.h"
|
||||
#include "compat/Variant.h"
|
||||
#include "utils/Dirs.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QPair>
|
||||
|
||||
static bool
|
||||
hasValue( const YAML::Node& v )
|
||||
{
|
||||
return v.IsDefined() && !v.IsNull();
|
||||
}
|
||||
|
||||
/** @brief Helper function to grab a QString out of the config, and to warn if not present. */
|
||||
static QString
|
||||
requireString( const ::YAML::Node& config, const char* key )
|
||||
{
|
||||
auto v = config[ key ];
|
||||
if ( hasValue( v ) )
|
||||
{
|
||||
return QString::fromStdString( v.as< std::string >() );
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << Logger::SubEntry << "Required settings.conf key" << key << "is missing.";
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Helper function to grab a bool out of the config, and to warn if not present. */
|
||||
static bool
|
||||
requireBool( const ::YAML::Node& config, const char* key, bool d )
|
||||
{
|
||||
auto v = config[ key ];
|
||||
if ( hasValue( v ) )
|
||||
{
|
||||
return v.as< bool >();
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << Logger::SubEntry << "Required settings.conf key" << key << "is missing.";
|
||||
return d;
|
||||
}
|
||||
}
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
InstanceDescription::InstanceDescription( const Calamares::ModuleSystem::InstanceKey& key )
|
||||
: m_instanceKey( key )
|
||||
, m_weight( -1 )
|
||||
{
|
||||
if ( !isValid() )
|
||||
{
|
||||
m_weight = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_configFileName = key.module() + QStringLiteral( ".conf" );
|
||||
}
|
||||
}
|
||||
|
||||
InstanceDescription
|
||||
InstanceDescription::fromSettings( const QVariantMap& m )
|
||||
{
|
||||
InstanceDescription r(
|
||||
Calamares::ModuleSystem::InstanceKey( m.value( "module" ).toString(), m.value( "id" ).toString() ) );
|
||||
if ( r.isValid() )
|
||||
{
|
||||
if ( m.value( "weight" ).isValid() )
|
||||
{
|
||||
int w = qBound( 1, m.value( "weight" ).toInt(), 100 );
|
||||
r.m_weight = w;
|
||||
}
|
||||
|
||||
QString c = m.value( "config" ).toString();
|
||||
if ( !c.isEmpty() )
|
||||
{
|
||||
r.m_configFileName = c;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
Settings* Settings::s_instance = nullptr;
|
||||
|
||||
Settings*
|
||||
Settings::instance()
|
||||
{
|
||||
if ( !s_instance )
|
||||
{
|
||||
cWarning() << "Getting nullptr Settings instance.";
|
||||
}
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
static void
|
||||
interpretModulesSearch( const bool debugMode, const QStringList& rawPaths, QStringList& output )
|
||||
{
|
||||
for ( const auto& path : rawPaths )
|
||||
{
|
||||
if ( path == "local" )
|
||||
{
|
||||
// If we're running in debug mode, we assume we might also be
|
||||
// running from the build dir, so we add a maximum priority
|
||||
// module search path in the build dir.
|
||||
if ( debugMode )
|
||||
{
|
||||
QString buildDirModules
|
||||
= QDir::current().absolutePath() + QDir::separator() + "src" + QDir::separator() + "modules";
|
||||
if ( QDir( buildDirModules ).exists() )
|
||||
{
|
||||
output.append( buildDirModules );
|
||||
}
|
||||
}
|
||||
|
||||
// Install path is set in CalamaresAddPlugin.cmake
|
||||
output.append( Calamares::systemLibDir().absolutePath() + QDir::separator() + "calamares"
|
||||
+ QDir::separator() + "modules" );
|
||||
}
|
||||
else
|
||||
{
|
||||
QDir d( path );
|
||||
if ( d.exists() && d.isReadable() )
|
||||
{
|
||||
output.append( d.absolutePath() );
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << Logger::SubEntry << "module-search entry non-existent" << path;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
interpretInstances( const ::YAML::Node& node, Settings::InstanceDescriptionList& customInstances )
|
||||
{
|
||||
// Parse the custom instances section
|
||||
if ( node )
|
||||
{
|
||||
QVariant instancesV = Calamares::YAML::toVariant( node ).toList();
|
||||
if ( typeOf( instancesV ) == ListVariantType )
|
||||
{
|
||||
const auto instances = instancesV.toList();
|
||||
for ( const QVariant& instancesVListItem : instances )
|
||||
{
|
||||
if ( typeOf( instancesVListItem ) != MapVariantType )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
auto description = InstanceDescription::fromSettings( instancesVListItem.toMap() );
|
||||
if ( !description.isValid() )
|
||||
{
|
||||
cWarning() << "Invalid entry in *instances*" << instancesVListItem;
|
||||
}
|
||||
// Append it **anyway**, since this will bail out after Settings is constructed
|
||||
customInstances.append( description );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
interpretSequence( const ::YAML::Node& node, Settings::ModuleSequence& moduleSequence )
|
||||
{
|
||||
// Parse the modules sequence section
|
||||
if ( node )
|
||||
{
|
||||
QVariant sequenceV = Calamares::YAML::toVariant( node );
|
||||
if ( typeOf( sequenceV ) != ListVariantType )
|
||||
{
|
||||
throw ::YAML::Exception( ::YAML::Mark(), "sequence key does not have a list-value" );
|
||||
}
|
||||
|
||||
const auto sequence = sequenceV.toList();
|
||||
for ( const QVariant& sequenceVListItem : sequence )
|
||||
{
|
||||
if ( typeOf( sequenceVListItem ) != MapVariantType )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
QString thisActionS = sequenceVListItem.toMap().firstKey();
|
||||
ModuleSystem::Action thisAction;
|
||||
if ( thisActionS == "show" )
|
||||
{
|
||||
thisAction = ModuleSystem::Action::Show;
|
||||
}
|
||||
else if ( thisActionS == "exec" )
|
||||
{
|
||||
thisAction = ModuleSystem::Action::Exec;
|
||||
}
|
||||
else
|
||||
{
|
||||
cDebug() << "Unknown action in *sequence*" << thisActionS;
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList thisActionRoster = sequenceVListItem.toMap().value( thisActionS ).toStringList();
|
||||
Calamares::ModuleSystem::InstanceKeyList roster;
|
||||
roster.reserve( thisActionRoster.count() );
|
||||
for ( const auto& s : thisActionRoster )
|
||||
{
|
||||
auto instanceKey = Calamares::ModuleSystem::InstanceKey::fromString( s );
|
||||
if ( !instanceKey.isValid() )
|
||||
{
|
||||
cWarning() << "Invalid instance in *sequence*" << s;
|
||||
}
|
||||
roster.append( instanceKey );
|
||||
}
|
||||
moduleSequence.append( qMakePair( thisAction, roster ) );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
throw ::YAML::Exception( ::YAML::Mark(), "sequence key is missing" );
|
||||
}
|
||||
}
|
||||
|
||||
Settings::Settings( bool debugMode )
|
||||
: QObject()
|
||||
, m_debug( debugMode )
|
||||
, m_doChroot( true )
|
||||
, m_promptInstall( false )
|
||||
, m_disableCancel( false )
|
||||
, m_disableCancelDuringExec( false )
|
||||
{
|
||||
cWarning() << "Using bogus Calamares settings in"
|
||||
<< ( debugMode ? QStringLiteral( "debug" ) : QStringLiteral( "regular" ) ) << "mode";
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
Settings::Settings( const QString& settingsFilePath, bool debugMode )
|
||||
: QObject()
|
||||
, m_settingsPath( settingsFilePath )
|
||||
, m_debug( debugMode )
|
||||
, m_doChroot( true )
|
||||
, m_promptInstall( false )
|
||||
, m_disableCancel( false )
|
||||
, m_disableCancelDuringExec( false )
|
||||
{
|
||||
cDebug() << "Using Calamares settings file at" << settingsFilePath;
|
||||
QFile file( settingsFilePath );
|
||||
if ( file.exists() && file.open( QFile::ReadOnly | QFile::Text ) )
|
||||
{
|
||||
setConfiguration( file.readAll(), file.fileName() );
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "Cannot read settings file" << file.fileName();
|
||||
}
|
||||
|
||||
s_instance = this;
|
||||
}
|
||||
|
||||
bool
|
||||
Settings::isModuleEnabled( const QString& module ) const
|
||||
{
|
||||
// Iterate over the list of modules searching for a match
|
||||
for ( const auto& moduleInstance : std::as_const( m_moduleInstances ) )
|
||||
{
|
||||
if ( moduleInstance.key().module() == module )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
Settings::reconcileInstancesAndSequence()
|
||||
{
|
||||
// Since moduleFinder captures targetKey by reference, we can
|
||||
// update targetKey to change what the finder lambda looks for.
|
||||
Calamares::ModuleSystem::InstanceKey targetKey;
|
||||
auto moduleFinder = [ &targetKey ]( const InstanceDescription& d ) { return d.isValid() && d.key() == targetKey; };
|
||||
|
||||
// Check the sequence against the existing instances (which so far are only custom)
|
||||
for ( const auto& step : m_modulesSequence )
|
||||
{
|
||||
for ( const auto& instanceKey : step.second )
|
||||
{
|
||||
targetKey = instanceKey;
|
||||
const auto it = std::find_if( m_moduleInstances.constBegin(), m_moduleInstances.constEnd(), moduleFinder );
|
||||
if ( it == m_moduleInstances.constEnd() )
|
||||
{
|
||||
if ( instanceKey.isCustom() )
|
||||
{
|
||||
cWarning() << "Custom instance key" << instanceKey << "is not listed in the *instances*";
|
||||
}
|
||||
m_moduleInstances.append( InstanceDescription( instanceKey ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Settings::setConfiguration( const QByteArray& ba, const QString& explainName )
|
||||
{
|
||||
try
|
||||
{
|
||||
// Not using Calamares::YAML:: convenience methods because we **want** the exception here
|
||||
auto config = ::YAML::Load( ba.constData() );
|
||||
Q_ASSERT( config.IsMap() );
|
||||
|
||||
interpretModulesSearch(
|
||||
debugMode(), Calamares::YAML::toStringList( config[ "modules-search" ] ), m_modulesSearchPaths );
|
||||
interpretInstances( config[ "instances" ], m_moduleInstances );
|
||||
interpretSequence( config[ "sequence" ], m_modulesSequence );
|
||||
|
||||
m_brandingComponentName = requireString( config, "branding" );
|
||||
m_promptInstall = requireBool( config, "prompt-install", false );
|
||||
m_doChroot = !requireBool( config, "dont-chroot", false );
|
||||
m_isSetupMode = requireBool( config, "oem-setup", !m_doChroot );
|
||||
m_disableCancel = requireBool( config, "disable-cancel", false );
|
||||
m_disableCancelDuringExec = requireBool( config, "disable-cancel-during-exec", false );
|
||||
m_hideBackAndNextDuringExec = requireBool( config, "hide-back-and-next-during-exec", false );
|
||||
m_quitAtEnd = requireBool( config, "quit-at-end", false );
|
||||
|
||||
reconcileInstancesAndSequence();
|
||||
}
|
||||
catch ( ::YAML::Exception& e )
|
||||
{
|
||||
Calamares::YAML::explainException( e, ba, explainName );
|
||||
}
|
||||
}
|
||||
|
||||
QStringList
|
||||
Settings::modulesSearchPaths() const
|
||||
{
|
||||
return m_modulesSearchPaths;
|
||||
}
|
||||
|
||||
Settings::InstanceDescriptionList
|
||||
Settings::moduleInstances() const
|
||||
{
|
||||
return m_moduleInstances;
|
||||
}
|
||||
|
||||
Settings::ModuleSequence
|
||||
Settings::modulesSequence() const
|
||||
{
|
||||
return m_modulesSequence;
|
||||
}
|
||||
|
||||
QString
|
||||
Settings::brandingComponentName() const
|
||||
{
|
||||
return m_brandingComponentName;
|
||||
}
|
||||
|
||||
static QStringList
|
||||
settingsFileCandidates( bool assumeBuilddir )
|
||||
{
|
||||
static const char settings[] = "settings.conf";
|
||||
|
||||
QStringList settingsPaths;
|
||||
if ( Calamares::isAppDataDirOverridden() )
|
||||
{
|
||||
settingsPaths << Calamares::appDataDir().absoluteFilePath( settings );
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( assumeBuilddir )
|
||||
{
|
||||
settingsPaths << QDir::current().absoluteFilePath( settings );
|
||||
}
|
||||
if ( Calamares::haveExtraDirs() )
|
||||
{
|
||||
for ( auto s : Calamares::extraConfigDirs() )
|
||||
{
|
||||
settingsPaths << ( s + settings );
|
||||
}
|
||||
}
|
||||
settingsPaths << CMAKE_INSTALL_FULL_SYSCONFDIR "/calamares/settings.conf"; // String concat
|
||||
settingsPaths << Calamares::appDataDir().absoluteFilePath( settings );
|
||||
}
|
||||
|
||||
return settingsPaths;
|
||||
}
|
||||
|
||||
Settings*
|
||||
Settings::init( bool debugMode )
|
||||
{
|
||||
if ( s_instance )
|
||||
{
|
||||
cWarning() << "Calamares::Settings already created";
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
QStringList settingsFileCandidatesByPriority = settingsFileCandidates( debugMode );
|
||||
|
||||
QFileInfo settingsFile;
|
||||
bool found = false;
|
||||
|
||||
foreach ( const QString& path, settingsFileCandidatesByPriority )
|
||||
{
|
||||
QFileInfo pathFi( path );
|
||||
if ( pathFi.exists() && pathFi.isReadable() )
|
||||
{
|
||||
settingsFile = pathFi;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( !found || !settingsFile.exists() || !settingsFile.isReadable() )
|
||||
{
|
||||
cError() << "Cowardly refusing to continue startup without settings."
|
||||
<< Logger::DebugList( settingsFileCandidatesByPriority );
|
||||
if ( Calamares::isAppDataDirOverridden() )
|
||||
{
|
||||
cError() << "FATAL: explicitly configured application data directory is missing settings.conf";
|
||||
}
|
||||
else
|
||||
{
|
||||
cError() << "FATAL: none of the expected configuration file paths exist.";
|
||||
}
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
auto* settings = new Calamares::Settings( settingsFile.absoluteFilePath(), debugMode ); // Creates singleton
|
||||
if ( settings->modulesSequence().count() < 1 )
|
||||
{
|
||||
cError() << "FATAL: no sequence set.";
|
||||
::exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
Settings*
|
||||
Settings::init( const QString& path )
|
||||
{
|
||||
if ( s_instance )
|
||||
{
|
||||
cWarning() << "Calamares::Settings already created";
|
||||
return s_instance;
|
||||
}
|
||||
return new Calamares::Settings( path, true );
|
||||
}
|
||||
|
||||
bool
|
||||
Settings::isValid() const
|
||||
{
|
||||
if ( brandingComponentName().isEmpty() )
|
||||
{
|
||||
cWarning() << "No branding component is set";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto invalidDescriptor = []( const InstanceDescription& d ) { return !d.isValid(); };
|
||||
const auto invalidDescriptorIt
|
||||
= std::find_if( m_moduleInstances.constBegin(), m_moduleInstances.constEnd(), invalidDescriptor );
|
||||
if ( invalidDescriptorIt != m_moduleInstances.constEnd() )
|
||||
{
|
||||
cWarning() << "Invalid module instance in *instances* or *sequence*";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
205
calamares/src/libcalamares/Settings.h
Normal file
205
calamares/src/libcalamares/Settings.h
Normal file
@@ -0,0 +1,205 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Gabriel Craciunescu <crazy@frugalware.org>
|
||||
* SPDX-FileCopyrightText: 2019 Dominic Hayes <ferenosdev@outlook.com>
|
||||
* SPDX-FileCopyrightText: 2017-2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SETTINGS_H
|
||||
#define SETTINGS_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "modulesystem/Actions.h"
|
||||
#include "modulesystem/InstanceKey.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
/** @brief Description of an instance as named in `settings.conf`
|
||||
*
|
||||
* An instance is an intended-step-in-sequence; it is not yet
|
||||
* a loaded module. The instances have config-files and weights
|
||||
* which are used by the module manager when loading modules
|
||||
* and creating jobs.
|
||||
*/
|
||||
class DLLEXPORT InstanceDescription
|
||||
{
|
||||
using InstanceKey = Calamares::ModuleSystem::InstanceKey;
|
||||
|
||||
public:
|
||||
/** @brief An invalid InstanceDescription
|
||||
*
|
||||
* Use `fromSettings()` to populate an InstanceDescription and
|
||||
* check its validity.
|
||||
*/
|
||||
InstanceDescription() = default;
|
||||
|
||||
/** @brief An InstanceDescription with no special settings.
|
||||
*
|
||||
* Regardless of @p key being custom, sets weight to 1 and
|
||||
* the configuration file to @c key.module() (plus the ".conf"
|
||||
* extension).
|
||||
*
|
||||
* To InstanceDescription is custom if the key is.
|
||||
*/
|
||||
InstanceDescription( const InstanceKey& key );
|
||||
|
||||
static InstanceDescription fromSettings( const QVariantMap& );
|
||||
|
||||
bool isValid() const { return m_instanceKey.isValid(); }
|
||||
|
||||
const InstanceKey& key() const { return m_instanceKey; }
|
||||
QString configFileName() const { return m_configFileName; }
|
||||
bool isCustom() const { return m_instanceKey.isCustom(); }
|
||||
int weight() const { return m_weight < 0 ? 1 : m_weight; }
|
||||
bool explicitWeight() const { return m_weight > 0; }
|
||||
|
||||
private:
|
||||
InstanceKey m_instanceKey;
|
||||
QString m_configFileName;
|
||||
int m_weight = 0;
|
||||
};
|
||||
|
||||
class DLLEXPORT Settings : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
#ifdef BUILD_AS_TEST
|
||||
public:
|
||||
#endif
|
||||
explicit Settings( bool debugMode );
|
||||
explicit Settings( const QString& settingsFilePath, bool debugMode );
|
||||
|
||||
void setConfiguration( const QByteArray& configData, const QString& explainName );
|
||||
void reconcileInstancesAndSequence();
|
||||
|
||||
public:
|
||||
static Settings* instance();
|
||||
/// @brief Find a settings.conf, following @p debugMode
|
||||
static Settings* init( bool debugMode );
|
||||
/// @brief Explicif filename, debug is always true (for testing)
|
||||
static Settings* init( const QString& filename );
|
||||
|
||||
/// @brief Get the path this settings was created for (may be empty)
|
||||
QString path() const { return m_settingsPath; }
|
||||
|
||||
QStringList modulesSearchPaths() const;
|
||||
|
||||
using InstanceDescriptionList = QList< InstanceDescription >;
|
||||
/** @brief All the module instances used
|
||||
*
|
||||
* Each module-instance mentioned in `settings.conf` has an entry
|
||||
* in the moduleInstances list -- both custom entries that are
|
||||
* in the *instances* section, and each module mentioned in the
|
||||
* *sequence*.
|
||||
*/
|
||||
InstanceDescriptionList moduleInstances() const;
|
||||
|
||||
using ModuleSequence = QList< QPair< ModuleSystem::Action, Calamares::ModuleSystem::InstanceKeyList > >;
|
||||
/** @brief Representation of *sequence* of execution
|
||||
*
|
||||
* Each "section" of the *sequence* key in `settings.conf` gets an
|
||||
* entry here, stating what kind of action is taken and which modules
|
||||
* take part (in order). Each entry in the list is an instance key
|
||||
* which can be found in the moduleInstances() list.
|
||||
*/
|
||||
ModuleSequence modulesSequence() const;
|
||||
|
||||
QString brandingComponentName() const;
|
||||
|
||||
/** @brief Are the settings consistent and valid?
|
||||
*
|
||||
* Checks that at least branding is set, and that the instances
|
||||
* and sequence are valid.
|
||||
*/
|
||||
bool isValid() const;
|
||||
|
||||
/** @brief Is this a debugging run?
|
||||
*
|
||||
* Returns true if Calamares is in debug mode. In debug mode,
|
||||
* modules and settings are loaded from more locations, to help
|
||||
* development and debugging.
|
||||
*/
|
||||
bool debugMode() const { return m_debug; }
|
||||
|
||||
/** @brief Distinguish "install" from "OEM" modes.
|
||||
*
|
||||
* Returns true in "install" mode, which is where actions happen
|
||||
* in a chroot -- the target system, which exists separately from
|
||||
* the source system. In "OEM" mode, returns false and most actions
|
||||
* apply to the *current* (host) system.
|
||||
*/
|
||||
bool doChroot() const { return m_doChroot; }
|
||||
|
||||
/** @brief Global setting of prompt-before-install.
|
||||
*
|
||||
* Returns true when the configuration is such that the user
|
||||
* should be prompted one-last-time before any action is taken
|
||||
* that really affects the machine.
|
||||
*/
|
||||
bool showPromptBeforeExecution() const { return m_promptInstall; }
|
||||
|
||||
/** @brief Distinguish between "install" and "setup" modes.
|
||||
*
|
||||
* This influences user-visible strings, for instance using the
|
||||
* word "setup" instead of "install" where relevant.
|
||||
*/
|
||||
bool isSetupMode() const { return m_isSetupMode; }
|
||||
|
||||
/** @brief Returns whether the named module is enabled
|
||||
*
|
||||
* Returns true if @p module is enabled in settings.conf. Be aware that it
|
||||
* only tests for a specific module name so if a QML and non-QML version
|
||||
* of the same module exists, it must be specified explicitly
|
||||
*
|
||||
* @p module is a module name or module key e.g. packagechooser) and not a
|
||||
* full module key+id (e.g. packagechooser@packagechooser)
|
||||
*
|
||||
*/
|
||||
bool isModuleEnabled( const QString& module ) const;
|
||||
|
||||
/** @brief Global setting of disable-cancel: can't cancel ever. */
|
||||
bool disableCancel() const { return m_disableCancel; }
|
||||
|
||||
/** @brief Temporary setting of disable-cancel: can't cancel during exec. */
|
||||
bool disableCancelDuringExec() const { return m_disableCancelDuringExec; }
|
||||
|
||||
bool hideBackAndNextDuringExec() const { return m_hideBackAndNextDuringExec; }
|
||||
|
||||
/** @brief Is quit-at-end set? (Quit automatically when done) */
|
||||
bool quitAtEnd() const { return m_quitAtEnd; }
|
||||
|
||||
private:
|
||||
static Settings* s_instance;
|
||||
QString m_settingsPath;
|
||||
|
||||
QStringList m_modulesSearchPaths;
|
||||
|
||||
InstanceDescriptionList m_moduleInstances;
|
||||
ModuleSequence m_modulesSequence;
|
||||
|
||||
QString m_brandingComponentName;
|
||||
|
||||
// bools are initialized here according to default setting
|
||||
bool m_debug;
|
||||
bool m_doChroot = true;
|
||||
bool m_isSetupMode = false;
|
||||
bool m_promptInstall = false;
|
||||
bool m_disableCancel = false;
|
||||
bool m_disableCancelDuringExec = false;
|
||||
bool m_hideBackAndNextDuringExec = false;
|
||||
bool m_quitAtEnd = false;
|
||||
};
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // SETTINGS_H
|
||||
669
calamares/src/libcalamares/Tests.cpp
Normal file
669
calamares/src/libcalamares/Tests.cpp
Normal file
@@ -0,0 +1,669 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018-2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "JobQueue.h"
|
||||
#include "Settings.h"
|
||||
#include "compat/Variant.h"
|
||||
#include "modulesystem/InstanceKey.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class TestLibCalamares : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestLibCalamares() {}
|
||||
~TestLibCalamares() override {}
|
||||
|
||||
private Q_SLOTS:
|
||||
void testGSModify();
|
||||
void testGSLoadSave();
|
||||
void testGSLoadSave2();
|
||||
void testGSLoadSaveYAMLStringList();
|
||||
void testGSNestedLookup();
|
||||
|
||||
void testInstanceKey();
|
||||
void testInstanceDescription();
|
||||
|
||||
void testSettings();
|
||||
|
||||
void testJobQueue();
|
||||
};
|
||||
|
||||
void
|
||||
TestLibCalamares::testGSModify()
|
||||
{
|
||||
Calamares::GlobalStorage gs;
|
||||
QSignalSpy spy( &gs, &Calamares::GlobalStorage::changed );
|
||||
|
||||
const QString key( "derp" );
|
||||
|
||||
QCOMPARE( gs.count(), 0 );
|
||||
QVERIFY( !gs.contains( key ) );
|
||||
|
||||
const int value = 17;
|
||||
gs.insert( key, value );
|
||||
QCOMPARE( gs.count(), 1 );
|
||||
QVERIFY( gs.contains( key ) );
|
||||
QCOMPARE( Calamares::typeOf( gs.value( key ) ), Calamares::IntVariantType );
|
||||
QCOMPARE( gs.value( key ).toString(), QString( "17" ) ); // It isn't a string, but does convert
|
||||
QCOMPARE( gs.value( key ).toInt(), value );
|
||||
|
||||
gs.remove( key );
|
||||
QCOMPARE( gs.count(), 0 );
|
||||
QVERIFY( !gs.contains( key ) );
|
||||
|
||||
QCOMPARE( spy.count(), 2 ); // one insert, one remove
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testGSLoadSave()
|
||||
{
|
||||
Calamares::GlobalStorage gs;
|
||||
const QString jsonfilename( "gs.test.json" );
|
||||
const QString yamlfilename( "gs.test.yaml" );
|
||||
|
||||
gs.insert( "derp", 17 );
|
||||
gs.insert( "cow", "moo" );
|
||||
|
||||
QVariantList l;
|
||||
for ( const auto& s : QStringList { "dopey", "sneezy" } )
|
||||
{
|
||||
l.append( s );
|
||||
}
|
||||
gs.insert( "dwarfs", l );
|
||||
|
||||
QCOMPARE( gs.count(), 3 );
|
||||
|
||||
QVERIFY( gs.saveJson( jsonfilename ) );
|
||||
Calamares::GlobalStorage gs2;
|
||||
QCOMPARE( gs2.count(), 0 );
|
||||
QVERIFY( gs2.loadJson( jsonfilename ) );
|
||||
QCOMPARE( gs2.count(), 3 );
|
||||
QCOMPARE( gs2.data(), gs.data() );
|
||||
|
||||
QVERIFY( gs.saveYaml( yamlfilename ) );
|
||||
Calamares::GlobalStorage gs3;
|
||||
QCOMPARE( gs3.count(), 0 );
|
||||
QVERIFY( gs3.loadYaml( jsonfilename ) );
|
||||
QCOMPARE( gs3.count(), 3 );
|
||||
QCOMPARE( gs3.data(), gs.data() );
|
||||
|
||||
// YAML can load as JSON!
|
||||
QVERIFY( gs3.loadYaml( jsonfilename ) );
|
||||
QCOMPARE( gs3.count(), 3 );
|
||||
QCOMPARE( gs3.data(), gs.data() );
|
||||
|
||||
// Failures in loading
|
||||
QVERIFY( !gs3.loadJson( yamlfilename ) );
|
||||
|
||||
Calamares::GlobalStorage gs4;
|
||||
gs4.insert( "derp", 32 );
|
||||
gs4.insert( "dorp", "Varsseveld" );
|
||||
QCOMPARE( gs4.count(), 2 );
|
||||
QVERIFY( gs4.contains( "dorp" ) );
|
||||
QCOMPARE( gs4.value( "derp" ).toInt(), 32 );
|
||||
QVERIFY( gs4.loadJson( jsonfilename ) );
|
||||
// 3 keys from the file, but one overwrite
|
||||
QCOMPARE( gs4.count(), 4 );
|
||||
QVERIFY( gs4.contains( "dorp" ) );
|
||||
QCOMPARE( gs4.value( "derp" ).toInt(), 17 ); // This one was overwritten
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testGSLoadSave2()
|
||||
{
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
|
||||
const QString filename( BUILD_AS_TEST "/testdata/yaml-list.conf" );
|
||||
QVERIFY2( QFile::exists( filename ), qPrintable( filename ) );
|
||||
|
||||
Calamares::GlobalStorage gs1;
|
||||
const QString key( "dwarfs" );
|
||||
|
||||
QVERIFY( gs1.loadYaml( filename ) );
|
||||
QCOMPARE( gs1.count(), 4 ); // From examining the file
|
||||
QVERIFY( gs1.contains( key ) );
|
||||
cDebug() << Calamares::typeOf( gs1.value( key ) ) << gs1.value( key );
|
||||
QCOMPARE( Calamares::typeOf( gs1.value( key ) ), Calamares::ListVariantType );
|
||||
|
||||
const QString yamlfilename( "gs.test.yaml" );
|
||||
QVERIFY( gs1.saveYaml( yamlfilename ) );
|
||||
|
||||
Calamares::GlobalStorage gs2;
|
||||
QVERIFY( gs2.loadYaml( yamlfilename ) );
|
||||
QVERIFY( gs2.contains( key ) );
|
||||
QCOMPARE( Calamares::typeOf( gs2.value( key ) ), Calamares::ListVariantType );
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testGSLoadSaveYAMLStringList()
|
||||
{
|
||||
Calamares::GlobalStorage gs;
|
||||
const QString yamlfilename( "gs.test.yaml" );
|
||||
|
||||
gs.insert( "derp", 17 );
|
||||
gs.insert( "cow", "moo" );
|
||||
gs.insert( "dwarfs", QStringList { "happy", "dopey", "sleepy", "sneezy", "doc", "thorin", "balin" } );
|
||||
|
||||
QCOMPARE( gs.count(), 3 );
|
||||
QCOMPARE( gs.value( "dwarfs" ).toList().count(), 7 ); // There's seven dwarfs, right?
|
||||
QVERIFY( gs.value( "dwarfs" ).toStringList().contains( "thorin" ) );
|
||||
QVERIFY( !gs.value( "dwarfs" ).toStringList().contains( "gimli" ) );
|
||||
|
||||
|
||||
QVERIFY( gs.saveYaml( yamlfilename ) );
|
||||
|
||||
Calamares::GlobalStorage gs2;
|
||||
QCOMPARE( gs2.count(), 0 );
|
||||
QVERIFY( gs2.loadYaml( yamlfilename ) );
|
||||
QCOMPARE( gs2.count(), 3 );
|
||||
QEXPECT_FAIL( "", "QStringList doesn't write out nicely", Continue );
|
||||
QCOMPARE( gs2.value( "dwarfs" ).toList().count(), 7 ); // There's seven dwarfs, right?
|
||||
QCOMPARE( gs2.value( "dwarfs" ).toString(), QStringLiteral( "<QStringList>" ) ); // .. they're gone
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testGSNestedLookup()
|
||||
{
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
|
||||
const QString filename( BUILD_AS_TEST "/testdata/yaml-list.conf" );
|
||||
QVERIFY2( QFile::exists( filename ), qPrintable( filename ) );
|
||||
|
||||
Calamares::GlobalStorage gs2;
|
||||
QVERIFY( gs2.loadYaml( filename ) );
|
||||
|
||||
bool ok = false;
|
||||
const auto v0 = Calamares::lookup( &gs2, "horse.colors.neck", ok );
|
||||
QVERIFY( ok );
|
||||
QVERIFY( v0.canConvert< QString >() );
|
||||
QCOMPARE( v0.toString(), QStringLiteral( "roan" ) );
|
||||
const auto v1 = Calamares::lookup( &gs2, "horse.colors.nose", ok );
|
||||
QVERIFY( !ok );
|
||||
QVERIFY( !v1.isValid() );
|
||||
const auto v2 = Calamares::lookup( &gs2, "cow.colors.nose", ok );
|
||||
QVERIFY( !ok );
|
||||
QVERIFY( !v2.isValid() );
|
||||
const auto v3 = Calamares::lookup( &gs2, "dwarfs", ok );
|
||||
QVERIFY( ok );
|
||||
QVERIFY( v3.canConvert< QVariantList >() ); // because it's a list-valued thing
|
||||
const auto v4 = Calamares::lookup( &gs2, "dwarfs.sleepy", ok );
|
||||
QVERIFY( !ok ); // Sleepy is a value in the list of dwarfs, not a key
|
||||
const auto v5 = Calamares::lookup( &gs2, "derp", ok );
|
||||
QVERIFY( ok );
|
||||
QCOMPARE( v5.toInt(), 17 );
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testInstanceKey()
|
||||
{
|
||||
using InstanceKey = Calamares::ModuleSystem::InstanceKey;
|
||||
{
|
||||
InstanceKey k;
|
||||
QVERIFY( !k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QVERIFY( k.module().isEmpty() );
|
||||
}
|
||||
{
|
||||
InstanceKey k( QStringLiteral( "welcome" ), QString() );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( "welcome" ) );
|
||||
QCOMPARE( k.id(), QStringLiteral( "welcome" ) );
|
||||
}
|
||||
{
|
||||
InstanceKey k( QStringLiteral( "shellprocess" ), QStringLiteral( "zfssetup" ) );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( "shellprocess" ) );
|
||||
QCOMPARE( k.id(), QStringLiteral( "zfssetup" ) );
|
||||
}
|
||||
|
||||
{
|
||||
// This is a bad idea, names and ids with odd punctuation
|
||||
InstanceKey k( QStringLiteral( " o__O " ), QString() );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( " o__O " ) );
|
||||
}
|
||||
{
|
||||
// .. but @ is disallowed
|
||||
InstanceKey k( QStringLiteral( "welcome@hi" ), QString() );
|
||||
QVERIFY( !k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QVERIFY( k.module().isEmpty() );
|
||||
}
|
||||
|
||||
{
|
||||
InstanceKey k = InstanceKey::fromString( "welcome" );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( "welcome" ) );
|
||||
QCOMPARE( k.id(), QStringLiteral( "welcome" ) );
|
||||
}
|
||||
{
|
||||
InstanceKey k = InstanceKey::fromString( "welcome@welcome" );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( "welcome" ) );
|
||||
QCOMPARE( k.id(), QStringLiteral( "welcome" ) );
|
||||
}
|
||||
|
||||
{
|
||||
InstanceKey k = InstanceKey::fromString( "welcome@hi" );
|
||||
QVERIFY( k.isValid() );
|
||||
QVERIFY( k.isCustom() );
|
||||
QCOMPARE( k.module(), QStringLiteral( "welcome" ) );
|
||||
QCOMPARE( k.id(), QStringLiteral( "hi" ) );
|
||||
}
|
||||
{
|
||||
InstanceKey k = InstanceKey::fromString( "welcome@hi@hi" );
|
||||
QVERIFY( !k.isValid() );
|
||||
QVERIFY( !k.isCustom() );
|
||||
QVERIFY( k.module().isEmpty() );
|
||||
QVERIFY( k.id().isEmpty() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testInstanceDescription()
|
||||
{
|
||||
using InstanceDescription = Calamares::InstanceDescription;
|
||||
using InstanceKey = Calamares::ModuleSystem::InstanceKey;
|
||||
|
||||
// With invalid keys
|
||||
//
|
||||
//
|
||||
{
|
||||
InstanceDescription d;
|
||||
QVERIFY( !d.isValid() );
|
||||
QVERIFY( !d.isCustom() );
|
||||
QCOMPARE( d.weight(), 0 );
|
||||
QVERIFY( d.configFileName().isEmpty() );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
}
|
||||
|
||||
{
|
||||
InstanceDescription d( InstanceKey {} ); // most-vexing, use brace-init instead
|
||||
QVERIFY( !d.isValid() );
|
||||
QVERIFY( !d.isCustom() );
|
||||
QCOMPARE( d.weight(), 0 );
|
||||
QVERIFY( d.configFileName().isEmpty() );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
}
|
||||
|
||||
// Private constructor
|
||||
//
|
||||
// This does set up the config file, to default values
|
||||
{
|
||||
InstanceDescription d( InstanceKey::fromString( "welcome" ) );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( !d.isCustom() );
|
||||
QCOMPARE( d.weight(), 1 ); // **now** the constraints kick in
|
||||
QVERIFY( !d.configFileName().isEmpty() );
|
||||
QCOMPARE( d.configFileName(), QStringLiteral( "welcome.conf" ) );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
}
|
||||
|
||||
{
|
||||
InstanceDescription d( InstanceKey::fromString( "welcome@hi" ) );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( d.isCustom() );
|
||||
QCOMPARE( d.weight(), 1 ); // **now** the constraints kick in
|
||||
QVERIFY( !d.configFileName().isEmpty() );
|
||||
QCOMPARE( d.configFileName(), QStringLiteral( "welcome.conf" ) );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
}
|
||||
|
||||
|
||||
// From settings, normal program flow
|
||||
//
|
||||
//
|
||||
{
|
||||
QVariantMap m;
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( !d.isValid() );
|
||||
}
|
||||
{
|
||||
QVariantMap m;
|
||||
m.insert( "name", "welcome" );
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( !d.isValid() );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
}
|
||||
{
|
||||
QVariantMap m;
|
||||
m.insert( "module", "welcome" );
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( !d.isCustom() );
|
||||
// Valid, but no weight set by settings
|
||||
QCOMPARE( d.weight(), 1 );
|
||||
QVERIFY( !d.explicitWeight() );
|
||||
|
||||
QCOMPARE( d.key().module(), QString( "welcome" ) );
|
||||
QCOMPARE( d.key().id(), QString( "welcome" ) );
|
||||
QCOMPARE( d.configFileName(), QString( "welcome.conf" ) );
|
||||
}
|
||||
{
|
||||
QVariantMap m;
|
||||
m.insert( "module", "welcome" );
|
||||
m.insert( "weight", 1 );
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( !d.isCustom() );
|
||||
|
||||
//Valid, set explicitly
|
||||
QCOMPARE( d.weight(), 1 );
|
||||
QVERIFY( d.explicitWeight() );
|
||||
|
||||
QCOMPARE( d.key().module(), QString( "welcome" ) );
|
||||
QCOMPARE( d.key().id(), QString( "welcome" ) );
|
||||
QCOMPARE( d.configFileName(), QString( "welcome.conf" ) );
|
||||
}
|
||||
{
|
||||
QVariantMap m;
|
||||
m.insert( "module", "welcome" );
|
||||
m.insert( "id", "hi" );
|
||||
m.insert( "weight", "17" ); // String, that's kind of bogus
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( d.isCustom() );
|
||||
QCOMPARE( d.weight(), 17 );
|
||||
QCOMPARE( d.key().module(), QString( "welcome" ) );
|
||||
QCOMPARE( d.key().id(), QString( "hi" ) );
|
||||
QCOMPARE( d.configFileName(), QString( "welcome.conf" ) );
|
||||
QVERIFY( d.explicitWeight() );
|
||||
}
|
||||
{
|
||||
QVariantMap m;
|
||||
m.insert( "module", "welcome" );
|
||||
m.insert( "id", "hi" );
|
||||
m.insert( "weight", 134 );
|
||||
m.insert( "config", "hi.conf" );
|
||||
|
||||
InstanceDescription d = InstanceDescription::fromSettings( m );
|
||||
QVERIFY( d.isValid() );
|
||||
QVERIFY( d.isCustom() );
|
||||
QCOMPARE( d.weight(), 100 );
|
||||
QCOMPARE( d.key().module(), QString( "welcome" ) );
|
||||
QCOMPARE( d.key().id(), QString( "hi" ) );
|
||||
QCOMPARE( d.configFileName(), QString( "hi.conf" ) );
|
||||
QVERIFY( d.explicitWeight() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
TestLibCalamares::testSettings()
|
||||
{
|
||||
{
|
||||
Calamares::Settings s( false );
|
||||
QVERIFY( !s.debugMode() );
|
||||
QVERIFY( !s.isValid() );
|
||||
}
|
||||
{
|
||||
Calamares::Settings s( true );
|
||||
QVERIFY( s.debugMode() );
|
||||
QVERIFY( s.moduleInstances().isEmpty() );
|
||||
QVERIFY( s.modulesSequence().isEmpty() );
|
||||
QVERIFY( s.brandingComponentName().isEmpty() );
|
||||
QVERIFY( !s.isValid() );
|
||||
|
||||
// *INDENT-OFF*
|
||||
s.setConfiguration( R"(---
|
||||
branding: default # needed for it to be considered valid
|
||||
instances:
|
||||
- module: welcome
|
||||
id: hi
|
||||
weight: 75
|
||||
- module: welcome
|
||||
id: yo
|
||||
config: yolo.conf
|
||||
sequence:
|
||||
- show:
|
||||
- welcome@hi
|
||||
- welcome@yo
|
||||
- dummycpp
|
||||
- summary
|
||||
- exec:
|
||||
- welcome@hi
|
||||
)",
|
||||
QStringLiteral( "<testdata>" ) );
|
||||
// *INDENT-ON*
|
||||
|
||||
QVERIFY( s.debugMode() );
|
||||
QCOMPARE( s.moduleInstances().count(), 4 ); // there are 4 module instances mentioned
|
||||
QCOMPARE( s.modulesSequence().count(), 2 ); // 2 steps (show, exec)
|
||||
QVERIFY( !s.brandingComponentName().isEmpty() );
|
||||
QVERIFY( s.isValid() );
|
||||
|
||||
// Make a lambda where we can adjust what it looks for from the outside,
|
||||
// by capturing a reference.
|
||||
QString moduleKey = QString( "welcome" );
|
||||
auto moduleFinder = [ &moduleKey ]( const Calamares::InstanceDescription& d )
|
||||
{ return d.isValid() && d.key().module() == moduleKey; };
|
||||
|
||||
const auto it0 = std::find_if( s.moduleInstances().constBegin(), s.moduleInstances().constEnd(), moduleFinder );
|
||||
QVERIFY( it0 != s.moduleInstances().constEnd() );
|
||||
|
||||
moduleKey = QString( "derp" );
|
||||
const auto it1 = std::find_if( s.moduleInstances().constBegin(), s.moduleInstances().constEnd(), moduleFinder );
|
||||
QVERIFY( it1 == s.moduleInstances().constEnd() );
|
||||
|
||||
int validCount = 0;
|
||||
int customCount = 0;
|
||||
for ( const auto& d : s.moduleInstances() )
|
||||
{
|
||||
if ( d.isValid() )
|
||||
{
|
||||
validCount++;
|
||||
}
|
||||
if ( d.isCustom() )
|
||||
{
|
||||
customCount++;
|
||||
}
|
||||
QVERIFY( d.isCustom() ? d.isValid() : true ); // All custom entries are valid
|
||||
|
||||
if ( !d.isCustom() )
|
||||
{
|
||||
QCOMPARE( d.configFileName(), QString( "%1.conf" ).arg( d.key().module() ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
// Specific cases from this config file
|
||||
if ( d.key().id() == QString( "yo" ) )
|
||||
{
|
||||
QCOMPARE( d.configFileName(), QString( "yolo.conf" ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
QCOMPARE( d.configFileName(), QString( "welcome.conf" ) ); // Not set in the settings data
|
||||
}
|
||||
}
|
||||
}
|
||||
QCOMPARE( customCount, 2 );
|
||||
QCOMPARE( validCount, 4 ); // welcome@hi is listed twice, in *show* and *exec*
|
||||
}
|
||||
}
|
||||
|
||||
constexpr const std::chrono::milliseconds MAX_TEST_DURATION( 3000 );
|
||||
constexpr const int MAX_TEST_SLEEP( 2 ); // seconds, < MAX_TEST_DURATION
|
||||
|
||||
Q_STATIC_ASSERT( std::chrono::seconds( MAX_TEST_SLEEP ) < MAX_TEST_DURATION );
|
||||
|
||||
class DummyJob : public Calamares::Job
|
||||
{
|
||||
public:
|
||||
DummyJob( QObject* parent )
|
||||
: Calamares::Job( parent )
|
||||
{
|
||||
}
|
||||
~DummyJob() override;
|
||||
|
||||
QString prettyName() const override;
|
||||
Calamares::JobResult exec() override;
|
||||
};
|
||||
|
||||
DummyJob::~DummyJob() {}
|
||||
|
||||
QString
|
||||
DummyJob::prettyName() const
|
||||
{
|
||||
return QString( "DummyJob" );
|
||||
}
|
||||
|
||||
Calamares::JobResult
|
||||
DummyJob::exec()
|
||||
{
|
||||
cDebug() << "Starting DummyJob";
|
||||
progress( 0.5 );
|
||||
QThread::sleep( MAX_TEST_SLEEP );
|
||||
cDebug() << ".. continuing DummyJob";
|
||||
progress( 0.75 );
|
||||
return Calamares::JobResult::ok();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
TestLibCalamares::testJobQueue()
|
||||
{
|
||||
// Run an empty queue
|
||||
{
|
||||
Calamares::JobQueue q;
|
||||
QVERIFY( !q.isRunning() );
|
||||
|
||||
QSignalSpy spy_progress( &q, &Calamares::JobQueue::progress );
|
||||
QSignalSpy spy_finished( &q, &Calamares::JobQueue::finished );
|
||||
QSignalSpy spy_failed( &q, &Calamares::JobQueue::failed );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &q, &Calamares::JobQueue::finished, &loop, &QEventLoop::quit );
|
||||
QTimer::singleShot( MAX_TEST_DURATION, &loop, &QEventLoop::quit );
|
||||
q.start();
|
||||
QVERIFY( q.isRunning() );
|
||||
loop.exec();
|
||||
QVERIFY( !q.isRunning() );
|
||||
QCOMPARE( spy_finished.count(), 1 );
|
||||
QCOMPARE( spy_failed.count(), 0 );
|
||||
QCOMPARE( spy_progress.count(), 1 ); // just one, 100% at queue end
|
||||
}
|
||||
|
||||
// Run a dummy queue
|
||||
{
|
||||
Calamares::JobQueue q;
|
||||
QVERIFY( !q.isRunning() );
|
||||
|
||||
q.enqueue( 8, Calamares::JobList() << Calamares::job_ptr( new DummyJob( this ) ) );
|
||||
QSignalSpy spy_progress( &q, &Calamares::JobQueue::progress );
|
||||
QSignalSpy spy_finished( &q, &Calamares::JobQueue::finished );
|
||||
QSignalSpy spy_failed( &q, &Calamares::JobQueue::failed );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &q, &Calamares::JobQueue::finished, &loop, &QEventLoop::quit );
|
||||
QTimer::singleShot( MAX_TEST_DURATION, &loop, &QEventLoop::quit );
|
||||
q.start();
|
||||
QVERIFY( q.isRunning() );
|
||||
loop.exec();
|
||||
QVERIFY( !q.isRunning() );
|
||||
QCOMPARE( spy_finished.count(), 1 );
|
||||
QCOMPARE( spy_failed.count(), 0 );
|
||||
// 0% by the queue at job start
|
||||
// 50% by the job itself
|
||||
// 90% by the job itself
|
||||
// 100% by the queue at job end
|
||||
// 100% by the queue at queue end
|
||||
QCOMPARE( spy_progress.count(), 5 );
|
||||
}
|
||||
|
||||
{
|
||||
Calamares::JobQueue q;
|
||||
QVERIFY( !q.isRunning() );
|
||||
|
||||
q.enqueue( 8, Calamares::JobList() << Calamares::job_ptr( new DummyJob( this ) ) );
|
||||
q.enqueue( 12,
|
||||
Calamares::JobList() << Calamares::job_ptr( new DummyJob( this ) )
|
||||
<< Calamares::job_ptr( new DummyJob( this ) ) );
|
||||
QSignalSpy spy_progress( &q, &Calamares::JobQueue::progress );
|
||||
QSignalSpy spy_finished( &q, &Calamares::JobQueue::finished );
|
||||
QSignalSpy spy_failed( &q, &Calamares::JobQueue::failed );
|
||||
|
||||
QEventLoop loop;
|
||||
connect( &q, &Calamares::JobQueue::finished, &loop, &QEventLoop::quit );
|
||||
// Run the loop longer because the jobs take longer (there are 3 of them)
|
||||
QTimer::singleShot( 3 * MAX_TEST_DURATION, &loop, &QEventLoop::quit );
|
||||
q.start();
|
||||
QVERIFY( q.isRunning() );
|
||||
loop.exec();
|
||||
QVERIFY( !q.isRunning() );
|
||||
QCOMPARE( spy_finished.count(), 1 );
|
||||
QCOMPARE( spy_failed.count(), 0 );
|
||||
// 0% by the queue at job start
|
||||
// 50% by the job itself
|
||||
// 90% by the job itself
|
||||
// 100% by the queue at job end
|
||||
// 4 more for the next job
|
||||
// 4 more for the next job
|
||||
// 100% by the queue at queue end
|
||||
QCOMPARE( spy_progress.count(), 13 );
|
||||
|
||||
/* Consider how progress will be reported:
|
||||
*
|
||||
* - the first module has weight 8, so the 1 job it has has weight 8
|
||||
* - the second module has weight 12, so each of its two jobs has weight 6
|
||||
*
|
||||
* Total weight of the modules is 20. So the events are
|
||||
*
|
||||
* Job Progress Overall Weight Consumed Overall Progress
|
||||
* 1 0 0 0.00
|
||||
* 1 50 4 0.20
|
||||
* 1 75 6 0.30
|
||||
* 1 100 8 0.40
|
||||
* 2 0 8 0.40
|
||||
* 2 50 11 (8 + 50% of 6) 0.55
|
||||
* 2 75 12.5 0.625
|
||||
* 2 100 14 0.70
|
||||
* 3 0 14 0.70
|
||||
* 3 50 17 0.85
|
||||
* 3 75 18.5 0.925
|
||||
* 3 100 20 1.00
|
||||
* - 100 20 1.00
|
||||
*/
|
||||
cDebug() << "Progress signals:";
|
||||
qreal overallProgress = 0.0;
|
||||
for ( const auto& e : spy_progress )
|
||||
{
|
||||
QCOMPARE( e.count(), 2 );
|
||||
const auto v = e.first();
|
||||
QVERIFY( v.canConvert< qreal >() );
|
||||
qreal progress = v.toReal();
|
||||
cDebug() << Logger::SubEntry << progress;
|
||||
QVERIFY( progress >= overallProgress ); // Doesn't go backwards
|
||||
overallProgress = progress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QTEST_GUILESS_MAIN( TestLibCalamares )
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
||||
#include "Tests.moc"
|
||||
30
calamares/src/libcalamares/compat/CheckBox.h
Normal file
30
calamares/src/libcalamares/compat/CheckBox.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_COMPAT_XML_H
|
||||
#define CALAMARES_COMPAT_XML_H
|
||||
|
||||
#include <QCheckBox>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 7, 0 )
|
||||
using checkBoxStateType = int;
|
||||
const auto checkBoxStateChangedSignal = &QCheckBox::stateChanged;
|
||||
constexpr checkBoxStateType checkBoxUncheckedValue = 0;
|
||||
#else
|
||||
using checkBoxStateType = Qt::CheckState;
|
||||
const auto checkBoxStateChangedSignal = &QCheckBox::checkStateChanged;
|
||||
constexpr checkBoxStateType checkBoxUncheckedValue = Qt::Unchecked;
|
||||
#endif
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
30
calamares/src/libcalamares/compat/Mutex.h
Normal file
30
calamares/src/libcalamares/compat/Mutex.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_COMPAT_MUTEX_H
|
||||
#define CALAMARES_COMPAT_MUTEX_H
|
||||
|
||||
#include <QMutexLocker>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
|
||||
/*
|
||||
* In Qt5, QMutexLocker is a class and operates implicitly on
|
||||
* QMutex but in Qt6 it is a template and needs a specialization.
|
||||
*/
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
using MutexLocker = QMutexLocker;
|
||||
#else
|
||||
using MutexLocker = QMutexLocker< QMutex >;
|
||||
#endif
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
23
calamares/src/libcalamares/compat/Size.h
Normal file
23
calamares/src/libcalamares/compat/Size.h
Normal file
@@ -0,0 +1,23 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_COMPAT_SIZE_H
|
||||
#define CALAMARES_COMPAT_SIZE_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
/* Compatibility of size types (e.g. qsizetype, STL sizes, int) between Qt5 and Qt6 */
|
||||
|
||||
using NumberForTr = int;
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
57
calamares/src/libcalamares/compat/Variant.h
Normal file
57
calamares/src/libcalamares/compat/Variant.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2023 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_COMPAT_VARIANT_H
|
||||
#define CALAMARES_COMPAT_VARIANT_H
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
/* Compatibility of QVariant between Qt5 and Qt6 */
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
const auto typeOf = []( const QVariant& v ) { return v.type(); };
|
||||
const auto ListVariantType = QVariant::List;
|
||||
const auto MapVariantType = QVariant::Map;
|
||||
const auto HashVariantType = QVariant::Hash;
|
||||
const auto StringVariantType = QVariant::String;
|
||||
const auto CharVariantType = QVariant::Char;
|
||||
const auto StringListVariantType = QVariant::StringList;
|
||||
const auto BoolVariantType = QVariant::Bool;
|
||||
const auto IntVariantType = QVariant::Int;
|
||||
const auto UIntVariantType = QVariant::UInt;
|
||||
const auto LongLongVariantType = QVariant::LongLong;
|
||||
const auto ULongLongVariantType = QVariant::ULongLong;
|
||||
const auto DoubleVariantType = QVariant::Double;
|
||||
#else
|
||||
const auto typeOf = []( const QVariant& v ) { return v.typeId(); };
|
||||
const auto ListVariantType = QMetaType::Type::QVariantList;
|
||||
const auto MapVariantType = QMetaType::Type::QVariantMap;
|
||||
const auto HashVariantType = QMetaType::Type::QVariantHash;
|
||||
const auto StringVariantType = QMetaType::Type::QString;
|
||||
const auto CharVariantType = QMetaType::Type::Char;
|
||||
const auto StringListVariantType = QMetaType::Type::QStringList;
|
||||
const auto BoolVariantType = QMetaType::Type::Bool;
|
||||
const auto IntVariantType = QMetaType::Type::Int;
|
||||
const auto UIntVariantType = QMetaType::Type::UInt;
|
||||
const auto LongLongVariantType = QMetaType::Type::LongLong;
|
||||
const auto ULongLongVariantType = QMetaType::Type::ULongLong;
|
||||
const auto DoubleVariantType = QMetaType::Type::Double;
|
||||
#endif
|
||||
|
||||
inline bool
|
||||
isIntegerVariantType( const QVariant& v )
|
||||
{
|
||||
const auto t = typeOf( v );
|
||||
return t == IntVariantType || t == UIntVariantType || t == LongLongVariantType || t == ULongLongVariantType;
|
||||
}
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
42
calamares/src/libcalamares/compat/Xml.h
Normal file
42
calamares/src/libcalamares/compat/Xml.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2024 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef CALAMARES_COMPAT_XML_H
|
||||
#define CALAMARES_COMPAT_XML_H
|
||||
|
||||
#include <QDomDocument>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
struct ParseResult
|
||||
{
|
||||
QString errorMessage;
|
||||
int errorLine = -1;
|
||||
int errorColumn = -1;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline ParseResult
|
||||
setXmlContent( QDomDocument& doc, const QByteArray& ba )
|
||||
{
|
||||
ParseResult p;
|
||||
const bool r = doc.setContent( ba, &p.errorMessage, &p.errorLine, &p.errorColumn );
|
||||
return r ? ParseResult {} : p;
|
||||
}
|
||||
#else
|
||||
[[nodiscard]] inline QDomDocument::ParseResult
|
||||
setXmlContent( QDomDocument& doc, const QByteArray& ba )
|
||||
{
|
||||
return doc.setContent( ba );
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
35
calamares/src/libcalamares/geoip/GeoIPFixed.cpp
Normal file
35
calamares/src/libcalamares/geoip/GeoIPFixed.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GeoIPFixed.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
GeoIPFixed::GeoIPFixed( const QString& attribute )
|
||||
: Interface( attribute.isEmpty() ? QStringLiteral( "Europe/Amsterdam" ) : attribute )
|
||||
{
|
||||
}
|
||||
|
||||
QString
|
||||
GeoIPFixed::rawReply( const QByteArray& )
|
||||
{
|
||||
return m_element;
|
||||
}
|
||||
|
||||
GeoIP::RegionZonePair
|
||||
GeoIPFixed::processReply( const QByteArray& data )
|
||||
{
|
||||
return splitTZString( rawReply( data ) );
|
||||
}
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
43
calamares/src/libcalamares/geoip/GeoIPFixed.h
Normal file
43
calamares/src/libcalamares/geoip/GeoIPFixed.h
Normal file
@@ -0,0 +1,43 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIP_GEOIPFIXED_H
|
||||
#define GEOIP_GEOIPFIXED_H
|
||||
|
||||
#include "Interface.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
/** @brief GeoIP with a fixed return value
|
||||
*
|
||||
* The data is ignored entirely and the attribute value is returned unchanged.
|
||||
* Note that you still need to provide a usable URL for a successful GeoIP
|
||||
* lookup -- the URL's data is just ignored.
|
||||
*
|
||||
* @note This class is an implementation detail.
|
||||
*/
|
||||
class GeoIPFixed : public Interface
|
||||
{
|
||||
public:
|
||||
/** @brief Configure the value to return from rawReply()
|
||||
*
|
||||
* An empty string, which would not be a valid zone name, is
|
||||
* translated to "Europe/Amsterdam".
|
||||
*/
|
||||
explicit GeoIPFixed( const QString& value = QString() );
|
||||
|
||||
virtual RegionZonePair processReply( const QByteArray& ) override;
|
||||
virtual QString rawReply( const QByteArray& ) override;
|
||||
};
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
92
calamares/src/libcalamares/geoip/GeoIPJSON.cpp
Normal file
92
calamares/src/libcalamares/geoip/GeoIPJSON.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2016 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GeoIPJSON.h"
|
||||
|
||||
#include "compat/Variant.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
#include "utils/Yaml.h"
|
||||
|
||||
#include <QByteArray>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
GeoIPJSON::GeoIPJSON( const QString& attribute )
|
||||
: Interface( attribute.isEmpty() ? QStringLiteral( "time_zone" ) : attribute )
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Indexes into a map @m by selectors @p l
|
||||
*
|
||||
* Each element of @p l is an index into map @m or a sub-map thereof,
|
||||
* so that "foo.bar.baz" looks up "baz" in the sub-map "bar" of sub-map
|
||||
* "foo" of @p m, like a regular JSON lookup would.
|
||||
*/
|
||||
static QString
|
||||
selectMap( const QVariantMap& m, const QStringList& l, int index )
|
||||
{
|
||||
if ( index >= l.count() )
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
QString attributeName = l[ index ];
|
||||
if ( index == l.count() - 1 )
|
||||
{
|
||||
return Calamares::getString( m, attributeName );
|
||||
}
|
||||
else
|
||||
{
|
||||
bool success = false; // bogus
|
||||
if ( m.contains( attributeName ) )
|
||||
{
|
||||
return selectMap( Calamares::getSubMap( m, attributeName, success ), l, index + 1 );
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
GeoIPJSON::rawReply( const QByteArray& data )
|
||||
{
|
||||
try
|
||||
{
|
||||
auto doc = ::YAML::Load( data );
|
||||
|
||||
QVariant var = Calamares::YAML::toVariant( doc );
|
||||
if ( !var.isNull() && var.isValid() && Calamares::typeOf( var ) == Calamares::MapVariantType )
|
||||
{
|
||||
return selectMap( var.toMap(), m_element.split( '.' ), 0 );
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "Invalid YAML data for GeoIPJSON";
|
||||
}
|
||||
}
|
||||
catch ( ::YAML::Exception& e )
|
||||
{
|
||||
Calamares::YAML::explainException( e, data, "GeoIP data" );
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
GeoIP::RegionZonePair
|
||||
GeoIPJSON::processReply( const QByteArray& data )
|
||||
{
|
||||
return splitTZString( rawReply( data ) );
|
||||
}
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
44
calamares/src/libcalamares/geoip/GeoIPJSON.h
Normal file
44
calamares/src/libcalamares/geoip/GeoIPJSON.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIP_GEOIPJSON_H
|
||||
#define GEOIP_GEOIPJSON_H
|
||||
|
||||
#include "Interface.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
/** @brief GeoIP lookup for services that return JSON.
|
||||
*
|
||||
* This is the original implementation of GeoIP lookup,
|
||||
* (e.g. using the FreeGeoIP.net service), or similar.
|
||||
*
|
||||
* The data is assumed to be in JSON format with a time_zone attribute.
|
||||
*
|
||||
* @note This class is an implementation detail.
|
||||
*/
|
||||
class GeoIPJSON : public Interface
|
||||
{
|
||||
public:
|
||||
/** @brief Configure the attribute name which is selected.
|
||||
*
|
||||
* If an empty string is passed in (not a valid attribute name),
|
||||
* then "time_zone" is used.
|
||||
*/
|
||||
explicit GeoIPJSON( const QString& attribute = QString() );
|
||||
|
||||
virtual RegionZonePair processReply( const QByteArray& ) override;
|
||||
virtual QString rawReply( const QByteArray& ) override;
|
||||
};
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
262
calamares/src/libcalamares/geoip/GeoIPTests.cpp
Normal file
262
calamares/src/libcalamares/geoip/GeoIPTests.cpp
Normal file
@@ -0,0 +1,262 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GeoIPTests.h"
|
||||
|
||||
#include "GeoIPFixed.h"
|
||||
#include "GeoIPJSON.h"
|
||||
#ifdef QT_XML_LIB
|
||||
#include "GeoIPXML.h"
|
||||
#endif
|
||||
#include "Handler.h"
|
||||
|
||||
#include "network/Manager.h"
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
QTEST_GUILESS_MAIN( GeoIPTests )
|
||||
|
||||
using namespace Calamares::GeoIP;
|
||||
|
||||
GeoIPTests::GeoIPTests() {}
|
||||
|
||||
GeoIPTests::~GeoIPTests() {}
|
||||
|
||||
void
|
||||
GeoIPTests::initTestCase()
|
||||
{
|
||||
}
|
||||
|
||||
static const char json_data_attribute[] = "{\"time_zone\":\"Europe/Amsterdam\"}";
|
||||
|
||||
void
|
||||
GeoIPTests::testJSON()
|
||||
{
|
||||
GeoIPJSON handler;
|
||||
auto tz = handler.processReply( json_data_attribute );
|
||||
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Europe" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Amsterdam" ) );
|
||||
|
||||
// JSON is quite tolerant
|
||||
tz = handler.processReply( "time_zone: \"Europe/Brussels\"" );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Brussels" ) );
|
||||
|
||||
tz = handler.processReply( "time_zone: America/New_York\n" );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testJSONalt()
|
||||
{
|
||||
GeoIPJSON handler( "zona_de_hora" );
|
||||
|
||||
auto tz = handler.processReply( json_data_attribute );
|
||||
QCOMPARE( tz.region(), QString() ); // Not found
|
||||
|
||||
tz = handler.processReply( "tarifa: 12\nzona_de_hora: Europe/Madrid" );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Europe" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Madrid" ) );
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testJSONbad()
|
||||
{
|
||||
static const char data[] = "time_zone: 1";
|
||||
|
||||
GeoIPJSON handler;
|
||||
auto tz = handler.processReply( data );
|
||||
|
||||
tz = handler.processReply( data );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = handler.processReply( "" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = handler.processReply( "<html><body>404 Forbidden</body></html>" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = handler.processReply( "{ time zone = 'America/LosAngeles'}" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
}
|
||||
|
||||
static const char xml_data_ubiquity[] =
|
||||
R"(<Response>
|
||||
<Ip>85.150.1.1</Ip>
|
||||
<Status>OK</Status>
|
||||
<CountryCode>NL</CountryCode>
|
||||
<CountryCode3>NLD</CountryCode3>
|
||||
<CountryName>Netherlands</CountryName>
|
||||
<RegionCode>None</RegionCode>
|
||||
<RegionName>None</RegionName>
|
||||
<City>None</City>
|
||||
<ZipPostalCode/>
|
||||
<Latitude>50.0</Latitude>
|
||||
<Longitude>4.0</Longitude>
|
||||
<AreaCode>0</AreaCode>
|
||||
<TimeZone>Europe/Amsterdam</TimeZone>
|
||||
</Response>)";
|
||||
|
||||
void
|
||||
GeoIPTests::testXML()
|
||||
{
|
||||
#ifdef QT_XML_LIB
|
||||
GeoIPXML handler;
|
||||
auto tz = handler.processReply( xml_data_ubiquity );
|
||||
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Europe" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Amsterdam" ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testXML2()
|
||||
{
|
||||
#ifdef QT_XML_LIB
|
||||
static const char data[]
|
||||
= "<Response><TimeZone>America/North Dakota/Beulah</TimeZone></Response>"; // With a space!
|
||||
|
||||
GeoIPXML handler;
|
||||
auto tz = handler.processReply( data );
|
||||
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "North_Dakota/Beulah" ) ); // Without space
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testXMLalt()
|
||||
{
|
||||
#ifdef QT_XML_LIB
|
||||
GeoIPXML handler( "ZT" );
|
||||
|
||||
auto tz = handler.processReply( "<A><B/><C><ZT>Moon/Dark_side</ZT></C></A>" );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Moon" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Dark_side" ) );
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testXMLbad()
|
||||
{
|
||||
#ifdef QT_XML_LIB
|
||||
GeoIPXML handler;
|
||||
auto tz = handler.processReply( "{time_zone: \"Europe/Paris\"}" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = handler.processReply( "<Response></Response>" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = handler.processReply( "fnord<html/>" );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testSplitTZ()
|
||||
{
|
||||
using namespace Calamares::GeoIP;
|
||||
auto tz = splitTZString( QStringLiteral( "Moon/Dark_side" ) );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Moon" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Dark_side" ) );
|
||||
|
||||
// Some providers return weirdly escaped data
|
||||
tz = splitTZString( QStringLiteral( "America\\/NewYork" ) );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "NewYork" ) ); // That's not actually the zone name
|
||||
|
||||
// Check that bogus data fails
|
||||
tz = splitTZString( QString() );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
tz = splitTZString( QStringLiteral( "America.NewYork" ) );
|
||||
QCOMPARE( tz.region(), QString() );
|
||||
|
||||
// Check that three-level is split properly and space is replaced
|
||||
tz = splitTZString( QStringLiteral( "America/North Dakota/Beulah" ) );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "North_Dakota/Beulah" ) );
|
||||
}
|
||||
|
||||
#define CHECK_GET( t, selector, url ) \
|
||||
{ \
|
||||
auto tz = GeoIP##t( selector ).processReply( Calamares::Network::Manager().synchronousGet( QUrl( url ) ) ); \
|
||||
qDebug() << tz; \
|
||||
QCOMPARE( default_tz, tz ); \
|
||||
auto tz2 = Calamares::GeoIP::Handler( "" #t, url, selector ).get(); \
|
||||
qDebug() << tz2; \
|
||||
QCOMPARE( default_tz, tz2 ); \
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testGet()
|
||||
{
|
||||
if ( !QProcessEnvironment::systemEnvironment().contains( QStringLiteral( "TEST_HTTP_GET" ) ) )
|
||||
{
|
||||
qDebug() << "Skipping HTTP GET tests, set TEST_HTTP_GET environment variable to enable";
|
||||
return;
|
||||
}
|
||||
|
||||
GeoIPJSON default_handler;
|
||||
// Call the KDE service the definitive source.
|
||||
auto default_tz = default_handler.processReply(
|
||||
Calamares::Network::Manager().synchronousGet( QUrl( "https://geoip.kde.org/v1/calamares" ) ) );
|
||||
|
||||
// This is bogus, because the test isn't always run by me
|
||||
// QCOMPARE( default_tz.region(), QStringLiteral("Europe") );
|
||||
// QCOMPARE( default_tz.zone(), QStringLiteral("Amsterdam") );
|
||||
QVERIFY( !default_tz.region().isEmpty() );
|
||||
QVERIFY( !default_tz.zone().isEmpty() );
|
||||
|
||||
// Each expansion of CHECK_GET does a synchronous GET, then checks that
|
||||
// the TZ data is the same as the default_tz; this is fragile if the
|
||||
// services don't agree on the location of where the test is run.
|
||||
CHECK_GET( JSON, QString(), "https://geoip.kde.org/v1/calamares" ) // Check it's consistent
|
||||
CHECK_GET( JSON, QStringLiteral( "timezone" ), "https://ipapi.co/json" ) // Different JSON
|
||||
CHECK_GET( JSON, QStringLiteral( "timezone" ), "http://ip-api.com/json" )
|
||||
|
||||
CHECK_GET( JSON, QStringLiteral( "Location.TimeZone" ), "https://geoip.kde.org/debug" ) // 2-level JSON
|
||||
|
||||
#ifdef QT_XML_LIB
|
||||
CHECK_GET( XML, QString(), "http://geoip.ubuntu.com/lookup" ) // Ubiquity's XML format
|
||||
CHECK_GET( XML, QString(), "https://geoip.kde.org/v1/ubiquity" ) // Temporary KDE service
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
GeoIPTests::testFixed()
|
||||
{
|
||||
{
|
||||
GeoIPFixed f;
|
||||
auto tz = f.processReply( QByteArray() );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "Europe" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Amsterdam" ) );
|
||||
|
||||
QCOMPARE( f.processReply( xml_data_ubiquity ), tz );
|
||||
QCOMPARE( f.processReply( QByteArray( "derp" ) ), tz );
|
||||
}
|
||||
{
|
||||
GeoIPFixed f( QStringLiteral( "America/Vancouver" ) );
|
||||
auto tz = f.processReply( QByteArray() );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "Vancouver" ) );
|
||||
|
||||
QCOMPARE( f.processReply( xml_data_ubiquity ), tz );
|
||||
QCOMPARE( f.processReply( QByteArray( "derp" ) ), tz );
|
||||
}
|
||||
{
|
||||
GeoIPFixed f( QStringLiteral( "America/North Dakota/Beulah" ) );
|
||||
auto tz = f.processReply( QByteArray() );
|
||||
QCOMPARE( tz.region(), QStringLiteral( "America" ) );
|
||||
QCOMPARE( tz.zone(), QStringLiteral( "North_Dakota/Beulah" ) );
|
||||
|
||||
QCOMPARE( f.processReply( xml_data_ubiquity ), tz );
|
||||
QCOMPARE( f.processReply( QByteArray( "derp" ) ), tz );
|
||||
}
|
||||
}
|
||||
37
calamares/src/libcalamares/geoip/GeoIPTests.h
Normal file
37
calamares/src/libcalamares/geoip/GeoIPTests.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIPTESTS_H
|
||||
#define GEOIPTESTS_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class GeoIPTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
GeoIPTests();
|
||||
~GeoIPTests() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testFixed();
|
||||
void testJSON();
|
||||
void testJSONalt();
|
||||
void testJSONbad();
|
||||
void testXML();
|
||||
void testXML2();
|
||||
void testXMLalt();
|
||||
void testXMLbad();
|
||||
void testSplitTZ();
|
||||
|
||||
void testGet();
|
||||
};
|
||||
|
||||
#endif
|
||||
92
calamares/src/libcalamares/geoip/GeoIPXML.cpp
Normal file
92
calamares/src/libcalamares/geoip/GeoIPXML.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "GeoIPXML.h"
|
||||
|
||||
#include "compat/Xml.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QtXml/QDomDocument>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
GeoIPXML::GeoIPXML( const QString& element )
|
||||
: Interface( element.isEmpty() ? QStringLiteral( "TimeZone" ) : element )
|
||||
{
|
||||
}
|
||||
|
||||
static QStringList
|
||||
getElementTexts( const QByteArray& data, const QString& tag )
|
||||
{
|
||||
QStringList elements;
|
||||
|
||||
QDomDocument doc;
|
||||
const auto p = Calamares::setXmlContent( doc, data );
|
||||
if ( p.errorMessage.isEmpty() )
|
||||
{
|
||||
const auto tzElements = doc.elementsByTagName( tag );
|
||||
cDebug() << "GeoIP found" << tzElements.length() << "elements";
|
||||
for ( int it = 0; it < tzElements.length(); ++it )
|
||||
{
|
||||
auto e = tzElements.at( it ).toElement();
|
||||
auto e_text = e.text();
|
||||
if ( !e_text.isEmpty() )
|
||||
{
|
||||
elements.append( e_text );
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "GeoIP XML data error:" << p.errorMessage << "(line" << p.errorLine << ':' << p.errorColumn
|
||||
<< ')';
|
||||
}
|
||||
|
||||
if ( elements.count() < 1 )
|
||||
{
|
||||
cWarning() << "GeopIP XML had no non-empty elements" << tag;
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
QString
|
||||
GeoIPXML::rawReply( const QByteArray& data )
|
||||
{
|
||||
for ( const auto& e : getElementTexts( data, m_element ) )
|
||||
{
|
||||
if ( !e.isEmpty() )
|
||||
{
|
||||
return e;
|
||||
}
|
||||
}
|
||||
|
||||
return QString();
|
||||
}
|
||||
|
||||
GeoIP::RegionZonePair
|
||||
GeoIPXML::processReply( const QByteArray& data )
|
||||
{
|
||||
for ( const auto& e : getElementTexts( data, m_element ) )
|
||||
{
|
||||
auto tz = splitTZString( e );
|
||||
if ( tz.isValid() )
|
||||
{
|
||||
return RegionZonePair( tz );
|
||||
}
|
||||
}
|
||||
|
||||
return RegionZonePair();
|
||||
}
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
44
calamares/src/libcalamares/geoip/GeoIPXML.h
Normal file
44
calamares/src/libcalamares/geoip/GeoIPXML.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIP_GEOIPXML_H
|
||||
#define GEOIP_GEOIPXML_H
|
||||
|
||||
#include "Interface.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
/** @brief GeoIP lookup with XML data
|
||||
*
|
||||
* The data is assumed to be in XML format with a
|
||||
* <Response><TimeZone></TimeZone></Response>
|
||||
* element, which contains the text (string) for the region/zone. This
|
||||
* format is expected by, e.g. the Ubiquity installer.
|
||||
*
|
||||
* @note This class is an implementation detail.
|
||||
*/
|
||||
class GeoIPXML : public Interface
|
||||
{
|
||||
public:
|
||||
/** @brief Configure the element tag which is selected.
|
||||
*
|
||||
* If an empty string is passed in (not a valid element tag),
|
||||
* then "TimeZone" is used.
|
||||
*/
|
||||
explicit GeoIPXML( const QString& element = QString() );
|
||||
|
||||
virtual RegionZonePair processReply( const QByteArray& ) override;
|
||||
virtual QString rawReply( const QByteArray& ) override;
|
||||
};
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
176
calamares/src/libcalamares/geoip/Handler.cpp
Normal file
176
calamares/src/libcalamares/geoip/Handler.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Handler.h"
|
||||
|
||||
#include "GeoIPFixed.h"
|
||||
#include "GeoIPJSON.h"
|
||||
#if defined( QT_XML_LIB )
|
||||
#include "GeoIPXML.h"
|
||||
#endif
|
||||
|
||||
#include "Settings.h"
|
||||
#include "network/Manager.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/NamedEnum.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
static const NamedEnumTable< Calamares::GeoIP::Handler::Type >&
|
||||
handlerTypes()
|
||||
{
|
||||
using Type = Calamares::GeoIP::Handler::Type;
|
||||
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
static const NamedEnumTable<Type> names{
|
||||
{ QStringLiteral( "none" ), Type::None },
|
||||
{ QStringLiteral( "json" ), Type::JSON },
|
||||
{ QStringLiteral( "xml" ), Type::XML },
|
||||
{ QStringLiteral( "fixed" ), Type::Fixed }
|
||||
};
|
||||
// *INDENT-ON*
|
||||
// clang-format on
|
||||
|
||||
return names;
|
||||
}
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
Handler::Handler()
|
||||
: m_type( Type::None )
|
||||
{
|
||||
}
|
||||
|
||||
Handler::Handler( const QString& implementation, const QString& url, const QString& selector )
|
||||
: m_type( Type::None )
|
||||
, m_url( url )
|
||||
, m_selector( selector )
|
||||
{
|
||||
bool ok = false;
|
||||
m_type = handlerTypes().find( implementation, ok );
|
||||
if ( !ok )
|
||||
{
|
||||
cWarning() << "GeoIP style" << implementation << "is not recognized.";
|
||||
}
|
||||
else if ( m_type == Type::None )
|
||||
{
|
||||
cWarning() << "GeoIP style *none* does not do anything.";
|
||||
}
|
||||
else if ( m_type == Type::Fixed && Calamares::Settings::instance()
|
||||
&& !Calamares::Settings::instance()->debugMode() )
|
||||
{
|
||||
cWarning() << "GeoIP style *fixed* is not recommended for production.";
|
||||
}
|
||||
#if !defined( QT_XML_LIB )
|
||||
else if ( m_type == Type::XML )
|
||||
{
|
||||
m_type = Type::None;
|
||||
cWarning() << "GeoIP style *xml* is not supported in this version of Calamares.";
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
Handler::~Handler() {}
|
||||
|
||||
static std::unique_ptr< Interface >
|
||||
create_interface( Handler::Type t, const QString& selector )
|
||||
{
|
||||
switch ( t )
|
||||
{
|
||||
case Handler::Type::None:
|
||||
return nullptr;
|
||||
case Handler::Type::JSON:
|
||||
return std::make_unique< GeoIPJSON >( selector );
|
||||
case Handler::Type::XML:
|
||||
#if defined( QT_XML_LIB )
|
||||
return std::make_unique< GeoIPXML >( selector );
|
||||
#else
|
||||
return nullptr;
|
||||
#endif
|
||||
case Handler::Type::Fixed:
|
||||
return std::make_unique< GeoIPFixed >( selector );
|
||||
}
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static RegionZonePair
|
||||
do_query( Handler::Type type, const QString& url, const QString& selector )
|
||||
{
|
||||
const auto interface = create_interface( type, selector );
|
||||
if ( !interface )
|
||||
{
|
||||
return RegionZonePair();
|
||||
}
|
||||
|
||||
using namespace Calamares::Network;
|
||||
return interface->processReply(
|
||||
Calamares::Network::Manager().synchronousGet( url, { RequestOptions::FakeUserAgent } ) );
|
||||
}
|
||||
|
||||
static QString
|
||||
do_raw_query( Handler::Type type, const QString& url, const QString& selector )
|
||||
{
|
||||
const auto interface = create_interface( type, selector );
|
||||
if ( !interface )
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
|
||||
using namespace Calamares::Network;
|
||||
return interface->rawReply(
|
||||
Calamares::Network::Manager().synchronousGet( url, { RequestOptions::FakeUserAgent } ) );
|
||||
}
|
||||
|
||||
RegionZonePair
|
||||
Handler::get() const
|
||||
{
|
||||
if ( !isValid() )
|
||||
{
|
||||
return RegionZonePair();
|
||||
}
|
||||
return do_query( m_type, m_url, m_selector );
|
||||
}
|
||||
|
||||
QFuture< RegionZonePair >
|
||||
Handler::query() const
|
||||
{
|
||||
Handler::Type type = m_type;
|
||||
QString url = m_url;
|
||||
QString selector = m_selector;
|
||||
|
||||
return QtConcurrent::run( [ = ] { return do_query( type, url, selector ); } );
|
||||
}
|
||||
|
||||
QString
|
||||
Handler::getRaw() const
|
||||
{
|
||||
if ( !isValid() )
|
||||
{
|
||||
return QString();
|
||||
}
|
||||
return do_raw_query( m_type, m_url, m_selector );
|
||||
}
|
||||
|
||||
QFuture< QString >
|
||||
Handler::queryRaw() const
|
||||
{
|
||||
Handler::Type type = m_type;
|
||||
QString url = m_url;
|
||||
QString selector = m_selector;
|
||||
|
||||
return QtConcurrent::run( [ = ] { return do_raw_query( type, url, selector ); } );
|
||||
}
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
86
calamares/src/libcalamares/geoip/Handler.h
Normal file
86
calamares/src/libcalamares/geoip/Handler.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIP_HANDLER_H
|
||||
#define GEOIP_HANDLER_H
|
||||
|
||||
#include "Interface.h"
|
||||
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
#include <QtConcurrent/QtConcurrentRun>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
/** @brief Handle one complete GeoIP lookup.
|
||||
*
|
||||
* This class handles one complete GeoIP lookup. Create it with
|
||||
* suitable configuration values, then call get(). This is a
|
||||
* synchronous API and will return an invalid zone pair on
|
||||
* error or if the configuration is not understood. For an
|
||||
* async API, use query().
|
||||
*/
|
||||
class DLLEXPORT Handler
|
||||
{
|
||||
public:
|
||||
enum class Type
|
||||
{
|
||||
None, // No lookup, returns empty string
|
||||
JSON, // JSON-formatted data, returns extracted field
|
||||
XML, // XML-formatted data, returns extracted field
|
||||
Fixed // Returns selector string verbatim
|
||||
};
|
||||
|
||||
/** @brief An unconfigured handler; this always returns errors. */
|
||||
Handler();
|
||||
/** @brief A handler for a specific GeoIP source.
|
||||
*
|
||||
* The @p implementation name selects an implementation; currently JSON and XML
|
||||
* are supported. The @p url is retrieved by query() and then the @p selector
|
||||
* is used to select something from the data returned by the @url.
|
||||
*/
|
||||
Handler( const QString& implementation, const QString& url, const QString& selector );
|
||||
|
||||
~Handler();
|
||||
|
||||
/** @brief Synchronously get the GeoIP result.
|
||||
*
|
||||
* If the Handler is valid, then do the actual fetching and interpretation
|
||||
* of data and return the result. An invalid Handler will return an
|
||||
* invalid (empty) result.
|
||||
*/
|
||||
RegionZonePair get() const;
|
||||
/// @brief Like get, but don't interpret the contents
|
||||
QString getRaw() const;
|
||||
|
||||
/** @brief Asynchronously get the GeoIP result.
|
||||
*
|
||||
* See get() for the return value.
|
||||
*/
|
||||
QFuture< RegionZonePair > query() const;
|
||||
/// @brief Like query, but don't interpret the contents
|
||||
QFuture< QString > queryRaw() const;
|
||||
|
||||
bool isValid() const { return m_type != Type::None; }
|
||||
Type type() const { return m_type; }
|
||||
QString url() const { return m_url; }
|
||||
QString selector() const { return m_selector; }
|
||||
|
||||
private:
|
||||
Type m_type;
|
||||
const QString m_url;
|
||||
const QString m_selector;
|
||||
};
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
46
calamares/src/libcalamares/geoip/Interface.cpp
Normal file
46
calamares/src/libcalamares/geoip/Interface.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Interface.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/String.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
|
||||
Interface::Interface( const QString& e )
|
||||
: m_element( e )
|
||||
{
|
||||
}
|
||||
|
||||
Interface::~Interface() {}
|
||||
|
||||
RegionZonePair
|
||||
splitTZString( const QString& tz )
|
||||
{
|
||||
QString timezoneString( tz );
|
||||
timezoneString.remove( '\\' );
|
||||
timezoneString.replace( ' ', '_' );
|
||||
|
||||
QStringList tzParts = timezoneString.split( '/', SplitSkipEmptyParts );
|
||||
if ( tzParts.size() >= 2 )
|
||||
{
|
||||
QString region = tzParts.takeFirst();
|
||||
QString zone = tzParts.join( '/' );
|
||||
return RegionZonePair( region, zone );
|
||||
}
|
||||
|
||||
return RegionZonePair( QString(), QString() );
|
||||
}
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
127
calamares/src/libcalamares/geoip/Interface.h
Normal file
127
calamares/src/libcalamares/geoip/Interface.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef GEOIP_INTERFACE_H
|
||||
#define GEOIP_INTERFACE_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
|
||||
#include <tuple>
|
||||
|
||||
class QByteArray;
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace GeoIP
|
||||
{
|
||||
/** @brief A Region, Zone pair of strings
|
||||
*
|
||||
* A GeoIP lookup returns a timezone, which is represented as a Region,
|
||||
* Zone pair of strings (e.g. "Europe" and "Amsterdam"). Generally,
|
||||
* pasting the strings back together with a "/" is the right thing to
|
||||
* do. The Zone **may** contain a "/" (e.g. "Kentucky/Monticello").
|
||||
*/
|
||||
class DLLEXPORT RegionZonePair
|
||||
{
|
||||
public:
|
||||
/** @brief Construct from two strings, like qMakePair(). */
|
||||
RegionZonePair( const QString& region, const QString& zone )
|
||||
: m_region( region )
|
||||
, m_zone( zone )
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief Construct from an existing pair. */
|
||||
RegionZonePair( const RegionZonePair& p )
|
||||
: RegionZonePair( p.m_region, p.m_zone )
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief An invalid zone pair (empty strings). */
|
||||
RegionZonePair() = default;
|
||||
|
||||
bool isValid() const { return !m_region.isEmpty(); }
|
||||
|
||||
QString region() const { return m_region; }
|
||||
QString zone() const { return m_zone; }
|
||||
|
||||
friend bool operator==( const RegionZonePair& lhs, const RegionZonePair& rhs ) noexcept
|
||||
{
|
||||
return std::tie( lhs.m_region, lhs.m_zone ) == std::tie( rhs.m_region, rhs.m_zone );
|
||||
}
|
||||
|
||||
QString asString() const { return isValid() ? region() + QChar( '/' ) + zone() : QString(); }
|
||||
|
||||
private:
|
||||
QString m_region;
|
||||
QString m_zone;
|
||||
};
|
||||
|
||||
inline QDebug&
|
||||
operator<<( QDebug&& s, const RegionZonePair& tz )
|
||||
{
|
||||
return s << tz.asString();
|
||||
}
|
||||
|
||||
inline QDebug&
|
||||
operator<<( QDebug& s, const RegionZonePair& tz )
|
||||
{
|
||||
return s << tz.asString();
|
||||
}
|
||||
|
||||
/** @brief Splits a region/zone string into a pair.
|
||||
*
|
||||
* Cleans up the string by removing backslashes (\\)
|
||||
* since some providers return silly-escaped names. Replaces
|
||||
* spaces with _ since some providers return human-readable names.
|
||||
* Splits on the first / in the resulting string, or returns a
|
||||
* pair of empty QStrings if it can't. (e.g. America/North Dakota/Beulah
|
||||
* will return "America", "North_Dakota/Beulah").
|
||||
*/
|
||||
DLLEXPORT RegionZonePair splitTZString( const QString& s );
|
||||
|
||||
/**
|
||||
* @brief Interface for GeoIP retrievers.
|
||||
*
|
||||
* A GeoIP retriever takes a configured URL (from the config file)
|
||||
* and can handle the data returned from its interpretation of that
|
||||
* configured URL, returning a region and zone.
|
||||
*/
|
||||
class DLLEXPORT Interface
|
||||
{
|
||||
public:
|
||||
virtual ~Interface();
|
||||
|
||||
/** @brief Handle a (successful) request by interpreting the data.
|
||||
*
|
||||
* Should return a ( <zone>, <region> ) pair, e.g.
|
||||
* ( "Europe", "Amsterdam" ). This is called **only** if the
|
||||
* request to the fullUrl was successful; the handler
|
||||
* is free to read as much, or as little, data as it
|
||||
* likes. On error, returns a RegionZonePair with empty
|
||||
* strings (e.g. ( "", "" ) ).
|
||||
*/
|
||||
virtual RegionZonePair processReply( const QByteArray& ) = 0;
|
||||
|
||||
/** @brief Get the raw reply data. */
|
||||
virtual QString rawReply( const QByteArray& ) = 0;
|
||||
|
||||
protected:
|
||||
Interface( const QString& element = QString() );
|
||||
|
||||
QString m_element; // string for selecting from data
|
||||
};
|
||||
|
||||
} // namespace GeoIP
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
85
calamares/src/libcalamares/geoip/test_geoip.cpp
Normal file
85
calamares/src/libcalamares/geoip/test_geoip.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2018 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* This is a test-application that does one GeoIP parse.
|
||||
*/
|
||||
|
||||
#include "GeoIPFixed.h"
|
||||
#include "GeoIPJSON.h"
|
||||
#ifdef QT_XML_LIB
|
||||
#include "GeoIPXML.h"
|
||||
#endif
|
||||
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using std::cerr;
|
||||
using namespace Calamares::GeoIP;
|
||||
|
||||
int
|
||||
main( int argc, char** argv )
|
||||
{
|
||||
if ( ( argc != 2 ) && ( argc != 3 ) )
|
||||
{
|
||||
cerr << "Usage: curl url | test_geoip <format> [selector]\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
QString format( argv[ 1 ] );
|
||||
QString selector = argc == 3 ? QString( argv[ 2 ] ) : QString();
|
||||
|
||||
Logger::setupLogLevel( Logger::LOGVERBOSE );
|
||||
cDebug() << "Doing GeoIP interpretation with format=" << format << "selector=" << selector;
|
||||
|
||||
Interface* handler = nullptr;
|
||||
if ( QStringLiteral( "json" ) == format )
|
||||
{
|
||||
handler = new GeoIPJSON( selector );
|
||||
}
|
||||
#ifdef QT_XML_LIB
|
||||
else if ( QStringLiteral( "xml" ) == format )
|
||||
{
|
||||
handler = new GeoIPXML( selector );
|
||||
}
|
||||
#endif
|
||||
else if ( QStringLiteral( "fixed" ) == format )
|
||||
{
|
||||
handler = new GeoIPFixed( selector );
|
||||
}
|
||||
|
||||
if ( !handler )
|
||||
{
|
||||
cerr << "Unknown format '" << format.toLatin1().constData() << "'\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
QByteArray ba;
|
||||
while ( !std::cin.eof() )
|
||||
{
|
||||
char arr[ 1024 ];
|
||||
std::cin.read( arr, sizeof( arr ) );
|
||||
int s = static_cast< int >( std::cin.gcount() );
|
||||
ba.append( arr, s );
|
||||
}
|
||||
|
||||
auto tz = handler->processReply( ba );
|
||||
if ( tz.region().isEmpty() )
|
||||
{
|
||||
std::cout << "No TimeZone determined from input.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "TimeZone Region=" << tz.region().toLatin1().constData()
|
||||
<< "\nTimeZone Zone=" << tz.zone().toLatin1().constData() << '\n';
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
255
calamares/src/libcalamares/locale/CountryData_p.cpp
Normal file
255
calamares/src/libcalamares/locale/CountryData_p.cpp
Normal file
@@ -0,0 +1,255 @@
|
||||
/* GENERATED FILE DO NOT EDIT
|
||||
*
|
||||
* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 1991-2019 Unicode, Inc.
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* This file is derived from CLDR data from Unicode, Inc. Applicable terms
|
||||
* are listed at http://unicode.org/copyright.html , of which the most
|
||||
* important are:
|
||||
*
|
||||
* A. Unicode Copyright
|
||||
* 1. Copyright © 1991-2019 Unicode, Inc. All rights reserved.
|
||||
* B. Definitions
|
||||
* Unicode Data Files ("DATA FILES") include all data files under the directories:
|
||||
* https://www.unicode.org/Public/
|
||||
* C. Terms of Use
|
||||
* 1. Certain documents and files on this website contain a legend indicating
|
||||
* that "Modification is permitted." Any person is hereby authorized,
|
||||
* without fee, to modify such documents and files to create derivative
|
||||
* works conforming to the Unicode® Standard, subject to Terms and
|
||||
* Conditions herein.
|
||||
* 2. Any person is hereby authorized, without fee, to view, use, reproduce,
|
||||
* and distribute all documents and files, subject to the Terms and
|
||||
* Conditions herein.
|
||||
*/
|
||||
|
||||
/* MODIFICATIONS
|
||||
*
|
||||
* Edited anyway:
|
||||
* 20191211 India (IN) changed to AnyLanguage, since Hindi doesn't make sense. #1284
|
||||
* 20210207 Belarus (BY) changed to Russian, as the more-common-language. #1634
|
||||
* 20210615 Tokelau and Tuvalu country enum values changed to avoid deprecation warning.
|
||||
*
|
||||
*/
|
||||
|
||||
// BEGIN Generated from CLDR data
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
|
||||
struct CountryData
|
||||
{
|
||||
QLocale::Language l;
|
||||
QLocale::Country c;
|
||||
char cc1;
|
||||
char cc2;
|
||||
};
|
||||
|
||||
static constexpr int const country_data_size = 198;
|
||||
|
||||
static const CountryData country_data_table[] = {
|
||||
{ QLocale::Language::Catalan, QLocale::Country::Andorra, 'A', 'D' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::UnitedArabEmirates, 'A', 'E' },
|
||||
{ QLocale::Language::Persian, QLocale::Country::Afghanistan, 'A', 'F' },
|
||||
{ QLocale::Language::Albanian, QLocale::Country::Albania, 'A', 'L' },
|
||||
{ QLocale::Language::Armenian, QLocale::Country::Armenia, 'A', 'M' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::Angola, 'A', 'O' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::Antarctica, 'A', 'Q' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Argentina, 'A', 'R' },
|
||||
{ QLocale::Language::Samoan, QLocale::Country::AmericanSamoa, 'A', 'S' },
|
||||
{ QLocale::Language::German, QLocale::Country::Austria, 'A', 'T' },
|
||||
{ QLocale::Language::Dutch, QLocale::Country::Aruba, 'A', 'W' },
|
||||
{ QLocale::Language::Swedish, QLocale::Country::AlandIslands, 'A', 'X' },
|
||||
{ QLocale::Language::Azerbaijani, QLocale::Country::Azerbaijan, 'A', 'Z' },
|
||||
{ QLocale::Language::Bosnian, QLocale::Country::BosniaAndHerzegowina, 'B', 'A' },
|
||||
{ QLocale::Language::Bengali, QLocale::Country::Bangladesh, 'B', 'D' },
|
||||
{ QLocale::Language::Dutch, QLocale::Country::Belgium, 'B', 'E' },
|
||||
{ QLocale::Language::French, QLocale::Country::BurkinaFaso, 'B', 'F' },
|
||||
{ QLocale::Language::Bulgarian, QLocale::Country::Bulgaria, 'B', 'G' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Bahrain, 'B', 'H' },
|
||||
{ QLocale::Language::Rundi, QLocale::Country::Burundi, 'B', 'I' },
|
||||
{ QLocale::Language::French, QLocale::Country::Benin, 'B', 'J' },
|
||||
{ QLocale::Language::French, QLocale::Country::SaintBarthelemy, 'B', 'L' },
|
||||
{ QLocale::Language::Malay, QLocale::Country::Brunei, 'B', 'N' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Bolivia, 'B', 'O' },
|
||||
{ QLocale::Language::Papiamento, QLocale::Country::Bonaire, 'B', 'Q' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::Brazil, 'B', 'R' },
|
||||
{ QLocale::Language::Dzongkha, QLocale::Country::Bhutan, 'B', 'T' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::BouvetIsland, 'B', 'V' },
|
||||
{ QLocale::Language::Russian, QLocale::Country::Belarus, 'B', 'Y' },
|
||||
{ QLocale::Language::Swahili, QLocale::Country::CongoKinshasa, 'C', 'D' },
|
||||
{ QLocale::Language::French, QLocale::Country::CentralAfricanRepublic, 'C', 'F' },
|
||||
{ QLocale::Language::French, QLocale::Country::CongoBrazzaville, 'C', 'G' },
|
||||
{ QLocale::Language::German, QLocale::Country::Switzerland, 'C', 'H' },
|
||||
{ QLocale::Language::French, QLocale::Country::IvoryCoast, 'C', 'I' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Chile, 'C', 'L' },
|
||||
{ QLocale::Language::French, QLocale::Country::Cameroon, 'C', 'M' },
|
||||
{ QLocale::Language::Chinese, QLocale::Country::China, 'C', 'N' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Colombia, 'C', 'O' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::ClippertonIsland, 'C', 'P' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::CostaRica, 'C', 'R' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Cuba, 'C', 'U' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::CapeVerde, 'C', 'V' },
|
||||
{ QLocale::Language::Papiamento, QLocale::Country::CuraSao, 'C', 'W' },
|
||||
{ QLocale::Language::Greek, QLocale::Country::Cyprus, 'C', 'Y' },
|
||||
{ QLocale::Language::Czech, QLocale::Country::CzechRepublic, 'C', 'Z' },
|
||||
{ QLocale::Language::German, QLocale::Country::Germany, 'D', 'E' },
|
||||
{ QLocale::Language::Afar, QLocale::Country::Djibouti, 'D', 'J' },
|
||||
{ QLocale::Language::Danish, QLocale::Country::Denmark, 'D', 'K' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::DominicanRepublic, 'D', 'O' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Algeria, 'D', 'Z' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::CeutaAndMelilla, 'E', 'A' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Ecuador, 'E', 'C' },
|
||||
{ QLocale::Language::Estonian, QLocale::Country::Estonia, 'E', 'E' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Egypt, 'E', 'G' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::WesternSahara, 'E', 'H' },
|
||||
{ QLocale::Language::Tigrinya, QLocale::Country::Eritrea, 'E', 'R' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Spain, 'E', 'S' },
|
||||
{ QLocale::Language::Amharic, QLocale::Country::Ethiopia, 'E', 'T' },
|
||||
{ QLocale::Language::English, QLocale::Country::EuropeanUnion, 'E', 'U' },
|
||||
{ QLocale::Language::German, QLocale::Country::AnyCountry, 'E', 'Z' },
|
||||
{ QLocale::Language::Finnish, QLocale::Country::Finland, 'F', 'I' },
|
||||
{ QLocale::Language::Faroese, QLocale::Country::FaroeIslands, 'F', 'O' },
|
||||
{ QLocale::Language::French, QLocale::Country::France, 'F', 'R' },
|
||||
{ QLocale::Language::French, QLocale::Country::Gabon, 'G', 'A' },
|
||||
{ QLocale::Language::Georgian, QLocale::Country::Georgia, 'G', 'E' },
|
||||
{ QLocale::Language::French, QLocale::Country::FrenchGuiana, 'G', 'F' },
|
||||
{ QLocale::Language::Akan, QLocale::Country::Ghana, 'G', 'H' },
|
||||
{ QLocale::Language::Greenlandic, QLocale::Country::Greenland, 'G', 'L' },
|
||||
{ QLocale::Language::French, QLocale::Country::Guinea, 'G', 'N' },
|
||||
{ QLocale::Language::French, QLocale::Country::Guadeloupe, 'G', 'P' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::EquatorialGuinea, 'G', 'Q' },
|
||||
{ QLocale::Language::Greek, QLocale::Country::Greece, 'G', 'R' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::SouthGeorgiaAndTheSouthSandwichIslands, 'G', 'S' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Guatemala, 'G', 'T' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::GuineaBissau, 'G', 'W' },
|
||||
{ QLocale::Language::Chinese, QLocale::Country::HongKong, 'H', 'K' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::HeardAndMcDonaldIslands, 'H', 'M' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Honduras, 'H', 'N' },
|
||||
{ QLocale::Language::Croatian, QLocale::Country::Croatia, 'H', 'R' },
|
||||
{ QLocale::Language::Haitian, QLocale::Country::Haiti, 'H', 'T' },
|
||||
{ QLocale::Language::Hungarian, QLocale::Country::Hungary, 'H', 'U' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::CanaryIslands, 'I', 'C' },
|
||||
{ QLocale::Language::Indonesian, QLocale::Country::Indonesia, 'I', 'D' },
|
||||
{ QLocale::Language::Hebrew, QLocale::Country::Israel, 'I', 'L' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::India, 'I', 'N' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Iraq, 'I', 'Q' },
|
||||
{ QLocale::Language::Persian, QLocale::Country::Iran, 'I', 'R' },
|
||||
{ QLocale::Language::Icelandic, QLocale::Country::Iceland, 'I', 'S' },
|
||||
{ QLocale::Language::Italian, QLocale::Country::Italy, 'I', 'T' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Jordan, 'J', 'O' },
|
||||
{ QLocale::Language::Japanese, QLocale::Country::Japan, 'J', 'P' },
|
||||
{ QLocale::Language::Swahili, QLocale::Country::Kenya, 'K', 'E' },
|
||||
{ QLocale::Language::Kirghiz, QLocale::Country::Kyrgyzstan, 'K', 'G' },
|
||||
{ QLocale::Language::Khmer, QLocale::Country::Cambodia, 'K', 'H' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Comoros, 'K', 'M' },
|
||||
{ QLocale::Language::Korean, QLocale::Country::NorthKorea, 'K', 'P' },
|
||||
{ QLocale::Language::Korean, QLocale::Country::SouthKorea, 'K', 'R' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Kuwait, 'K', 'W' },
|
||||
{ QLocale::Language::Russian, QLocale::Country::Kazakhstan, 'K', 'Z' },
|
||||
{ QLocale::Language::Lao, QLocale::Country::Laos, 'L', 'A' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Lebanon, 'L', 'B' },
|
||||
{ QLocale::Language::German, QLocale::Country::Liechtenstein, 'L', 'I' },
|
||||
{ QLocale::Language::Sinhala, QLocale::Country::SriLanka, 'L', 'K' },
|
||||
{ QLocale::Language::SouthernSotho, QLocale::Country::Lesotho, 'L', 'S' },
|
||||
{ QLocale::Language::Lithuanian, QLocale::Country::Lithuania, 'L', 'T' },
|
||||
{ QLocale::Language::French, QLocale::Country::Luxembourg, 'L', 'U' },
|
||||
{ QLocale::Language::Latvian, QLocale::Country::Latvia, 'L', 'V' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Libya, 'L', 'Y' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Morocco, 'M', 'A' },
|
||||
{ QLocale::Language::French, QLocale::Country::Monaco, 'M', 'C' },
|
||||
{ QLocale::Language::Romanian, QLocale::Country::Moldova, 'M', 'D' },
|
||||
{ QLocale::Language::Serbian, QLocale::Country::Montenegro, 'M', 'E' },
|
||||
{ QLocale::Language::French, QLocale::Country::SaintMartin, 'M', 'F' },
|
||||
{ QLocale::Language::Malagasy, QLocale::Country::Madagascar, 'M', 'G' },
|
||||
{ QLocale::Language::Macedonian, QLocale::Country::Macedonia, 'M', 'K' },
|
||||
{ QLocale::Language::Bambara, QLocale::Country::Mali, 'M', 'L' },
|
||||
{ QLocale::Language::Burmese, QLocale::Country::Myanmar, 'M', 'M' },
|
||||
{ QLocale::Language::Mongolian, QLocale::Country::Mongolia, 'M', 'N' },
|
||||
{ QLocale::Language::Chinese, QLocale::Country::Macau, 'M', 'O' },
|
||||
{ QLocale::Language::French, QLocale::Country::Martinique, 'M', 'Q' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Mauritania, 'M', 'R' },
|
||||
{ QLocale::Language::Maltese, QLocale::Country::Malta, 'M', 'T' },
|
||||
{ QLocale::Language::Morisyen, QLocale::Country::Mauritius, 'M', 'U' },
|
||||
{ QLocale::Language::Divehi, QLocale::Country::Maldives, 'M', 'V' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Mexico, 'M', 'X' },
|
||||
{ QLocale::Language::Malay, QLocale::Country::Malaysia, 'M', 'Y' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::Mozambique, 'M', 'Z' },
|
||||
{ QLocale::Language::Afrikaans, QLocale::Country::Namibia, 'N', 'A' },
|
||||
{ QLocale::Language::French, QLocale::Country::NewCaledonia, 'N', 'C' },
|
||||
{ QLocale::Language::Hausa, QLocale::Country::Niger, 'N', 'E' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Nicaragua, 'N', 'I' },
|
||||
{ QLocale::Language::Dutch, QLocale::Country::Netherlands, 'N', 'L' },
|
||||
{ QLocale::Language::NorwegianBokmal, QLocale::Country::Norway, 'N', 'O' },
|
||||
{ QLocale::Language::Nepali, QLocale::Country::Nepal, 'N', 'P' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Oman, 'O', 'M' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Panama, 'P', 'A' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Peru, 'P', 'E' },
|
||||
{ QLocale::Language::French, QLocale::Country::FrenchPolynesia, 'P', 'F' },
|
||||
{ QLocale::Language::TokPisin, QLocale::Country::PapuaNewGuinea, 'P', 'G' },
|
||||
{ QLocale::Language::Filipino, QLocale::Country::Philippines, 'P', 'H' },
|
||||
{ QLocale::Language::Urdu, QLocale::Country::Pakistan, 'P', 'K' },
|
||||
{ QLocale::Language::Polish, QLocale::Country::Poland, 'P', 'L' },
|
||||
{ QLocale::Language::French, QLocale::Country::SaintPierreAndMiquelon, 'P', 'M' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::PuertoRico, 'P', 'R' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::PalestinianTerritories, 'P', 'S' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::Portugal, 'P', 'T' },
|
||||
{ QLocale::Language::Palauan, QLocale::Country::Palau, 'P', 'W' },
|
||||
{ QLocale::Language::Guarani, QLocale::Country::Paraguay, 'P', 'Y' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Qatar, 'Q', 'A' },
|
||||
{ QLocale::Language::English, QLocale::Country::OutlyingOceania, 'Q', 'O' },
|
||||
{ QLocale::Language::French, QLocale::Country::Reunion, 'R', 'E' },
|
||||
{ QLocale::Language::Romanian, QLocale::Country::Romania, 'R', 'O' },
|
||||
{ QLocale::Language::Serbian, QLocale::Country::Serbia, 'R', 'S' },
|
||||
{ QLocale::Language::Russian, QLocale::Country::Russia, 'R', 'U' },
|
||||
{ QLocale::Language::Kinyarwanda, QLocale::Country::Rwanda, 'R', 'W' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::SaudiArabia, 'S', 'A' },
|
||||
{ QLocale::Language::French, QLocale::Country::Seychelles, 'S', 'C' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Sudan, 'S', 'D' },
|
||||
{ QLocale::Language::Swedish, QLocale::Country::Sweden, 'S', 'E' },
|
||||
{ QLocale::Language::Slovenian, QLocale::Country::Slovenia, 'S', 'I' },
|
||||
{ QLocale::Language::NorwegianBokmal, QLocale::Country::SvalbardAndJanMayenIslands, 'S', 'J' },
|
||||
{ QLocale::Language::Slovak, QLocale::Country::Slovakia, 'S', 'K' },
|
||||
{ QLocale::Language::Italian, QLocale::Country::SanMarino, 'S', 'M' },
|
||||
{ QLocale::Language::French, QLocale::Country::Senegal, 'S', 'N' },
|
||||
{ QLocale::Language::Somali, QLocale::Country::Somalia, 'S', 'O' },
|
||||
{ QLocale::Language::Dutch, QLocale::Country::Suriname, 'S', 'R' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::SaoTomeAndPrincipe, 'S', 'T' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::ElSalvador, 'S', 'V' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Syria, 'S', 'Y' },
|
||||
{ QLocale::Language::French, QLocale::Country::Chad, 'T', 'D' },
|
||||
{ QLocale::Language::French, QLocale::Country::FrenchSouthernTerritories, 'T', 'F' },
|
||||
{ QLocale::Language::French, QLocale::Country::Togo, 'T', 'G' },
|
||||
{ QLocale::Language::Thai, QLocale::Country::Thailand, 'T', 'H' },
|
||||
{ QLocale::Language::Tajik, QLocale::Country::Tajikistan, 'T', 'J' },
|
||||
{ QLocale::Language::TokelauLanguage, QLocale::Country::TokelauCountry, 'T', 'K' },
|
||||
{ QLocale::Language::Portuguese, QLocale::Country::EastTimor, 'T', 'L' },
|
||||
{ QLocale::Language::Turkmen, QLocale::Country::Turkmenistan, 'T', 'M' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Tunisia, 'T', 'N' },
|
||||
{ QLocale::Language::Tongan, QLocale::Country::Tonga, 'T', 'O' },
|
||||
{ QLocale::Language::Turkish, QLocale::Country::Turkey, 'T', 'R' },
|
||||
{ QLocale::Language::TuvaluLanguage, QLocale::Country::TuvaluCountry, 'T', 'V' },
|
||||
{ QLocale::Language::Chinese, QLocale::Country::Taiwan, 'T', 'W' },
|
||||
{ QLocale::Language::Swahili, QLocale::Country::Tanzania, 'T', 'Z' },
|
||||
{ QLocale::Language::Ukrainian, QLocale::Country::Ukraine, 'U', 'A' },
|
||||
{ QLocale::Language::Swahili, QLocale::Country::Uganda, 'U', 'G' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Uruguay, 'U', 'Y' },
|
||||
{ QLocale::Language::Uzbek, QLocale::Country::Uzbekistan, 'U', 'Z' },
|
||||
{ QLocale::Language::Italian, QLocale::Country::VaticanCityState, 'V', 'A' },
|
||||
{ QLocale::Language::Spanish, QLocale::Country::Venezuela, 'V', 'E' },
|
||||
{ QLocale::Language::Vietnamese, QLocale::Country::Vietnam, 'V', 'N' },
|
||||
{ QLocale::Language::Bislama, QLocale::Country::Vanuatu, 'V', 'U' },
|
||||
{ QLocale::Language::French, QLocale::Country::WallisAndFutunaIslands, 'W', 'F' },
|
||||
{ QLocale::Language::Samoan, QLocale::Country::Samoa, 'W', 'S' },
|
||||
{ QLocale::Language::Albanian, QLocale::Country::Kosovo, 'X', 'K' },
|
||||
{ QLocale::Language::Arabic, QLocale::Country::Yemen, 'Y', 'E' },
|
||||
{ QLocale::Language::French, QLocale::Country::Mayotte, 'Y', 'T' },
|
||||
{ QLocale::Language::Shona, QLocale::Country::Zimbabwe, 'Z', 'W' },
|
||||
{ QLocale::Language::AnyLanguage, QLocale::Country::AnyCountry, 0, 0 },
|
||||
};
|
||||
|
||||
static_assert( (sizeof(country_data_table) / sizeof(CountryData)) == country_data_size, "Table size mismatch for CountryData" );
|
||||
|
||||
// END Generated from CLDR data
|
||||
91
calamares/src/libcalamares/locale/Global.cpp
Normal file
91
calamares/src/libcalamares/locale/Global.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#include "Global.h"
|
||||
|
||||
#include "GlobalStorage.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
static const char gsKey[] = "localeConf";
|
||||
|
||||
template < typename T >
|
||||
void
|
||||
insertGS( const QMap< QString, T >& values, QVariantMap& localeConf )
|
||||
{
|
||||
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
|
||||
{
|
||||
localeConf.insert( it.key(), it.value() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
insertGS( Calamares::GlobalStorage& gs, const QMap< QString, QString >& values, InsertMode mode )
|
||||
{
|
||||
QVariantMap localeConf = mode == InsertMode::Overwrite ? QVariantMap() : gs.value( gsKey ).toMap();
|
||||
insertGS( values, localeConf );
|
||||
gs.insert( gsKey, localeConf );
|
||||
}
|
||||
|
||||
void
|
||||
insertGS( Calamares::GlobalStorage& gs, const QVariantMap& values, InsertMode mode )
|
||||
{
|
||||
QVariantMap localeConf = mode == InsertMode::Overwrite ? QVariantMap() : gs.value( gsKey ).toMap();
|
||||
insertGS( values, localeConf );
|
||||
gs.insert( gsKey, localeConf );
|
||||
}
|
||||
|
||||
void
|
||||
insertGS( Calamares::GlobalStorage& gs, const QString& key, const QString& value )
|
||||
{
|
||||
QVariantMap localeConf = gs.value( gsKey ).toMap();
|
||||
localeConf.insert( key, value );
|
||||
gs.insert( gsKey, localeConf );
|
||||
}
|
||||
|
||||
void
|
||||
removeGS( Calamares::GlobalStorage& gs, const QString& key )
|
||||
{
|
||||
if ( gs.contains( gsKey ) )
|
||||
{
|
||||
QVariantMap localeConf = gs.value( gsKey ).toMap();
|
||||
if ( localeConf.contains( key ) )
|
||||
{
|
||||
localeConf.remove( key );
|
||||
gs.insert( gsKey, localeConf );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
clearGS( Calamares::GlobalStorage& gs )
|
||||
{
|
||||
gs.remove( gsKey );
|
||||
}
|
||||
|
||||
QString
|
||||
readGS( Calamares::GlobalStorage& gs, const QString& key )
|
||||
{
|
||||
if ( gs.contains( gsKey ) )
|
||||
{
|
||||
QVariantMap localeConf = gs.value( gsKey ).toMap();
|
||||
if ( localeConf.contains( key ) )
|
||||
{
|
||||
return localeConf.value( key ).toString();
|
||||
}
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
83
calamares/src/libcalamares/locale/Global.h
Normal file
83
calamares/src/libcalamares/locale/Global.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/** @file GlobalStorage management for Locale settings
|
||||
*
|
||||
* The *localeConf* key in Global Storage is semi-structured,
|
||||
* and there are multiple modules that write to it (and some that
|
||||
* read from it). Functions in this file provide access to
|
||||
* that semi-structured data.
|
||||
*/
|
||||
|
||||
#ifndef LOCALE_GLOBAL_H
|
||||
#define LOCALE_GLOBAL_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
class GlobalStorage;
|
||||
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
/** @brief Selector for methods that insert multiple values.
|
||||
*
|
||||
* When inserting, use @c Overwrite to remove all keys not in the collection
|
||||
* of values being inserted; use @c Merge to preserve whatever is
|
||||
* already in Global Storage but not mentioned in the collection.
|
||||
*/
|
||||
enum class InsertMode
|
||||
{
|
||||
Overwrite,
|
||||
Merge
|
||||
};
|
||||
|
||||
/** @brief Insert the given @p values into the *localeConf* map in @p gs
|
||||
*
|
||||
* @param gs The Global Storage to write to
|
||||
* @param values The collection of keys and values to write to @p gs
|
||||
* @param mode Indicates whether the *localeConf* key is cleared first
|
||||
*
|
||||
* The keys in the collection @p values should be first-level keys
|
||||
* in *localeConf*, e.g. "LANG" or "LC_TIME". No effort is made to
|
||||
* enforce this.
|
||||
*/
|
||||
DLLEXPORT void insertGS( Calamares::GlobalStorage& gs, const QVariantMap& values, InsertMode mode = InsertMode::Merge );
|
||||
/** @brief Insert the given @p values into the *localeConf* map in @p gs
|
||||
*
|
||||
* Alternate way of providing the keys and values.
|
||||
*/
|
||||
DLLEXPORT void
|
||||
insertGS( Calamares::GlobalStorage& gs, const QMap< QString, QString >& values, InsertMode mode = InsertMode::Merge );
|
||||
/** @brief Write a single @p key and @p value to the *localeConf* map
|
||||
*/
|
||||
DLLEXPORT void insertGS( Calamares::GlobalStorage& gs, const QString& key, const QString& value );
|
||||
/** @brief Remove a single @p key from the *localeConf* map
|
||||
*/
|
||||
DLLEXPORT void removeGS( Calamares::GlobalStorage& gs, const QString& key );
|
||||
/** @brief Remove the *localeConf* map from Global Storage
|
||||
*/
|
||||
DLLEXPORT void clearGS( Calamares::GlobalStorage& gs );
|
||||
|
||||
/** @brief Gets a value from the *localeConf* map in @p gs
|
||||
*
|
||||
* If the key is not set (or doesn't exist), returns QString().
|
||||
*/
|
||||
DLLEXPORT QString readGS( Calamares::GlobalStorage& gs, const QString& key );
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
98
calamares/src/libcalamares/locale/Lookup.cpp
Normal file
98
calamares/src/libcalamares/locale/Lookup.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Lookup.h"
|
||||
|
||||
#include "CountryData_p.cpp"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
struct TwoChar
|
||||
{
|
||||
TwoChar( const QString& code )
|
||||
: cc1( 0 )
|
||||
, cc2( 0 )
|
||||
{
|
||||
if ( code.length() == 2 )
|
||||
{
|
||||
cc1 = code[ 0 ].toLatin1();
|
||||
cc2 = code[ 1 ].toLatin1();
|
||||
}
|
||||
}
|
||||
|
||||
char cc1;
|
||||
char cc2;
|
||||
};
|
||||
|
||||
static const CountryData*
|
||||
lookup( TwoChar c )
|
||||
{
|
||||
if ( !c.cc1 )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const CountryData* p
|
||||
= std::find_if( country_data_table,
|
||||
country_data_table + country_data_size,
|
||||
[ c = c ]( const CountryData& d ) { return ( d.cc1 == c.cc1 ) && ( d.cc2 == c.cc2 ); } );
|
||||
if ( p == country_data_table + country_data_size )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
QLocale::Country
|
||||
countryForCode( const QString& code )
|
||||
{
|
||||
const CountryData* p = lookup( TwoChar( code ) );
|
||||
return p ? p->c : QLocale::Country::AnyCountry;
|
||||
}
|
||||
|
||||
QLocale::Language
|
||||
languageForCountry( const QString& code )
|
||||
{
|
||||
const CountryData* p = lookup( TwoChar( code ) );
|
||||
return p ? p->l : QLocale::Language::AnyLanguage;
|
||||
}
|
||||
|
||||
QPair< QLocale::Country, QLocale::Language >
|
||||
countryData( const QString& code )
|
||||
{
|
||||
const CountryData* p = lookup( TwoChar( code ) );
|
||||
return p ? qMakePair( p->c, p->l ) : qMakePair( QLocale::Country::AnyCountry, QLocale::Language::AnyLanguage );
|
||||
}
|
||||
|
||||
QLocale
|
||||
countryLocale( const QString& code )
|
||||
{
|
||||
auto p = countryData( code );
|
||||
return QLocale( p.second, p.first );
|
||||
}
|
||||
|
||||
QLocale::Language
|
||||
languageForCountry( QLocale::Country country )
|
||||
{
|
||||
const CountryData* p = std::find_if( country_data_table,
|
||||
country_data_table + country_data_size,
|
||||
[ c = country ]( const CountryData& d ) { return d.c == c; } );
|
||||
if ( p == country_data_table + country_data_size )
|
||||
{
|
||||
return QLocale::Language::AnyLanguage;
|
||||
}
|
||||
return p->l;
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
47
calamares/src/libcalamares/locale/Lookup.h
Normal file
47
calamares/src/libcalamares/locale/Lookup.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LOCALE_LOOKUP_H
|
||||
#define LOCALE_LOOKUP_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QPair>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
/* All the functions in this file do lookups of locale data
|
||||
* based on CLDR tables; these are lookups that you can't (easily)
|
||||
* do with just QLocale (e.g. from 2-letter country code to a likely
|
||||
* locale).
|
||||
*/
|
||||
|
||||
/// @brief Map a 2-letter code to a Country, or AnyCountry if not found
|
||||
DLLEXPORT QLocale::Country countryForCode( const QString& code );
|
||||
/** @brief Map a Country to a Language, or AnyLanguage if not found
|
||||
*
|
||||
* This is a *likely* language for the given country, based on the
|
||||
* CLDR tables. For instance, this maps Belgium to Dutch.
|
||||
*/
|
||||
DLLEXPORT QLocale::Language languageForCountry( QLocale::Country country );
|
||||
/// @brief Map a 2-letter code to a Language, or AnyLanguage if not found
|
||||
DLLEXPORT QLocale::Language languageForCountry( const QString& code );
|
||||
|
||||
/// @brief Get both Country and Language for a 2-letter code
|
||||
DLLEXPORT QPair< QLocale::Country, QLocale::Language > countryData( const QString& code );
|
||||
/// @brief Get a likely locale for a 2-letter country code
|
||||
DLLEXPORT QLocale countryLocale( const QString& code );
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
553
calamares/src/libcalamares/locale/Tests.cpp
Normal file
553
calamares/src/libcalamares/locale/Tests.cpp
Normal file
@@ -0,0 +1,553 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "locale/Global.h"
|
||||
#include "locale/TimeZone.h"
|
||||
#include "locale/TranslatableConfiguration.h"
|
||||
#include "locale/TranslationsModel.h"
|
||||
|
||||
#include "CalamaresVersion.h"
|
||||
#include "GlobalStorage.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Retranslator.h"
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class LocaleTests : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
LocaleTests();
|
||||
~LocaleTests() override;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void testLanguageModelCount();
|
||||
void testTranslatableLanguages();
|
||||
void testTranslatableConfig1();
|
||||
void testTranslatableConfig2();
|
||||
void testTranslatableConfigContext();
|
||||
void testLanguageScripts();
|
||||
|
||||
void testEsperanto();
|
||||
void testInterlingue();
|
||||
|
||||
// TimeZone testing
|
||||
void testRegions();
|
||||
void testSimpleZones();
|
||||
void testComplexZones();
|
||||
void testTZLookup();
|
||||
void testTZIterator();
|
||||
void testLocationLookup_data();
|
||||
void testLocationLookup();
|
||||
void testLocationLookup2();
|
||||
|
||||
// Global Storage updates
|
||||
void testGSUpdates();
|
||||
};
|
||||
|
||||
LocaleTests::LocaleTests() {}
|
||||
|
||||
LocaleTests::~LocaleTests() {}
|
||||
|
||||
void
|
||||
LocaleTests::initTestCase()
|
||||
{
|
||||
Logger::setupLogLevel( Logger::LOGDEBUG );
|
||||
|
||||
// Otherwise plain get() is dubious in the TranslatableConfiguration tests
|
||||
QLocale::setDefault( QLocale( QStringLiteral( "en_US" ) ) );
|
||||
QVERIFY( ( QLocale().name() == "C" ) || ( QLocale().name() == "en_US" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testLanguageModelCount()
|
||||
{
|
||||
const auto* m = Calamares::Locale::availableTranslations();
|
||||
|
||||
QVERIFY( m );
|
||||
QVERIFY( m->rowCount( QModelIndex() ) > 1 );
|
||||
|
||||
int dutch = m->find( QLocale( "nl_NL" ) );
|
||||
QVERIFY( dutch > 0 );
|
||||
QCOMPARE( m->find( "NL" ), dutch );
|
||||
// must be capitals
|
||||
QCOMPARE( m->find( "nl" ), -1 );
|
||||
QCOMPARE( m->find( QLocale( "nl" ) ), dutch );
|
||||
|
||||
// Belgium speaks Dutch as well
|
||||
QCOMPARE( m->find( "BE" ), dutch );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testLanguageScripts()
|
||||
{
|
||||
const auto* m = Calamares::Locale::availableTranslations();
|
||||
|
||||
QVERIFY( m );
|
||||
|
||||
// Cursory test that all the locales found have a sensible language,
|
||||
// and that some specific languages have sensible corresponding data.
|
||||
//
|
||||
// This fails on Esperanto (or, if Esperanto is added to Qt, then
|
||||
// this will pass and the test after the loop will fail.
|
||||
for ( int i = 0; i < m->rowCount( QModelIndex() ); ++i )
|
||||
{
|
||||
const auto& label = m->locale( i );
|
||||
const auto locale = label.locale();
|
||||
cDebug() << label.label() << locale;
|
||||
|
||||
QVERIFY( locale.language() == QLocale::Greek ? locale.script() == QLocale::GreekScript : true );
|
||||
QVERIFY( locale.language() == QLocale::Korean ? locale.script() == QLocale::KoreanScript : true );
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
QVERIFY( locale.language() == QLocale::Lithuanian ? locale.country() == QLocale::Lithuania : true );
|
||||
#else
|
||||
QVERIFY( locale.language() == QLocale::Lithuanian ? locale.territory() == QLocale::Lithuania : true );
|
||||
#endif
|
||||
QVERIFY( locale.language() != QLocale::C );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testEsperanto()
|
||||
{
|
||||
QCOMPARE( QLocale( "eo" ).language(), QLocale::Esperanto );
|
||||
QCOMPARE( QLocale( QLocale::Esperanto ).language(), QLocale::Esperanto ); // Probably fails on 5.12, too
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testInterlingue()
|
||||
{
|
||||
#if CALAMARES_QT_SUPPORT_INTERLINGUE
|
||||
// ie was fixed in version ... of Qt
|
||||
QCOMPARE( QLocale( "ie" ).language(), QLocale::Interlingue );
|
||||
QCOMPARE( QLocale( QLocale::Interlingue ).language(), QLocale::Interlingue );
|
||||
#else
|
||||
// ie / Interlingue is borked (is "ie" even the right name?)
|
||||
QCOMPARE( QLocale( "ie" ).language(), QLocale::C );
|
||||
QCOMPARE( QLocale( QLocale::Interlingue ).language(), QLocale::English );
|
||||
#endif
|
||||
|
||||
// "ia" exists (post-war variant of Interlingue)
|
||||
QCOMPARE( QLocale( "ia" ).language(), QLocale::Interlingua );
|
||||
// "bork" does not exist
|
||||
QCOMPARE( QLocale( "bork" ).language(), QLocale::C );
|
||||
}
|
||||
|
||||
static const QStringList&
|
||||
someLanguages()
|
||||
{
|
||||
static QStringList languages { "nl", "de", "da", "nb", "sr@latin", "ar", "ru" };
|
||||
return languages;
|
||||
}
|
||||
|
||||
/** @brief Check consistency of test data
|
||||
* Check that all the languages used in testing, are actually enabled
|
||||
* in Calamares translations.
|
||||
*/
|
||||
void
|
||||
LocaleTests::testTranslatableLanguages()
|
||||
{
|
||||
cDebug() << "Translation languages:" << Calamares::Locale::availableLanguages();
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
// Could be QVERIFY, but then we don't see what language code fails
|
||||
QCOMPARE( Calamares::Locale::availableLanguages().contains( language ) ? language : QString(), language );
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Test strings with no translations
|
||||
*/
|
||||
void
|
||||
LocaleTests::testTranslatableConfig1()
|
||||
{
|
||||
Calamares::Locale::TranslatedString ts0;
|
||||
QVERIFY( ts0.isEmpty() );
|
||||
QCOMPARE( ts0.count(), 1 ); // the empty string
|
||||
|
||||
Calamares::Locale::TranslatedString ts1( "Hello" );
|
||||
QCOMPARE( ts1.count(), 1 );
|
||||
QVERIFY( !ts1.isEmpty() );
|
||||
|
||||
QCOMPARE( ts1.get(), QStringLiteral( "Hello" ) );
|
||||
QCOMPARE( ts1.get( QLocale( "nl" ) ), QStringLiteral( "Hello" ) );
|
||||
|
||||
QVariantMap map;
|
||||
map.insert( "description", "description (no language)" );
|
||||
Calamares::Locale::TranslatedString ts2( map, "description" );
|
||||
QCOMPARE( ts2.count(), 1 );
|
||||
QVERIFY( !ts2.isEmpty() );
|
||||
|
||||
QCOMPARE( ts2.get(), QStringLiteral( "description (no language)" ) );
|
||||
QCOMPARE( ts2.get( QLocale( "nl" ) ), QStringLiteral( "description (no language)" ) );
|
||||
}
|
||||
|
||||
/** @bref Test strings with translations.
|
||||
*/
|
||||
void
|
||||
LocaleTests::testTranslatableConfig2()
|
||||
{
|
||||
QVariantMap map;
|
||||
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
map.insert( QString( "description[%1]" ).arg( language ),
|
||||
QString( "description (language %1)" ).arg( language ) );
|
||||
if ( language != "nl" )
|
||||
{
|
||||
map.insert( QString( "name[%1]" ).arg( language ), QString( "name (language %1)" ).arg( language ) );
|
||||
}
|
||||
}
|
||||
|
||||
// If there's no untranslated string in the map, it is considered empty
|
||||
Calamares::Locale::TranslatedString ts0( map, "description" );
|
||||
QVERIFY( ts0.isEmpty() ); // Because no untranslated string
|
||||
QCOMPARE( ts0.count(),
|
||||
someLanguages().count() + 1 ); // But there are entries for the translations, plus an empty string
|
||||
|
||||
// expand the map with untranslated entries
|
||||
map.insert( QString( "description" ), "description (no language)" );
|
||||
map.insert( QString( "name" ), "name (no language)" );
|
||||
|
||||
Calamares::Locale::TranslatedString ts1( map, "description" );
|
||||
// The +1 is because "" is always also inserted
|
||||
QCOMPARE( ts1.count(), someLanguages().count() + 1 );
|
||||
QVERIFY( !ts1.isEmpty() );
|
||||
|
||||
QCOMPARE( ts1.get(), QStringLiteral( "description (no language)" ) ); // it wasn't set
|
||||
QCOMPARE( ts1.get( QLocale( "nl" ) ), QStringLiteral( "description (language nl)" ) );
|
||||
for ( const auto& language : someLanguages() )
|
||||
{
|
||||
// Skip Serbian (latin) because QLocale() constructed with it
|
||||
// doesn't retain the @latin part.
|
||||
if ( language == "sr@latin" )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Could be QVERIFY, but then we don't see what language code fails
|
||||
QCOMPARE( ts1.get( QLocale( language ) ) == QString( "description (language %1)" ).arg( language ) ? language
|
||||
: QString(),
|
||||
language );
|
||||
}
|
||||
QCOMPARE( ts1.get( QLocale( QLocale::Language::Serbian, QLocale::Script::LatinScript, QLocale::Country::Serbia ) ),
|
||||
QStringLiteral( "description (language sr@latin)" ) );
|
||||
|
||||
Calamares::Locale::TranslatedString ts2( map, "name" );
|
||||
// We skipped dutch this time
|
||||
QCOMPARE( ts2.count(), someLanguages().count() );
|
||||
QVERIFY( !ts2.isEmpty() );
|
||||
|
||||
// This key doesn't exist
|
||||
Calamares::Locale::TranslatedString ts3( map, "front" );
|
||||
QVERIFY( ts3.isEmpty() );
|
||||
QCOMPARE( ts3.count(), 1 ); // The empty string
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testTranslatableConfigContext()
|
||||
{
|
||||
using TS = Calamares::Locale::TranslatedString;
|
||||
|
||||
const QString original( "Quit" );
|
||||
TS quitUntranslated( original );
|
||||
TS quitTranslated( original, metaObject()->className() );
|
||||
|
||||
QCOMPARE( quitUntranslated.get(), original );
|
||||
QCOMPARE( quitTranslated.get(), original );
|
||||
|
||||
// Load translation data from QRC
|
||||
QVERIFY( QFile::exists( ":/lang/localetest_nl.qm" ) );
|
||||
QTranslator t;
|
||||
QVERIFY( t.load( QString( ":/lang/localetest_nl" ) ) );
|
||||
QCoreApplication::installTranslator( &t );
|
||||
|
||||
// Translation doesn't affect the one without context
|
||||
QCOMPARE( quitUntranslated.get(), original );
|
||||
// But the translation **does** affect this class' context
|
||||
QCOMPARE( quitTranslated.get(), QStringLiteral( "Ophouden" ) );
|
||||
QCOMPARE( tr( "Quit" ), QStringLiteral( "Ophouden" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testRegions()
|
||||
{
|
||||
using namespace Calamares::Locale;
|
||||
RegionsModel regions;
|
||||
|
||||
QVERIFY( regions.rowCount( QModelIndex() ) > 3 ); // Africa, America, Asia
|
||||
|
||||
QStringList names;
|
||||
for ( int i = 0; i < regions.rowCount( QModelIndex() ); ++i )
|
||||
{
|
||||
QVariant name = regions.data( regions.index( i ), RegionsModel::NameRole );
|
||||
QVERIFY( name.isValid() );
|
||||
QVERIFY( !name.toString().isEmpty() );
|
||||
names.append( name.toString() );
|
||||
}
|
||||
|
||||
QVERIFY( names.contains( "America" ) );
|
||||
QVERIFY( !names.contains( "UTC" ) );
|
||||
}
|
||||
|
||||
static void
|
||||
displayedNames( QAbstractItemModel& model, QStringList& names )
|
||||
{
|
||||
names.clear();
|
||||
for ( int i = 0; i < model.rowCount( QModelIndex() ); ++i )
|
||||
{
|
||||
QVariant name = model.data( model.index( i, 0 ), Qt::DisplayRole );
|
||||
QVERIFY( name.isValid() );
|
||||
QVERIFY( !name.toString().isEmpty() );
|
||||
names.append( name.toString() );
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testSimpleZones()
|
||||
{
|
||||
using namespace Calamares::Locale;
|
||||
ZonesModel zones;
|
||||
|
||||
QVERIFY( zones.rowCount( QModelIndex() ) > 24 );
|
||||
|
||||
QStringList names;
|
||||
displayedNames( zones, names );
|
||||
QVERIFY( names.contains( "Amsterdam" ) );
|
||||
if ( !names.contains( "New York" ) )
|
||||
{
|
||||
for ( const auto& s : names )
|
||||
{
|
||||
if ( s.startsWith( 'N' ) )
|
||||
{
|
||||
cDebug() << s;
|
||||
}
|
||||
}
|
||||
}
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "America" ) );
|
||||
QVERIFY( !names.contains( "New_York" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testComplexZones()
|
||||
{
|
||||
using namespace Calamares::Locale;
|
||||
ZonesModel zones;
|
||||
RegionalZonesModel europe( &zones );
|
||||
|
||||
QStringList names;
|
||||
displayedNames( zones, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
|
||||
// No region set
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
|
||||
// Now filter
|
||||
europe.setRegion( "Europe" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( !names.contains( "New York" ) );
|
||||
QVERIFY( names.contains( "Prague" ) );
|
||||
QVERIFY( !names.contains( "Abidjan" ) );
|
||||
|
||||
europe.setRegion( "America" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "Prague" ) );
|
||||
QVERIFY( !names.contains( "Abidjan" ) );
|
||||
|
||||
europe.setRegion( "Africa" );
|
||||
displayedNames( europe, names );
|
||||
QVERIFY( !names.contains( "New York" ) );
|
||||
QVERIFY( !names.contains( "Prague" ) );
|
||||
QVERIFY( names.contains( "Abidjan" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testTZLookup()
|
||||
{
|
||||
using namespace Calamares::Locale;
|
||||
ZonesModel zones;
|
||||
|
||||
QVERIFY( zones.find( "America", "New_York" ) );
|
||||
QCOMPARE( zones.find( "America", "New_York" )->zone(), QStringLiteral( "New_York" ) );
|
||||
QCOMPARE( zones.find( "America", "New_York" )->translated(), QStringLiteral( "New York" ) );
|
||||
|
||||
QVERIFY( !zones.find( "Europe", "New_York" ) );
|
||||
QVERIFY( !zones.find( "America", "New York" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testTZIterator()
|
||||
{
|
||||
using namespace Calamares::Locale;
|
||||
const ZonesModel zones;
|
||||
|
||||
QVERIFY( zones.find( "Europe", "Rome" ) );
|
||||
|
||||
int count = 0;
|
||||
bool seenRome = false;
|
||||
bool seenGnome = false;
|
||||
for ( auto it = zones.begin(); it; ++it )
|
||||
{
|
||||
QVERIFY( *it );
|
||||
QVERIFY( !( *it )->zone().isEmpty() );
|
||||
seenRome |= ( *it )->zone() == QStringLiteral( "Rome" );
|
||||
seenGnome |= ( *it )->zone() == QStringLiteral( "Gnome" );
|
||||
count++;
|
||||
}
|
||||
|
||||
QVERIFY( seenRome );
|
||||
QVERIFY( !seenGnome );
|
||||
QCOMPARE( count, zones.rowCount( QModelIndex() ) );
|
||||
|
||||
QCOMPARE( zones.data( zones.index( 0 ), ZonesModel::RegionRole ).toString(), QStringLiteral( "Africa" ) );
|
||||
QCOMPARE( ( *zones.begin() )->zone(), QStringLiteral( "Abidjan" ) );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testLocationLookup_data()
|
||||
{
|
||||
QTest::addColumn< double >( "latitude" );
|
||||
QTest::addColumn< double >( "longitude" );
|
||||
QTest::addColumn< QString >( "name" );
|
||||
|
||||
QTest::newRow( "London" ) << 50.0 << 0.0 << QString( "London" );
|
||||
QTest::newRow( "Tarawa E" ) << 0.0 << 179.0 << QString( "Tarawa" );
|
||||
QTest::newRow( "Tarawa W" ) << 0.0 << -179.0 << QString( "Tarawa" );
|
||||
|
||||
QTest::newRow( "Johannesburg" ) << -26.0 << 28.0 << QString( "Johannesburg" ); // South Africa
|
||||
QTest::newRow( "Maseru" ) << -29.0 << 27.0 << QString( "Maseru" ); // Lesotho
|
||||
QTest::newRow( "Windhoek" ) << -22.0 << 17.0 << QString( "Windhoek" ); // Namibia
|
||||
QTest::newRow( "Port Elisabeth" ) << -33.0 << 25.0 << QString( "Johannesburg" ); // South Africa
|
||||
QTest::newRow( "Cape Town" ) << -33.0 << 18.0 << QString( "Johannesburg" ); // South Africa
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testLocationLookup()
|
||||
{
|
||||
const Calamares::Locale::ZonesModel zones;
|
||||
|
||||
QFETCH( double, latitude );
|
||||
QFETCH( double, longitude );
|
||||
QFETCH( QString, name );
|
||||
|
||||
const auto* zone = zones.find( latitude, longitude );
|
||||
QVERIFY( zone );
|
||||
QCOMPARE( zone->zone(), name );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testLocationLookup2()
|
||||
{
|
||||
// Official
|
||||
// ZA -2615+02800 Africa/Johannesburg
|
||||
// Spot patch
|
||||
// "ZA -3230+02259 Africa/Johannesburg\n";
|
||||
|
||||
const Calamares::Locale::ZonesModel zones;
|
||||
const auto* zone = zones.find( -26.15, 28.00 );
|
||||
QCOMPARE( zone->zone(), QString( "Johannesburg" ) );
|
||||
// The TZ data sources use minutes-and-seconds notation,
|
||||
// so "2615" is 26 degrees, 15 minutes, and 15 minutes is
|
||||
// one-quarter of a degree.
|
||||
QCOMPARE( zone->latitude(), -26.25 );
|
||||
QCOMPARE( zone->longitude(), 28.00 );
|
||||
|
||||
// Elsewhere in South Africa
|
||||
const auto* altzone = zones.find( -32.0, 22.0 );
|
||||
QCOMPARE( altzone, zone ); // same pointer
|
||||
QCOMPARE( altzone->zone(), QString( "Johannesburg" ) );
|
||||
QCOMPARE( altzone->latitude(), -26.25 );
|
||||
QCOMPARE( altzone->longitude(), 28.00 );
|
||||
|
||||
altzone = zones.find( -29.0, 27.0 );
|
||||
QCOMPARE( altzone->zone(), QString( "Maseru" ) );
|
||||
// -2928, that's -29 and 28/60 of a degree, is almost half, but we don't want
|
||||
// to fall foul of variations in double-precision
|
||||
QCOMPARE( trunc( altzone->latitude() * 1000.0 ), -29466 );
|
||||
}
|
||||
|
||||
void
|
||||
LocaleTests::testGSUpdates()
|
||||
{
|
||||
Calamares::GlobalStorage gs;
|
||||
|
||||
const QString gsKey( "localeConf" );
|
||||
|
||||
QCOMPARE( gs.value( gsKey ), QVariant() );
|
||||
|
||||
// Insert one
|
||||
{
|
||||
Calamares::Locale::insertGS( gs, "LANG", "en_US" );
|
||||
auto map = gs.value( gsKey ).toMap();
|
||||
QCOMPARE( map.count(), 1 );
|
||||
QCOMPARE( map.value( "LANG" ).toString(), QString( "en_US" ) );
|
||||
}
|
||||
|
||||
// Overwrite one
|
||||
{
|
||||
Calamares::Locale::insertGS( gs, "LANG", "nl_BE" );
|
||||
auto map = gs.value( gsKey ).toMap();
|
||||
QCOMPARE( map.count(), 1 );
|
||||
QCOMPARE( map.value( "LANG" ).toString(), QString( "nl_BE" ) );
|
||||
}
|
||||
|
||||
// Insert a second value
|
||||
{
|
||||
Calamares::Locale::insertGS( gs, "LC_TIME", "UTC" );
|
||||
auto map = gs.value( gsKey ).toMap();
|
||||
QCOMPARE( map.count(), 2 );
|
||||
QCOMPARE( map.value( "LANG" ).toString(), QString( "nl_BE" ) );
|
||||
QCOMPARE( map.value( "LC_TIME" ).toString(), QString( "UTC" ) );
|
||||
}
|
||||
|
||||
// Overwrite parts
|
||||
{
|
||||
QMap< QString, QString > kv;
|
||||
kv.insert( "LANG", "en_SU" );
|
||||
kv.insert( "LC_CURRENCY", "rbl" );
|
||||
|
||||
// Overwrite one, add one
|
||||
Calamares::Locale::insertGS( gs, kv, Calamares::Locale::InsertMode::Merge );
|
||||
auto map = gs.value( gsKey ).toMap();
|
||||
QCOMPARE( map.count(), 3 );
|
||||
QCOMPARE( map.value( "LANG" ).toString(), QString( "en_SU" ) );
|
||||
QCOMPARE( map.value( "LC_TIME" ).toString(), QString( "UTC" ) ); // unchanged
|
||||
QCOMPARE( map.value( "LC_CURRENCY" ).toString(), QString( "rbl" ) );
|
||||
}
|
||||
|
||||
// Overwrite with clear
|
||||
{
|
||||
QMap< QString, QString > kv;
|
||||
kv.insert( "LANG", "en_US" );
|
||||
kv.insert( "LC_CURRENCY", "peso" );
|
||||
|
||||
// Overwrite one, add one
|
||||
Calamares::Locale::insertGS( gs, kv, Calamares::Locale::InsertMode::Overwrite );
|
||||
auto map = gs.value( gsKey ).toMap();
|
||||
QCOMPARE( map.count(), 2 ); // the rest were cleared
|
||||
QCOMPARE( map.value( "LANG" ).toString(), QString( "en_US" ) );
|
||||
QVERIFY( !map.contains( "LC_TIME" ) );
|
||||
QCOMPARE( map.value( "LC_TIME" ).toString(), QString() ); // removed
|
||||
QCOMPARE( map.value( "LC_CURRENCY" ).toString(), QString( "peso" ) );
|
||||
}
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN( LocaleTests )
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
||||
#include "Tests.moc"
|
||||
501
calamares/src/libcalamares/locale/TimeZone.cpp
Normal file
501
calamares/src/libcalamares/locale/TimeZone.cpp
Normal file
@@ -0,0 +1,501 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "TimeZone.h"
|
||||
|
||||
#include "locale/TranslatableString.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/String.h"
|
||||
|
||||
#include <QFile>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
|
||||
static const char TZ_DATA_FILE[] = "/usr/share/zoneinfo/zone.tab";
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
class RegionData;
|
||||
using RegionVector = QVector< RegionData* >;
|
||||
using ZoneVector = QVector< TimeZoneData* >;
|
||||
|
||||
/** @brief Turns a string longitude or latitude notation into a double
|
||||
*
|
||||
* This handles strings like "+4230+00131" from zone.tab,
|
||||
* which is degrees-and-minutes notation, and + means north or east.
|
||||
*/
|
||||
static double
|
||||
getRightGeoLocation( QString str )
|
||||
{
|
||||
double sign = 1, num = 0.00;
|
||||
|
||||
// Determine sign
|
||||
if ( str.startsWith( '-' ) )
|
||||
{
|
||||
sign = -1;
|
||||
str.remove( 0, 1 );
|
||||
}
|
||||
else if ( str.startsWith( '+' ) )
|
||||
{
|
||||
str.remove( 0, 1 );
|
||||
}
|
||||
|
||||
if ( str.length() == 4 || str.length() == 6 )
|
||||
{
|
||||
num = str.mid( 0, 2 ).toDouble() + str.mid( 2, 2 ).toDouble() / 60.0;
|
||||
}
|
||||
else if ( str.length() == 5 || str.length() == 7 )
|
||||
{
|
||||
num = str.mid( 0, 3 ).toDouble() + str.mid( 3, 2 ).toDouble() / 60.0;
|
||||
}
|
||||
|
||||
return sign * num;
|
||||
}
|
||||
|
||||
TimeZoneData::TimeZoneData( const QString& region,
|
||||
const QString& zone,
|
||||
const QString& country,
|
||||
double latitude,
|
||||
double longitude )
|
||||
: TranslatableString( zone )
|
||||
, m_region( region )
|
||||
, m_country( country )
|
||||
, m_latitude( latitude )
|
||||
, m_longitude( longitude )
|
||||
{
|
||||
setObjectName( region + '/' + zone );
|
||||
}
|
||||
|
||||
QString
|
||||
TimeZoneData::translated() const
|
||||
{
|
||||
// NOTE: context name must match what's used in zone-extractor.py
|
||||
return QObject::tr( m_human, "tz_names" );
|
||||
}
|
||||
|
||||
class RegionData : public TranslatableString
|
||||
{
|
||||
public:
|
||||
using TranslatableString::TranslatableString;
|
||||
QString translated() const override;
|
||||
};
|
||||
|
||||
QString
|
||||
RegionData::translated() const
|
||||
{
|
||||
// NOTE: context name must match what's used in zone-extractor.py
|
||||
return QObject::tr( m_human, "tz_regions" );
|
||||
}
|
||||
|
||||
static void
|
||||
loadTZData( RegionVector& regions, ZoneVector& zones, QTextStream& in )
|
||||
{
|
||||
while ( !in.atEnd() )
|
||||
{
|
||||
QString line = in.readLine().trimmed().split( '#', SplitKeepEmptyParts ).first().trimmed();
|
||||
if ( line.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList list = line.split( QRegularExpression( "[\t ]" ), SplitSkipEmptyParts );
|
||||
if ( list.size() < 3 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QStringList timezoneParts = list.at( 2 ).split( '/', SplitSkipEmptyParts );
|
||||
if ( timezoneParts.size() < 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString region = timezoneParts.first().trimmed();
|
||||
if ( region.isEmpty() )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString countryCode = list.at( 0 ).trimmed();
|
||||
if ( countryCode.size() != 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
timezoneParts.removeFirst();
|
||||
QString zone = timezoneParts.join( '/' );
|
||||
if ( zone.length() < 2 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QString position = list.at( 1 );
|
||||
int cooSplitPos = position.indexOf( QRegularExpression( "[-+]" ), 1 );
|
||||
double latitude;
|
||||
double longitude;
|
||||
if ( cooSplitPos > 0 )
|
||||
{
|
||||
latitude = getRightGeoLocation( position.mid( 0, cooSplitPos ) );
|
||||
longitude = getRightGeoLocation( position.mid( cooSplitPos ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Now we have region, zone, country, lat and longitude
|
||||
const RegionData* existingRegion = nullptr;
|
||||
for ( const auto* p : regions )
|
||||
{
|
||||
if ( p->key() == region )
|
||||
{
|
||||
existingRegion = p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !existingRegion )
|
||||
{
|
||||
regions.append( new RegionData( region ) );
|
||||
}
|
||||
zones.append( new TimeZoneData( region, zone, countryCode, latitude, longitude ) );
|
||||
}
|
||||
}
|
||||
|
||||
/** @brief Extra, fake, timezones
|
||||
*
|
||||
* The timezone locations in zone.tab are not always very useful,
|
||||
* given Calamares's standard "nearest zone" algorithm: for instance,
|
||||
* in most locations physically in the country of South Africa,
|
||||
* Maseru (the capital of Lesotho, and location for timezone Africa/Maseru)
|
||||
* is closer than Johannesburg (the location for timezone Africa/Johannesburg).
|
||||
*
|
||||
* The algorithm picks the wrong place. This is for instance annoying
|
||||
* when clicking on Cape Town, you get Maseru, and to get Johannesburg
|
||||
* you need to click somewhere very carefully north of Maserru.
|
||||
*
|
||||
* These alternate zones are used to introduce "extra locations"
|
||||
* into the timezone database, in order to influence the closest-location
|
||||
* algorithm. Lines are formatted just like in zone.tab: remember the \n
|
||||
*/
|
||||
static const char altZones[] =
|
||||
/* This extra zone is north-east of Karoo National park,
|
||||
* and means that Western Cape province and a good chunk of
|
||||
* Northern- and Eastern- Cape provinces get pulled in to Johannesburg.
|
||||
* Bloemfontein is still closer to Maseru than either correct zone,
|
||||
* but this is a definite improvement.
|
||||
*/
|
||||
"ZA -3230+02259 Africa/Johannesburg\n";
|
||||
|
||||
class Private : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RegionVector m_regions;
|
||||
ZoneVector m_zones; ///< The official timezones and locations
|
||||
ZoneVector m_altZones; ///< Extra locations for zones
|
||||
|
||||
Private()
|
||||
{
|
||||
m_regions.reserve( 12 ); // reasonable guess
|
||||
m_zones.reserve( 452 ); // wc -l /usr/share/zoneinfo/zone.tab
|
||||
|
||||
// Load the official timezones
|
||||
{
|
||||
QFile file( TZ_DATA_FILE );
|
||||
if ( file.open( QIODevice::ReadOnly | QIODevice::Text ) )
|
||||
{
|
||||
QTextStream in( &file );
|
||||
loadTZData( m_regions, m_zones, in );
|
||||
}
|
||||
}
|
||||
// Load the alternate zones (see documentation at altZones)
|
||||
{
|
||||
QTextStream in( altZones );
|
||||
loadTZData( m_regions, m_altZones, in );
|
||||
}
|
||||
|
||||
std::sort( m_regions.begin(),
|
||||
m_regions.end(),
|
||||
[]( const RegionData* lhs, const RegionData* rhs ) { return lhs->key() < rhs->key(); } );
|
||||
std::sort( m_zones.begin(),
|
||||
m_zones.end(),
|
||||
[]( const TimeZoneData* lhs, const TimeZoneData* rhs )
|
||||
{
|
||||
if ( lhs->region() == rhs->region() )
|
||||
{
|
||||
return lhs->zone() < rhs->zone();
|
||||
}
|
||||
return lhs->region() < rhs->region();
|
||||
} );
|
||||
|
||||
for ( auto* z : m_zones )
|
||||
{
|
||||
z->setParent( this );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
static Private*
|
||||
privateInstance()
|
||||
{
|
||||
static Private* s_p = new Private;
|
||||
return s_p;
|
||||
}
|
||||
|
||||
RegionsModel::RegionsModel( QObject* parent )
|
||||
: QAbstractListModel( parent )
|
||||
, m_private( privateInstance() )
|
||||
{
|
||||
}
|
||||
|
||||
RegionsModel::~RegionsModel() {}
|
||||
|
||||
int
|
||||
RegionsModel::rowCount( const QModelIndex& ) const
|
||||
{
|
||||
return m_private->m_regions.count();
|
||||
}
|
||||
|
||||
QVariant
|
||||
RegionsModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
if ( !index.isValid() || index.row() < 0 || index.row() >= m_private->m_regions.count() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto& region = m_private->m_regions[ index.row() ];
|
||||
if ( role == NameRole )
|
||||
{
|
||||
return region->translated();
|
||||
}
|
||||
if ( role == KeyRole )
|
||||
{
|
||||
return region->key();
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash< int, QByteArray >
|
||||
RegionsModel::roleNames() const
|
||||
{
|
||||
return { { NameRole, "name" }, { KeyRole, "key" } };
|
||||
}
|
||||
|
||||
QString
|
||||
RegionsModel::translated( const QString& region ) const
|
||||
{
|
||||
for ( const auto* p : m_private->m_regions )
|
||||
{
|
||||
if ( p->key() == region )
|
||||
{
|
||||
return p->translated();
|
||||
}
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
ZonesModel::ZonesModel( QObject* parent )
|
||||
: QAbstractListModel( parent )
|
||||
, m_private( privateInstance() )
|
||||
{
|
||||
}
|
||||
|
||||
ZonesModel::~ZonesModel() {}
|
||||
|
||||
int
|
||||
ZonesModel::rowCount( const QModelIndex& ) const
|
||||
{
|
||||
return m_private->m_zones.count();
|
||||
}
|
||||
|
||||
QVariant
|
||||
ZonesModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
if ( !index.isValid() || index.row() < 0 || index.row() >= m_private->m_zones.count() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto* zone = m_private->m_zones[ index.row() ];
|
||||
switch ( role )
|
||||
{
|
||||
case NameRole:
|
||||
return zone->translated();
|
||||
case KeyRole:
|
||||
return zone->key();
|
||||
case RegionRole:
|
||||
return zone->region();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QHash< int, QByteArray >
|
||||
ZonesModel::roleNames() const
|
||||
{
|
||||
return { { NameRole, "name" }, { KeyRole, "key" } };
|
||||
}
|
||||
|
||||
const TimeZoneData*
|
||||
ZonesModel::find( const QString& region, const QString& zone ) const
|
||||
{
|
||||
for ( const auto* p : m_private->m_zones )
|
||||
{
|
||||
if ( p->region() == region && p->zone() == zone )
|
||||
{
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
STATICTEST const TimeZoneData*
|
||||
find( double startingDistance,
|
||||
const ZoneVector& zones,
|
||||
const std::function< double( const TimeZoneData* ) >& distanceFunc )
|
||||
{
|
||||
double smallestDistance = startingDistance;
|
||||
const TimeZoneData* closest = nullptr;
|
||||
|
||||
for ( const auto* zone : zones )
|
||||
{
|
||||
double thisDistance = distanceFunc( zone );
|
||||
if ( thisDistance < smallestDistance )
|
||||
{
|
||||
closest = zone;
|
||||
smallestDistance = thisDistance;
|
||||
}
|
||||
}
|
||||
return closest;
|
||||
}
|
||||
|
||||
const TimeZoneData*
|
||||
ZonesModel::find( const std::function< double( const TimeZoneData* ) >& distanceFunc ) const
|
||||
{
|
||||
const auto* officialZone = Calamares::Locale::find( 1000000.0, m_private->m_zones, distanceFunc );
|
||||
const auto* altZone = Calamares::Locale::find( distanceFunc( officialZone ), m_private->m_altZones, distanceFunc );
|
||||
|
||||
// If nothing was closer than the official zone already was, altZone is
|
||||
// nullptr; but if there is a spot-patch, then we need to re-find
|
||||
// the zone by name, since we want to always return pointers into
|
||||
// m_zones, not into the alternative spots.
|
||||
return altZone ? find( altZone->region(), altZone->zone() ) : officialZone;
|
||||
}
|
||||
|
||||
const TimeZoneData*
|
||||
ZonesModel::find( double latitude, double longitude ) const
|
||||
{
|
||||
/* This is a somewhat derpy way of finding "closest",
|
||||
* in that it considers one degree of separation
|
||||
* either N/S or E/W equal to any other; this obviously
|
||||
* falls apart at the poles.
|
||||
*/
|
||||
auto distance = [ & ]( const TimeZoneData* zone ) -> double
|
||||
{
|
||||
// Latitude doesn't wrap around: there is nothing north of 90
|
||||
double latitudeDifference = abs( zone->latitude() - latitude );
|
||||
|
||||
// Longitude **does** wrap around, so consider the case of -178 and 178
|
||||
// which differ by 4 degrees.
|
||||
double westerly = qMin( zone->longitude(), longitude );
|
||||
double easterly = qMax( zone->longitude(), longitude );
|
||||
double longitudeDifference = 0.0;
|
||||
if ( westerly < 0.0 && !( easterly < 0.0 ) )
|
||||
{
|
||||
// Only if they're different signs can we have wrap-around.
|
||||
longitudeDifference = qMin( abs( westerly - easterly ), abs( 360.0 + westerly - easterly ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
longitudeDifference = abs( westerly - easterly );
|
||||
}
|
||||
|
||||
return latitudeDifference + longitudeDifference;
|
||||
};
|
||||
|
||||
return find( distance );
|
||||
}
|
||||
|
||||
QObject*
|
||||
ZonesModel::lookup( double latitude, double longitude ) const
|
||||
{
|
||||
const auto* p = find( latitude, longitude );
|
||||
if ( !p )
|
||||
{
|
||||
p = find( "America", "New_York" );
|
||||
}
|
||||
if ( !p )
|
||||
{
|
||||
cWarning() << "No zone (not even New York) found, expect crashes.";
|
||||
}
|
||||
return const_cast< QObject* >( reinterpret_cast< const QObject* >( p ) );
|
||||
}
|
||||
|
||||
ZonesModel::Iterator::operator bool() const
|
||||
{
|
||||
return 0 <= m_index && m_index < m_p->m_zones.count();
|
||||
}
|
||||
|
||||
const TimeZoneData*
|
||||
ZonesModel::Iterator::operator*() const
|
||||
{
|
||||
if ( *this )
|
||||
{
|
||||
return m_p->m_zones[ m_index ];
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
RegionalZonesModel::RegionalZonesModel( Calamares::Locale::ZonesModel* source, QObject* parent )
|
||||
: QSortFilterProxyModel( parent )
|
||||
, m_private( privateInstance() )
|
||||
{
|
||||
setSourceModel( source );
|
||||
}
|
||||
|
||||
RegionalZonesModel::~RegionalZonesModel() {}
|
||||
|
||||
void
|
||||
RegionalZonesModel::setRegion( const QString& r )
|
||||
{
|
||||
if ( r != m_region )
|
||||
{
|
||||
m_region = r;
|
||||
invalidateFilter();
|
||||
emit regionChanged( r );
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
RegionalZonesModel::filterAcceptsRow( int sourceRow, const QModelIndex& ) const
|
||||
{
|
||||
if ( m_region.isEmpty() )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( sourceRow < 0 || sourceRow >= m_private->m_zones.count() )
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& zone = m_private->m_zones[ sourceRow ];
|
||||
return ( zone->m_region == m_region );
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#include "utils/moc-warnings.h"
|
||||
|
||||
#include "TimeZone.moc"
|
||||
237
calamares/src/libcalamares/locale/TimeZone.h
Normal file
237
calamares/src/libcalamares/locale/TimeZone.h
Normal file
@@ -0,0 +1,237 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/** @file Timezone data and models to go with it
|
||||
*
|
||||
* The TimeZoneData class holds information from zone.tab, about
|
||||
* TZ names and locations (latitude and longitude) for geographic
|
||||
* lookups.
|
||||
*
|
||||
* The RegionModel lists the regions of the world (about 12) and
|
||||
* ZonesModel lists all the timezones; the RegionalZonesModel provides
|
||||
* a way to restrict the view of timezones to those of a specific region.
|
||||
*
|
||||
*/
|
||||
#ifndef LOCALE_TIMEZONE_H
|
||||
#define LOCALE_TIMEZONE_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include "locale/TranslatableString.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
class Private;
|
||||
class RegionalZonesModel;
|
||||
class ZonesModel;
|
||||
|
||||
class DLLEXPORT TimeZoneData : public QObject, TranslatableString
|
||||
{
|
||||
friend class RegionalZonesModel;
|
||||
friend class ZonesModel;
|
||||
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY( QString region READ region CONSTANT )
|
||||
Q_PROPERTY( QString zone READ zone CONSTANT )
|
||||
Q_PROPERTY( QString name READ translated CONSTANT )
|
||||
Q_PROPERTY( QString countryCode READ country CONSTANT )
|
||||
|
||||
public:
|
||||
TimeZoneData( const QString& region,
|
||||
const QString& zone,
|
||||
const QString& country,
|
||||
double latitude,
|
||||
double longitude );
|
||||
TimeZoneData( const TimeZoneData& ) = delete;
|
||||
TimeZoneData( TimeZoneData&& ) = delete;
|
||||
|
||||
///@brief Returns a translated, human-readable form of region/zone (e.g. "America/New York")
|
||||
QString translated() const override;
|
||||
|
||||
///@brief Returns the region key (e.g. "Europe") with no translation and no human-readable tweaks
|
||||
QString region() const { return m_region; }
|
||||
///@brief Returns the zone key (e.g. "New_York") with no translation and no human-readable tweaks
|
||||
QString zone() const { return key(); }
|
||||
|
||||
QString country() const { return m_country; }
|
||||
double latitude() const { return m_latitude; }
|
||||
double longitude() const { return m_longitude; }
|
||||
|
||||
private:
|
||||
QString m_region;
|
||||
QString m_country;
|
||||
double m_latitude;
|
||||
double m_longitude;
|
||||
};
|
||||
|
||||
/** @brief The list of timezone regions
|
||||
*
|
||||
* The regions are a short list of global areas (Africa, America, India ..)
|
||||
* which contain zones.
|
||||
*/
|
||||
class DLLEXPORT RegionsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
NameRole = Qt::DisplayRole,
|
||||
KeyRole = Qt::UserRole // So that currentData() will get the key
|
||||
};
|
||||
|
||||
RegionsModel( QObject* parent = nullptr );
|
||||
~RegionsModel() override;
|
||||
|
||||
int rowCount( const QModelIndex& parent ) const override;
|
||||
QVariant data( const QModelIndex& index, int role ) const override;
|
||||
|
||||
QHash< int, QByteArray > roleNames() const override;
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Provides a human-readable version of the region
|
||||
*
|
||||
* Returns @p region unchanged if there is no such region
|
||||
* or no translation for the region's name.
|
||||
*/
|
||||
QString translated( const QString& region ) const;
|
||||
|
||||
private:
|
||||
Private* m_private;
|
||||
};
|
||||
|
||||
class DLLEXPORT ZonesModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Roles
|
||||
{
|
||||
NameRole = Qt::DisplayRole,
|
||||
KeyRole = Qt::UserRole, // So that currentData() will get the key
|
||||
RegionRole = Qt::UserRole + 1
|
||||
};
|
||||
|
||||
ZonesModel( QObject* parent = nullptr );
|
||||
~ZonesModel() override;
|
||||
|
||||
int rowCount( const QModelIndex& parent ) const override;
|
||||
QVariant data( const QModelIndex& index, int role ) const override;
|
||||
|
||||
QHash< int, QByteArray > roleNames() const override;
|
||||
|
||||
/** @brief Iterator for the underlying list of zones
|
||||
*
|
||||
* Iterates over all the zones in the model. Operator * may return
|
||||
* a @c nullptr when the iterator is not valid. Typical usage:
|
||||
*
|
||||
* ```
|
||||
* for( auto it = model.begin(); it; ++it )
|
||||
* {
|
||||
* const auto* zonedata = *it;
|
||||
* ...
|
||||
* }
|
||||
*/
|
||||
class Iterator
|
||||
{
|
||||
friend class ZonesModel;
|
||||
Iterator( const Private* m )
|
||||
: m_index( 0 )
|
||||
, m_p( m )
|
||||
{
|
||||
}
|
||||
|
||||
public:
|
||||
operator bool() const;
|
||||
void operator++() { ++m_index; }
|
||||
const TimeZoneData* operator*() const;
|
||||
int index() const { return m_index; }
|
||||
|
||||
private:
|
||||
int m_index;
|
||||
const Private* m_p;
|
||||
};
|
||||
|
||||
Iterator begin() const { return Iterator( m_private ); }
|
||||
|
||||
/** @brief Look up TZ data based on an arbitrary distance function
|
||||
*
|
||||
* This is a generic method that can define distance in whatever
|
||||
* coordinate system is wanted; returns the zone with the smallest
|
||||
* distance. The @p distanceFunc must return "the distance" for
|
||||
* each zone. It would be polite to return something non-negative.
|
||||
*
|
||||
* Note: not a slot, because the parameter isn't moc-able.
|
||||
*/
|
||||
const TimeZoneData* find( const std::function< double( const TimeZoneData* ) >& distanceFunc ) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Look up TZ data based on its name.
|
||||
*
|
||||
* Returns @c nullptr if not found.
|
||||
*/
|
||||
const TimeZoneData* find( const QString& region, const QString& zone ) const;
|
||||
|
||||
/** @brief Look up TZ data based on the location.
|
||||
*
|
||||
* Returns the nearest zone to the given lat and lon. This is a
|
||||
* convenience function for calling find(), below, with a standard
|
||||
* distance function based on the distance between the given
|
||||
* location (lat and lon) and each zone's given location.
|
||||
*/
|
||||
const TimeZoneData* find( double latitude, double longitude ) const;
|
||||
|
||||
/** @brief Look up TZ data based on the location.
|
||||
*
|
||||
* Returns the nearest zone, or New York. This is non-const for QML
|
||||
* purposes, but the object should be considered const anyway.
|
||||
*/
|
||||
QObject* lookup( double latitude, double longitude ) const;
|
||||
|
||||
private:
|
||||
Private* m_private;
|
||||
};
|
||||
|
||||
class DLLEXPORT RegionalZonesModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY( QString region READ region WRITE setRegion NOTIFY regionChanged )
|
||||
|
||||
public:
|
||||
RegionalZonesModel( ZonesModel* source, QObject* parent = nullptr );
|
||||
~RegionalZonesModel() override;
|
||||
|
||||
bool filterAcceptsRow( int sourceRow, const QModelIndex& sourceParent ) const override;
|
||||
|
||||
QString region() const { return m_region; }
|
||||
|
||||
public Q_SLOTS:
|
||||
void setRegion( const QString& r );
|
||||
|
||||
signals:
|
||||
void regionChanged( const QString& );
|
||||
|
||||
private:
|
||||
Private* m_private;
|
||||
QString m_region;
|
||||
};
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif // LOCALE_TIMEZONE_H
|
||||
118
calamares/src/libcalamares/locale/TranslatableConfiguration.cpp
Normal file
118
calamares/src/libcalamares/locale/TranslatableConfiguration.cpp
Normal file
@@ -0,0 +1,118 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "TranslatableConfiguration.h"
|
||||
|
||||
#include "TranslationsModel.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QRegularExpression>
|
||||
#include <QRegularExpressionMatch>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
TranslatedString::TranslatedString( const QString& key, const char* context )
|
||||
: m_context( context )
|
||||
{
|
||||
m_strings[ QString() ] = key;
|
||||
}
|
||||
|
||||
TranslatedString::TranslatedString( const QString& string )
|
||||
: TranslatedString( string, nullptr )
|
||||
{
|
||||
}
|
||||
|
||||
TranslatedString::TranslatedString( const QVariantMap& map, const QString& key, const char* context )
|
||||
: m_context( context )
|
||||
{
|
||||
// Get the un-decorated value for the key
|
||||
QString value = Calamares::getString( map, key );
|
||||
m_strings[ QString() ] = value;
|
||||
|
||||
for ( auto it = map.constBegin(); it != map.constEnd(); ++it )
|
||||
{
|
||||
QString subkey = it.key();
|
||||
if ( subkey == key )
|
||||
{
|
||||
// Already obtained, above
|
||||
}
|
||||
else if ( subkey.startsWith( key ) )
|
||||
{
|
||||
QRegularExpressionMatch match;
|
||||
if ( subkey.indexOf( QRegularExpression( "\\[([a-zA-Z_@]*)\\]" ), 0, &match ) > 0 )
|
||||
{
|
||||
QString language = match.captured( 1 );
|
||||
m_strings[ language ] = it.value().toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString
|
||||
TranslatedString::get() const
|
||||
{
|
||||
return get( QLocale() );
|
||||
}
|
||||
|
||||
QString
|
||||
TranslatedString::get( const QLocale& locale ) const
|
||||
{
|
||||
// TODO: keep track of special cases like sr@latin and ca@valencia
|
||||
QString localeName = locale.name();
|
||||
// Special case, sr@latin doesn't have the @latin reflected in the name
|
||||
if ( locale.language() == QLocale::Language::Serbian && locale.script() == QLocale::Script::LatinScript )
|
||||
{
|
||||
localeName = QStringLiteral( "sr@latin" );
|
||||
}
|
||||
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
int index = localeName.indexOf( '@' );
|
||||
if ( index > 0 )
|
||||
{
|
||||
localeName.truncate( index );
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
}
|
||||
|
||||
index = localeName.indexOf( '_' );
|
||||
if ( index > 0 )
|
||||
{
|
||||
localeName.truncate( index );
|
||||
if ( m_strings.contains( localeName ) )
|
||||
{
|
||||
return m_strings[ localeName ];
|
||||
}
|
||||
}
|
||||
|
||||
// If we're given a context to work with, also try the same string in
|
||||
// the regular translation framework.
|
||||
const QString& s = m_strings[ QString() ];
|
||||
if ( m_context )
|
||||
{
|
||||
return QCoreApplication::translate( m_context, s.toLatin1().constData() );
|
||||
}
|
||||
else
|
||||
{
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
104
calamares/src/libcalamares/locale/TranslatableConfiguration.h
Normal file
104
calamares/src/libcalamares/locale/TranslatableConfiguration.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
/** @file Run-time translation of strings from configuration files
|
||||
*
|
||||
* The TranslatedString class provides a way of doing run-time
|
||||
* lookups of human-readable strings, from data provided in
|
||||
* the configuration files (*.conf) for Calamares. This acts
|
||||
* like "normal" translation through tr() calls, as far as the
|
||||
* user-visible part goes.
|
||||
*/
|
||||
#ifndef LOCALE_TRANSLATABLECONFIGURATION_H
|
||||
#define LOCALE_TRANSLATABLECONFIGURATION_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QMap>
|
||||
#include <QVariant>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
/** @brief A human-readable string from a configuration file
|
||||
*
|
||||
* The configuration files can contain human-readable strings,
|
||||
* but those need their own translations and are not supported
|
||||
* by QObject::tr or anything else.
|
||||
*/
|
||||
class DLLEXPORT TranslatedString
|
||||
{
|
||||
public:
|
||||
/** @brief Get all the translations connected to @p key
|
||||
*
|
||||
* Gets map[key] as the "untranslated" form, and then all the
|
||||
* keys of the form <key>[lang] are taken as the translation
|
||||
* for <lang> of the untranslated form.
|
||||
*
|
||||
* If @p context is not a nullptr, then that is taken as an
|
||||
* indication to **also** use the regular QObject::tr() translation
|
||||
* mechanism for these strings. It is recommended to pass in
|
||||
* metaObject()->className() as context (from a QObject based class)
|
||||
* to give the TranslatedString the same context as other calls
|
||||
* to tr() within that class.
|
||||
*
|
||||
* The @p context, if any, should point to static data; it is
|
||||
* **not** owned by the TranslatedString.
|
||||
*/
|
||||
TranslatedString( const QVariantMap& map, const QString& key, const char* context = nullptr );
|
||||
/** @brief Not-actually-translated string.
|
||||
*/
|
||||
TranslatedString( const QString& string );
|
||||
/** @brief Proxy for calling QObject::tr()
|
||||
*
|
||||
* This is like the two constructors above, with an empty map an a
|
||||
* non-null context. It will end up calling tr() with that context.
|
||||
*
|
||||
* The @p context, if any, should point to static data; it is
|
||||
* **not** owned by the TranslatedString.
|
||||
*/
|
||||
TranslatedString( const QString& key, const char* context );
|
||||
/// @brief Empty string
|
||||
TranslatedString()
|
||||
: TranslatedString( QString() )
|
||||
{
|
||||
}
|
||||
|
||||
/** @brief How many strings (translations) are there?
|
||||
*
|
||||
* This is always at least 1 (for the untranslated string),
|
||||
* but may be more than 1 even when isEmpty() is true --
|
||||
* if there is no untranslated version, for instance.
|
||||
*/
|
||||
int count() const { return m_strings.count(); }
|
||||
/** @brief Consider this string empty?
|
||||
*
|
||||
* Only the state of the untranslated string is considered,
|
||||
* so count() may be more than 1 even while the string is empty.
|
||||
*/
|
||||
bool isEmpty() const { return m_strings[ QString() ].isEmpty(); }
|
||||
|
||||
/// @brief Gets the string in the current locale
|
||||
QString get() const;
|
||||
|
||||
/// @brief Gets the string from the given locale
|
||||
QString get( const QLocale& ) const;
|
||||
|
||||
private:
|
||||
// Maps locale name to human-readable string, "" is English
|
||||
QMap< QString, QString > m_strings;
|
||||
const char* m_context = nullptr;
|
||||
};
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
77
calamares/src/libcalamares/locale/TranslatableString.cpp
Normal file
77
calamares/src/libcalamares/locale/TranslatableString.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#include "TranslatableString.h"
|
||||
|
||||
/** @brief Massage an identifier into a human-readable form
|
||||
*
|
||||
* Makes a copy of @p s, caller must free() it.
|
||||
*/
|
||||
static char*
|
||||
munge( const char* s )
|
||||
{
|
||||
char* t = strdup( s );
|
||||
if ( !t )
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// replace("_"," ") in the Python script
|
||||
char* p = t;
|
||||
while ( *p )
|
||||
{
|
||||
if ( ( *p ) == '_' )
|
||||
{
|
||||
*p = ' ';
|
||||
}
|
||||
++p;
|
||||
}
|
||||
|
||||
return t;
|
||||
}
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
TranslatableString::TranslatableString( TranslatableString&& t )
|
||||
: m_human( nullptr )
|
||||
, m_key()
|
||||
{
|
||||
// My pointers are initialized to nullptr
|
||||
std::swap( m_human, t.m_human );
|
||||
std::swap( m_key, t.m_key );
|
||||
}
|
||||
|
||||
TranslatableString::TranslatableString( const TranslatableString& t )
|
||||
: m_human( t.m_human ? strdup( t.m_human ) : nullptr )
|
||||
, m_key( t.m_key )
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString::TranslatableString( const char* s1 )
|
||||
: m_human( s1 ? munge( s1 ) : nullptr )
|
||||
, m_key( s1 ? QString( s1 ) : QString() )
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString::TranslatableString( const QString& s )
|
||||
: m_human( munge( s.toUtf8().constData() ) )
|
||||
, m_key( s )
|
||||
{
|
||||
}
|
||||
|
||||
TranslatableString::~TranslatableString()
|
||||
{
|
||||
free( m_human );
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
58
calamares/src/libcalamares/locale/TranslatableString.h
Normal file
58
calamares/src/libcalamares/locale/TranslatableString.h
Normal file
@@ -0,0 +1,58 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
#ifndef LOCALE_TRANSLATABLESTRING_H
|
||||
#define LOCALE_TRANSLATABLESTRING_H
|
||||
|
||||
#include <QString>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
/** @brief A pair of strings, one human-readable, one a key
|
||||
*
|
||||
* Given an identifier-like string (e.g. "New_York"), makes
|
||||
* a human-readable version of that and keeps a copy of the
|
||||
* identifier itself.
|
||||
*
|
||||
* This explicitly uses const char* instead of just being
|
||||
* QPair<QString, QString> because the human-readable part
|
||||
* may need to be translated through tr(), and that takes a char*
|
||||
* C-style strings.
|
||||
*/
|
||||
class TranslatableString
|
||||
{
|
||||
public:
|
||||
/// @brief An empty pair
|
||||
TranslatableString() {}
|
||||
/// @brief Given an identifier, create the pair
|
||||
explicit TranslatableString( const char* s1 );
|
||||
explicit TranslatableString( const QString& s );
|
||||
TranslatableString( TranslatableString&& t );
|
||||
TranslatableString( const TranslatableString& );
|
||||
virtual ~TranslatableString();
|
||||
|
||||
/// @brief Give the localized human-readable form
|
||||
virtual QString translated() const = 0;
|
||||
QString key() const { return m_key; }
|
||||
|
||||
bool operator==( const TranslatableString& other ) const { return m_key == other.m_key; }
|
||||
bool operator<( const TranslatableString& other ) const { return m_key < other.m_key; }
|
||||
|
||||
protected:
|
||||
char* m_human = nullptr;
|
||||
QString m_key;
|
||||
};
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
246
calamares/src/libcalamares/locale/Translation.cpp
Normal file
246
calamares/src/libcalamares/locale/Translation.cpp
Normal file
@@ -0,0 +1,246 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Translation.h"
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TranslationSpecialCase
|
||||
{
|
||||
const char* id; // The Calamares ID for the translation
|
||||
const char** regions;
|
||||
|
||||
QLocale::Language language;
|
||||
QLocale::Script script;
|
||||
QLocale::Country country;
|
||||
|
||||
const char* name; // Native name, if different from Qt
|
||||
|
||||
constexpr bool customLocale() const { return language != QLocale::Language::AnyLanguage; }
|
||||
};
|
||||
|
||||
/** @brief Handle special cases of Calamares language names
|
||||
*
|
||||
* If a given @p id (e.g. en_US, or sr@latin) has special handling,
|
||||
* put an entry in this table. The QLocale constants are used when a
|
||||
* particular @p id needs specific configuration, **if** @p language
|
||||
* is not @c AnyLanguage. The @p name is used as a human-readable
|
||||
* native name if the Qt name is not suitable.
|
||||
*
|
||||
* Another form of lookup maps a @p language + a region-identifier
|
||||
* to a @p id, running around Qt's neglect of `@region` variants.
|
||||
*
|
||||
* Examples:
|
||||
* - `sr@latin` needs specific Qt Locale settnigs, but the name is OK
|
||||
* - Chinese needs a specific name, but the Locale is OK
|
||||
*/
|
||||
static const char* serbian_latin_regions[] = { "latin", "latn", nullptr };
|
||||
static const char* catalan_regions[] = { "valencia", nullptr };
|
||||
static constexpr const TranslationSpecialCase special_cases[] = {
|
||||
{ "sr@latin",
|
||||
serbian_latin_regions,
|
||||
QLocale::Language::Serbian,
|
||||
QLocale::Script::LatinScript,
|
||||
QLocale::Country::Serbia,
|
||||
nullptr },
|
||||
// Valencian is a regional variant of Catalan
|
||||
{ "ca@valencia",
|
||||
catalan_regions,
|
||||
QLocale::Language::Catalan,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"Català (València)" },
|
||||
{ "ca_ES@valencia",
|
||||
catalan_regions,
|
||||
QLocale::Language::Catalan,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"Català (València)" },
|
||||
// Simplified Chinese, but drop the (China) from the name
|
||||
{ "zh_CN",
|
||||
nullptr,
|
||||
QLocale::Language::AnyLanguage,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"简体中文" },
|
||||
// Traditional Chinese, but drop (Taiwan) from the name
|
||||
{ "zh_TW",
|
||||
nullptr,
|
||||
QLocale::Language::AnyLanguage,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"繁體中文" },
|
||||
{ "oc",
|
||||
nullptr,
|
||||
QLocale::Language::AnyLanguage,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"Lenga d'òc" },
|
||||
// Luri
|
||||
{ "bqi",
|
||||
nullptr,
|
||||
QLocale::Language::NorthernLuri,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
nullptr },
|
||||
// Interlingue is mapped to interlingu*a* (up until Qt version ...) because
|
||||
// the real Language::Interlingue acts like C locale.
|
||||
{ "ie",
|
||||
nullptr,
|
||||
CALAMARES_QT_SUPPORT_INTERLINGUE ? QLocale::Language::Interlingue : QLocale::Language::Interlingua,
|
||||
QLocale::Script::AnyScript,
|
||||
QLocale::Country::AnyCountry,
|
||||
"Interlingue" },
|
||||
};
|
||||
|
||||
static inline bool
|
||||
lookup_region( const QByteArray& region, const char** regions_list )
|
||||
{
|
||||
if ( regions_list )
|
||||
{
|
||||
while ( *regions_list )
|
||||
{
|
||||
if ( region == *regions_list )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
regions_list++;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static QString
|
||||
specialCaseSystemLanguage()
|
||||
{
|
||||
const QByteArray lang_p = qgetenv( "LANG" );
|
||||
if ( lang_p.isEmpty() )
|
||||
{
|
||||
// This will figure out the system language some other way
|
||||
return {};
|
||||
}
|
||||
|
||||
auto lang_parts = lang_p.split( '@' );
|
||||
if ( lang_parts.size() != 2 )
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
QLocale locale( QString::fromLatin1( lang_p ) );
|
||||
auto it
|
||||
= std::find_if( std::cbegin( special_cases ),
|
||||
std::cend( special_cases ),
|
||||
[ language = locale.language(), region = lang_parts[ 1 ] ]( const TranslationSpecialCase& s )
|
||||
{ return ( s.language == language ) && lookup_region( region, s.regions ); } );
|
||||
return ( it != std::cend( special_cases ) ) ? QString::fromLatin1( it->id ) : QString();
|
||||
}
|
||||
|
||||
///@brief Country (territory) name for this locale
|
||||
QString
|
||||
territoryName( const QLocale& locale )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
return QLocale::countryToString( locale.country() );
|
||||
#else
|
||||
return QLocale::territoryToString( locale.territory() );
|
||||
#endif
|
||||
}
|
||||
|
||||
QString
|
||||
nativeTerritoryName( const QLocale& locale )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
return locale.nativeCountryName();
|
||||
#else
|
||||
return locale.nativeTerritoryName();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool
|
||||
needsTerritorialDisambiguation( const QLocale& locale )
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
return QLocale::countriesForLanguage( locale.language() ).count() > 1;
|
||||
#else
|
||||
std::set<QLocale::Territory> s;
|
||||
for(const auto & l : QLocale::matchingLocales( locale.language(), QLocale::Script::AnyScript, QLocale::Territory::AnyTerritory ))
|
||||
{
|
||||
s.insert(l.territory());
|
||||
}
|
||||
return s.size() > 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
Translation::Translation( QObject* parent )
|
||||
: Translation( { specialCaseSystemLanguage() }, LabelFormat::IfNeededWithCountry, parent )
|
||||
{
|
||||
}
|
||||
|
||||
Translation::Translation( const Id& localeId, LabelFormat format, QObject* parent )
|
||||
: QObject( parent )
|
||||
, m_locale( getLocale( localeId ) )
|
||||
, m_localeId( localeId.name.isEmpty() ? m_locale.name() : localeId.name )
|
||||
{
|
||||
auto it = std::find_if( std::cbegin( special_cases ),
|
||||
std::cend( special_cases ),
|
||||
[ &localeId ]( const TranslationSpecialCase& s ) { return localeId.name == s.id; } );
|
||||
const char* name = ( it != std::cend( special_cases ) ) ? it->name : nullptr;
|
||||
|
||||
QString longFormat = QObject::tr( "%1 (%2)" );
|
||||
|
||||
QString languageName = name ? QString::fromUtf8( name ) : m_locale.nativeLanguageName();
|
||||
QString englishName = m_locale.languageToString( m_locale.language() );
|
||||
|
||||
if ( languageName.isEmpty() )
|
||||
{
|
||||
languageName = QString( "* %1 (%2)" ).arg( localeId.name, englishName );
|
||||
}
|
||||
|
||||
bool needsCountryName = ( format == LabelFormat::AlwaysWithCountry )
|
||||
|| ( !name && localeId.name.contains( '_' ) && needsTerritorialDisambiguation( ( m_locale ) ) );
|
||||
const QString countryName = needsCountryName ? nativeTerritoryName( m_locale ) : QString();
|
||||
m_label = needsCountryName ? longFormat.arg( languageName, countryName ) : languageName;
|
||||
m_englishLabel = needsCountryName ? longFormat.arg( englishName, territoryName( m_locale ) ) : englishName;
|
||||
}
|
||||
|
||||
QLocale
|
||||
Translation::getLocale( const Id& localeId )
|
||||
{
|
||||
const QString& localeName = localeId.name;
|
||||
if ( localeName.isEmpty() )
|
||||
{
|
||||
return QLocale();
|
||||
}
|
||||
|
||||
auto it = std::find_if( std::cbegin( special_cases ),
|
||||
std::cend( special_cases ),
|
||||
[ &localeId ]( const TranslationSpecialCase& s ) { return localeId.name == s.id; } );
|
||||
if ( it != std::cend( special_cases ) && it->customLocale() )
|
||||
{
|
||||
return QLocale( it->language, it->script, it->country );
|
||||
}
|
||||
return QLocale( localeName );
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
145
calamares/src/libcalamares/locale/Translation.h
Normal file
145
calamares/src/libcalamares/locale/Translation.h
Normal file
@@ -0,0 +1,145 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014-2015 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2017-2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LOCALE_TRANSLATION_H
|
||||
#define LOCALE_TRANSLATION_H
|
||||
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QLocale>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
///@brief Define to 1 if the Qt version being used supports Interlingue fully
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 7, 0 )
|
||||
#define CALAMARES_QT_SUPPORT_INTERLINGUE 0
|
||||
#else
|
||||
#define CALAMARES_QT_SUPPORT_INTERLINGUE 1
|
||||
#endif
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Consistent locale (language + country) naming.
|
||||
*
|
||||
* Support class to turn locale names (as used by Calamares's
|
||||
* translation system) into QLocales, and also into consistent
|
||||
* human-readable text labels.
|
||||
*
|
||||
* This handles special-cases in Calamares translations:
|
||||
* - `sr@latin` is the name which Qt recognizes as `sr@latn`,
|
||||
* Serbian written with Latin characters (not Cyrillic).
|
||||
* - `ca@valencia` is the Catalan dialect spoken in Valencia.
|
||||
* There is no Qt code for it.
|
||||
*
|
||||
* (There are more special cases, not documented here)
|
||||
*/
|
||||
class DLLEXPORT Translation : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/** @brief Formatting option for label -- add (country) to label. */
|
||||
enum class LabelFormat
|
||||
{
|
||||
AlwaysWithCountry,
|
||||
IfNeededWithCountry
|
||||
};
|
||||
|
||||
struct Id
|
||||
{
|
||||
QString name;
|
||||
};
|
||||
|
||||
/** @brief Empty locale. This uses the system-default locale. */
|
||||
Translation( QObject* parent = nullptr );
|
||||
|
||||
/** @brief Construct from a locale name.
|
||||
*
|
||||
* The @p localeName should be one that Qt recognizes, e.g. en_US or ar_EY.
|
||||
* The @p format determines whether the country name is always present
|
||||
* in the label (human-readable form) or only if needed for disambiguation.
|
||||
*/
|
||||
Translation( const Id& localeId, LabelFormat format = LabelFormat::IfNeededWithCountry, QObject* parent = nullptr );
|
||||
|
||||
/** @brief Define a sorting order.
|
||||
*
|
||||
* Locales are sorted by their id, which means the ISO 2-letter code + country.
|
||||
*/
|
||||
bool operator<( const Translation& other ) const { return m_localeId < other.m_localeId; }
|
||||
|
||||
/** @brief Is this locale English?
|
||||
*
|
||||
* en_US and en (American English) is defined as English. The Queen's
|
||||
* English -- proper English -- is relegated to non-English status.
|
||||
*/
|
||||
bool isEnglish() const { return m_localeId == QLatin1String( "en_US" ) || m_localeId == QLatin1String( "en" ); }
|
||||
|
||||
/** @brief Get the human-readable name for this locale. */
|
||||
QString label() const { return m_label; }
|
||||
/** @brief Get the *English* human-readable name for this locale. */
|
||||
QString englishLabel() const { return m_englishLabel; }
|
||||
|
||||
/** @brief Get the Qt locale. */
|
||||
QLocale locale() const { return m_locale; }
|
||||
|
||||
/** @brief Gets the Calamares internal name (code) of the locale.
|
||||
*
|
||||
* This is a strongly-typed return to avoid it ending up all over
|
||||
* the place as a QString.
|
||||
*/
|
||||
Id id() const { return { m_localeId }; }
|
||||
|
||||
/// @brief Convenience accessor to the language part of the locale
|
||||
QLocale::Language language() const { return m_locale.language(); }
|
||||
|
||||
/// @brief Convenience accessor to the country part (if any) of the locale
|
||||
QLocale::Country country() const
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 6, 0 )
|
||||
|
||||
return m_locale.country();
|
||||
#else
|
||||
return m_locale.territory();
|
||||
#endif
|
||||
}
|
||||
|
||||
/** @brief Get a Qt locale for the given @p localeName
|
||||
*
|
||||
* This obeys special cases as described in the class documentation.
|
||||
*/
|
||||
static QLocale getLocale( const Id& localeId );
|
||||
|
||||
private:
|
||||
QLocale m_locale;
|
||||
QString m_localeId; // the locale identifier, e.g. "en_GB"
|
||||
QString m_label; // the native name of the locale
|
||||
QString m_englishLabel;
|
||||
};
|
||||
|
||||
static inline QDebug&
|
||||
operator<<( QDebug& s, const Translation::Id& id )
|
||||
{
|
||||
return s << id.name;
|
||||
}
|
||||
static inline bool
|
||||
operator==( const Translation::Id& lhs, const Translation::Id& rhs )
|
||||
{
|
||||
return lhs.name == rhs.name;
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
155
calamares/src/libcalamares/locale/TranslationsModel.cpp
Normal file
155
calamares/src/libcalamares/locale/TranslationsModel.cpp
Normal file
@@ -0,0 +1,155 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Camilo Higuita <milo.h@aol.com>
|
||||
* SPDX-FileCopyrightText: 2019-2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#include "TranslationsModel.h"
|
||||
|
||||
#include "Lookup.h"
|
||||
|
||||
#include "CalamaresTranslations.cc" // For the list of translations, generated at build time
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
TranslationsModel::TranslationsModel( const QStringList& locales, QObject* parent )
|
||||
: QAbstractListModel( parent )
|
||||
, m_localeIds( locales )
|
||||
{
|
||||
Q_ASSERT( locales.count() > 0 );
|
||||
m_locales.reserve( locales.count() );
|
||||
|
||||
for ( const auto& l : locales )
|
||||
{
|
||||
m_locales.push_back( new Translation( { l }, Translation::LabelFormat::IfNeededWithCountry, this ) );
|
||||
}
|
||||
}
|
||||
|
||||
TranslationsModel::~TranslationsModel() {}
|
||||
|
||||
int
|
||||
TranslationsModel::rowCount( const QModelIndex& ) const
|
||||
{
|
||||
return m_locales.count();
|
||||
}
|
||||
|
||||
QVariant
|
||||
TranslationsModel::data( const QModelIndex& index, int role ) const
|
||||
{
|
||||
if ( ( role != LabelRole ) && ( role != EnglishLabelRole ) )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if ( !index.isValid() )
|
||||
{
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
const auto& locale = m_locales.at( index.row() );
|
||||
switch ( role )
|
||||
{
|
||||
case LabelRole:
|
||||
return locale->label();
|
||||
case EnglishLabelRole:
|
||||
return locale->englishLabel();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QHash< int, QByteArray >
|
||||
TranslationsModel::roleNames() const
|
||||
{
|
||||
return { { LabelRole, "label" }, { EnglishLabelRole, "englishLabel" } };
|
||||
}
|
||||
|
||||
const Translation&
|
||||
TranslationsModel::locale( int row ) const
|
||||
{
|
||||
if ( ( row < 0 ) || ( row >= m_locales.count() ) )
|
||||
{
|
||||
for ( const auto& l : m_locales )
|
||||
{
|
||||
if ( l->isEnglish() )
|
||||
{
|
||||
return *l;
|
||||
}
|
||||
}
|
||||
return *m_locales[ 0 ];
|
||||
}
|
||||
return *m_locales[ row ];
|
||||
}
|
||||
|
||||
int
|
||||
TranslationsModel::find( std::function< bool( const Translation& ) > predicate ) const
|
||||
{
|
||||
for ( int row = 0; row < m_locales.count(); ++row )
|
||||
{
|
||||
if ( predicate( *m_locales[ row ] ) )
|
||||
{
|
||||
return row;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
int
|
||||
TranslationsModel::find( std::function< bool( const QLocale& ) > predicate ) const
|
||||
{
|
||||
return find( [ & ]( const Translation& l ) { return predicate( l.locale() ); } );
|
||||
}
|
||||
|
||||
int
|
||||
TranslationsModel::find( const QLocale& locale ) const
|
||||
{
|
||||
return find( [ & ]( const Translation& l ) { return locale == l.locale(); } );
|
||||
}
|
||||
|
||||
int
|
||||
TranslationsModel::find( const QString& countryCode ) const
|
||||
{
|
||||
if ( countryCode.length() != 2 )
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
auto c_l = countryData( countryCode );
|
||||
int r = find( [ & ]( const Translation& l )
|
||||
{ return ( l.language() == c_l.second ) && ( l.country() == c_l.first ); } );
|
||||
if ( r >= 0 )
|
||||
{
|
||||
return r;
|
||||
}
|
||||
return find( [ & ]( const Translation& l ) { return l.language() == c_l.second; } );
|
||||
}
|
||||
|
||||
int
|
||||
TranslationsModel::find( const Translation::Id& id ) const
|
||||
{
|
||||
return find( [ & ]( const Translation& l ) { return l.id() == id; } );
|
||||
}
|
||||
|
||||
TranslationsModel*
|
||||
availableTranslations()
|
||||
{
|
||||
static TranslationsModel* model = new TranslationsModel( availableLanguageList );
|
||||
return model;
|
||||
}
|
||||
|
||||
const QStringList&
|
||||
availableLanguages()
|
||||
{
|
||||
return availableLanguageList;
|
||||
}
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
94
calamares/src/libcalamares/locale/TranslationsModel.h
Normal file
94
calamares/src/libcalamares/locale/TranslationsModel.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2019 Camilo Higuita <milo.h@aol.com>
|
||||
* SPDX-FileCopyrightText: 2019-2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LOCALE_TRANSLATIONSMODEL_H
|
||||
#define LOCALE_TRANSLATIONSMODEL_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Translation.h"
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QVector>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace Locale
|
||||
{
|
||||
|
||||
class DLLEXPORT TranslationsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
LabelRole = Qt::DisplayRole,
|
||||
EnglishLabelRole = Qt::UserRole + 1
|
||||
};
|
||||
|
||||
TranslationsModel( const QStringList& locales, QObject* parent = nullptr );
|
||||
~TranslationsModel() override;
|
||||
|
||||
int rowCount( const QModelIndex& parent ) const override;
|
||||
|
||||
QVariant data( const QModelIndex& index, int role ) const override;
|
||||
QHash< int, QByteArray > roleNames() const override;
|
||||
|
||||
/** @brief Gets locale information for entry #n
|
||||
*
|
||||
* This is the backing data for the model; if @p row is out-of-range,
|
||||
* returns a reference to en_US.
|
||||
*/
|
||||
const Translation& locale( int row ) const;
|
||||
|
||||
/// @brief Returns all of the locale Ids (e.g. en_US) put into this model.
|
||||
const QStringList& localeIds() const { return m_localeIds; }
|
||||
|
||||
/** @brief Searches for an item that matches @p predicate
|
||||
*
|
||||
* Returns the row number of the first match, or -1 if there isn't one.
|
||||
*/
|
||||
int find( std::function< bool( const QLocale& ) > predicate ) const;
|
||||
int find( std::function< bool( const Translation& ) > predicate ) const;
|
||||
/// @brief Looks for an item using the same locale, -1 if there isn't one
|
||||
int find( const QLocale& ) const;
|
||||
/// @brief Looks for an item that best matches the 2-letter country code
|
||||
int find( const QString& countryCode ) const;
|
||||
/// @brief Looks up a translation Id
|
||||
int find( const Translation::Id& id ) const;
|
||||
|
||||
private:
|
||||
QVector< Translation* > m_locales;
|
||||
QStringList m_localeIds;
|
||||
};
|
||||
|
||||
/** @brief Returns a model with all available translations.
|
||||
*
|
||||
* The translations are set when Calamares is compiled; the list
|
||||
* of names used can be queried with avalableLanguages().
|
||||
*
|
||||
* This model is a singleton and can be shared.
|
||||
*
|
||||
* NOTE: While the model is not typed const, it should be. Do not modify.
|
||||
*/
|
||||
DLLEXPORT TranslationsModel* availableTranslations();
|
||||
|
||||
/** @brief The list of names (e.g. en, pt_BR) of available translations.
|
||||
*
|
||||
* The translations are set when Calamares is compiled.
|
||||
* At CMake-time, the list CALAMARES_TRANSLATION_LANGUAGES
|
||||
* is used to create the table.
|
||||
*/
|
||||
DLLEXPORT const QStringList& availableLanguages();
|
||||
|
||||
} // namespace Locale
|
||||
} // namespace Calamares
|
||||
#endif
|
||||
480
calamares/src/libcalamares/locale/ZoneData_p.cxxtr
Normal file
480
calamares/src/libcalamares/locale/ZoneData_p.cxxtr
Normal file
@@ -0,0 +1,480 @@
|
||||
/* GENERATED FILE DO NOT EDIT
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Arthur David Olson
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* This file is derived from zone.tab, which has its own copyright statement:
|
||||
*
|
||||
* This file is in the public domain, so clarified as of
|
||||
* 2009-05-17 by Arthur David Olson.
|
||||
*
|
||||
* From Paul Eggert (2018-06-27):
|
||||
* This file is intended as a backward-compatibility aid for older programs.
|
||||
* New programs should use zone1970.tab. This file is like zone1970.tab (see
|
||||
* zone1970.tab's comments), but with the following additional restrictions:
|
||||
*
|
||||
* 1. This file contains only ASCII characters.
|
||||
* 2. The first data column contains exactly one country code.
|
||||
*
|
||||
*/
|
||||
|
||||
/** THIS FILE EXISTS ONLY FOR TRANSLATIONS PURPOSES **/
|
||||
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
/* This returns a reference to local, which is a terrible idea.
|
||||
* Good thing it's not meant to be compiled.
|
||||
*/
|
||||
static const QStringList& tz_regions_table()
|
||||
{
|
||||
return QStringList {
|
||||
QObject::tr("Africa", "tz_regions"),
|
||||
QObject::tr("America", "tz_regions"),
|
||||
QObject::tr("Antarctica", "tz_regions"),
|
||||
QObject::tr("Arctic", "tz_regions"),
|
||||
QObject::tr("Asia", "tz_regions"),
|
||||
QObject::tr("Atlantic", "tz_regions"),
|
||||
QObject::tr("Australia", "tz_regions"),
|
||||
QObject::tr("Europe", "tz_regions"),
|
||||
QObject::tr("Indian", "tz_regions"),
|
||||
QObject::tr("Pacific", "tz_regions"),
|
||||
QString()
|
||||
};
|
||||
}
|
||||
|
||||
/* This returns a reference to local, which is a terrible idea.
|
||||
* Good thing it's not meant to be compiled.
|
||||
*/
|
||||
static const QStringList& tz_names_table()
|
||||
{
|
||||
return QStringList {
|
||||
QObject::tr("Abidjan", "tz_names"),
|
||||
QObject::tr("Accra", "tz_names"),
|
||||
QObject::tr("Adak", "tz_names"),
|
||||
QObject::tr("Addis Ababa", "tz_names"),
|
||||
QObject::tr("Adelaide", "tz_names"),
|
||||
QObject::tr("Aden", "tz_names"),
|
||||
QObject::tr("Algiers", "tz_names"),
|
||||
QObject::tr("Almaty", "tz_names"),
|
||||
QObject::tr("Amman", "tz_names"),
|
||||
QObject::tr("Amsterdam", "tz_names"),
|
||||
QObject::tr("Anadyr", "tz_names"),
|
||||
QObject::tr("Anchorage", "tz_names"),
|
||||
QObject::tr("Andorra", "tz_names"),
|
||||
QObject::tr("Anguilla", "tz_names"),
|
||||
QObject::tr("Antananarivo", "tz_names"),
|
||||
QObject::tr("Antigua", "tz_names"),
|
||||
QObject::tr("Apia", "tz_names"),
|
||||
QObject::tr("Aqtau", "tz_names"),
|
||||
QObject::tr("Aqtobe", "tz_names"),
|
||||
QObject::tr("Araguaina", "tz_names"),
|
||||
QObject::tr("Argentina/Buenos Aires", "tz_names"),
|
||||
QObject::tr("Argentina/Catamarca", "tz_names"),
|
||||
QObject::tr("Argentina/Cordoba", "tz_names"),
|
||||
QObject::tr("Argentina/Jujuy", "tz_names"),
|
||||
QObject::tr("Argentina/La Rioja", "tz_names"),
|
||||
QObject::tr("Argentina/Mendoza", "tz_names"),
|
||||
QObject::tr("Argentina/Rio Gallegos", "tz_names"),
|
||||
QObject::tr("Argentina/Salta", "tz_names"),
|
||||
QObject::tr("Argentina/San Juan", "tz_names"),
|
||||
QObject::tr("Argentina/San Luis", "tz_names"),
|
||||
QObject::tr("Argentina/Tucuman", "tz_names"),
|
||||
QObject::tr("Argentina/Ushuaia", "tz_names"),
|
||||
QObject::tr("Aruba", "tz_names"),
|
||||
QObject::tr("Ashgabat", "tz_names"),
|
||||
QObject::tr("Asmara", "tz_names"),
|
||||
QObject::tr("Astrakhan", "tz_names"),
|
||||
QObject::tr("Asuncion", "tz_names"),
|
||||
QObject::tr("Athens", "tz_names"),
|
||||
QObject::tr("Atikokan", "tz_names"),
|
||||
QObject::tr("Atyrau", "tz_names"),
|
||||
QObject::tr("Auckland", "tz_names"),
|
||||
QObject::tr("Azores", "tz_names"),
|
||||
QObject::tr("Baghdad", "tz_names"),
|
||||
QObject::tr("Bahia", "tz_names"),
|
||||
QObject::tr("Bahia Banderas", "tz_names"),
|
||||
QObject::tr("Bahrain", "tz_names"),
|
||||
QObject::tr("Baku", "tz_names"),
|
||||
QObject::tr("Bamako", "tz_names"),
|
||||
QObject::tr("Bangkok", "tz_names"),
|
||||
QObject::tr("Bangui", "tz_names"),
|
||||
QObject::tr("Banjul", "tz_names"),
|
||||
QObject::tr("Barbados", "tz_names"),
|
||||
QObject::tr("Barnaul", "tz_names"),
|
||||
QObject::tr("Beirut", "tz_names"),
|
||||
QObject::tr("Belem", "tz_names"),
|
||||
QObject::tr("Belgrade", "tz_names"),
|
||||
QObject::tr("Belize", "tz_names"),
|
||||
QObject::tr("Berlin", "tz_names"),
|
||||
QObject::tr("Bermuda", "tz_names"),
|
||||
QObject::tr("Bishkek", "tz_names"),
|
||||
QObject::tr("Bissau", "tz_names"),
|
||||
QObject::tr("Blanc-Sablon", "tz_names"),
|
||||
QObject::tr("Blantyre", "tz_names"),
|
||||
QObject::tr("Boa Vista", "tz_names"),
|
||||
QObject::tr("Bogota", "tz_names"),
|
||||
QObject::tr("Boise", "tz_names"),
|
||||
QObject::tr("Bougainville", "tz_names"),
|
||||
QObject::tr("Bratislava", "tz_names"),
|
||||
QObject::tr("Brazzaville", "tz_names"),
|
||||
QObject::tr("Brisbane", "tz_names"),
|
||||
QObject::tr("Broken Hill", "tz_names"),
|
||||
QObject::tr("Brunei", "tz_names"),
|
||||
QObject::tr("Brussels", "tz_names"),
|
||||
QObject::tr("Bucharest", "tz_names"),
|
||||
QObject::tr("Budapest", "tz_names"),
|
||||
QObject::tr("Bujumbura", "tz_names"),
|
||||
QObject::tr("Busingen", "tz_names"),
|
||||
QObject::tr("Cairo", "tz_names"),
|
||||
QObject::tr("Cambridge Bay", "tz_names"),
|
||||
QObject::tr("Campo Grande", "tz_names"),
|
||||
QObject::tr("Canary", "tz_names"),
|
||||
QObject::tr("Cancun", "tz_names"),
|
||||
QObject::tr("Cape Verde", "tz_names"),
|
||||
QObject::tr("Caracas", "tz_names"),
|
||||
QObject::tr("Casablanca", "tz_names"),
|
||||
QObject::tr("Casey", "tz_names"),
|
||||
QObject::tr("Cayenne", "tz_names"),
|
||||
QObject::tr("Cayman", "tz_names"),
|
||||
QObject::tr("Ceuta", "tz_names"),
|
||||
QObject::tr("Chagos", "tz_names"),
|
||||
QObject::tr("Chatham", "tz_names"),
|
||||
QObject::tr("Chicago", "tz_names"),
|
||||
QObject::tr("Chihuahua", "tz_names"),
|
||||
QObject::tr("Chisinau", "tz_names"),
|
||||
QObject::tr("Chita", "tz_names"),
|
||||
QObject::tr("Choibalsan", "tz_names"),
|
||||
QObject::tr("Christmas", "tz_names"),
|
||||
QObject::tr("Chuuk", "tz_names"),
|
||||
QObject::tr("Cocos", "tz_names"),
|
||||
QObject::tr("Colombo", "tz_names"),
|
||||
QObject::tr("Comoro", "tz_names"),
|
||||
QObject::tr("Conakry", "tz_names"),
|
||||
QObject::tr("Copenhagen", "tz_names"),
|
||||
QObject::tr("Costa Rica", "tz_names"),
|
||||
QObject::tr("Creston", "tz_names"),
|
||||
QObject::tr("Cuiaba", "tz_names"),
|
||||
QObject::tr("Curacao", "tz_names"),
|
||||
QObject::tr("Currie", "tz_names"),
|
||||
QObject::tr("Dakar", "tz_names"),
|
||||
QObject::tr("Damascus", "tz_names"),
|
||||
QObject::tr("Danmarkshavn", "tz_names"),
|
||||
QObject::tr("Dar es Salaam", "tz_names"),
|
||||
QObject::tr("Darwin", "tz_names"),
|
||||
QObject::tr("Davis", "tz_names"),
|
||||
QObject::tr("Dawson", "tz_names"),
|
||||
QObject::tr("Dawson Creek", "tz_names"),
|
||||
QObject::tr("Denver", "tz_names"),
|
||||
QObject::tr("Detroit", "tz_names"),
|
||||
QObject::tr("Dhaka", "tz_names"),
|
||||
QObject::tr("Dili", "tz_names"),
|
||||
QObject::tr("Djibouti", "tz_names"),
|
||||
QObject::tr("Dominica", "tz_names"),
|
||||
QObject::tr("Douala", "tz_names"),
|
||||
QObject::tr("Dubai", "tz_names"),
|
||||
QObject::tr("Dublin", "tz_names"),
|
||||
QObject::tr("DumontDUrville", "tz_names"),
|
||||
QObject::tr("Dushanbe", "tz_names"),
|
||||
QObject::tr("Easter", "tz_names"),
|
||||
QObject::tr("Edmonton", "tz_names"),
|
||||
QObject::tr("Efate", "tz_names"),
|
||||
QObject::tr("Eirunepe", "tz_names"),
|
||||
QObject::tr("El Aaiun", "tz_names"),
|
||||
QObject::tr("El Salvador", "tz_names"),
|
||||
QObject::tr("Enderbury", "tz_names"),
|
||||
QObject::tr("Eucla", "tz_names"),
|
||||
QObject::tr("Fakaofo", "tz_names"),
|
||||
QObject::tr("Famagusta", "tz_names"),
|
||||
QObject::tr("Faroe", "tz_names"),
|
||||
QObject::tr("Fiji", "tz_names"),
|
||||
QObject::tr("Fort Nelson", "tz_names"),
|
||||
QObject::tr("Fortaleza", "tz_names"),
|
||||
QObject::tr("Freetown", "tz_names"),
|
||||
QObject::tr("Funafuti", "tz_names"),
|
||||
QObject::tr("Gaborone", "tz_names"),
|
||||
QObject::tr("Galapagos", "tz_names"),
|
||||
QObject::tr("Gambier", "tz_names"),
|
||||
QObject::tr("Gaza", "tz_names"),
|
||||
QObject::tr("Gibraltar", "tz_names"),
|
||||
QObject::tr("Glace Bay", "tz_names"),
|
||||
QObject::tr("Godthab", "tz_names"),
|
||||
QObject::tr("Goose Bay", "tz_names"),
|
||||
QObject::tr("Grand Turk", "tz_names"),
|
||||
QObject::tr("Grenada", "tz_names"),
|
||||
QObject::tr("Guadalcanal", "tz_names"),
|
||||
QObject::tr("Guadeloupe", "tz_names"),
|
||||
QObject::tr("Guam", "tz_names"),
|
||||
QObject::tr("Guatemala", "tz_names"),
|
||||
QObject::tr("Guayaquil", "tz_names"),
|
||||
QObject::tr("Guernsey", "tz_names"),
|
||||
QObject::tr("Guyana", "tz_names"),
|
||||
QObject::tr("Halifax", "tz_names"),
|
||||
QObject::tr("Harare", "tz_names"),
|
||||
QObject::tr("Havana", "tz_names"),
|
||||
QObject::tr("Hebron", "tz_names"),
|
||||
QObject::tr("Helsinki", "tz_names"),
|
||||
QObject::tr("Hermosillo", "tz_names"),
|
||||
QObject::tr("Ho Chi Minh", "tz_names"),
|
||||
QObject::tr("Hobart", "tz_names"),
|
||||
QObject::tr("Hong Kong", "tz_names"),
|
||||
QObject::tr("Honolulu", "tz_names"),
|
||||
QObject::tr("Hovd", "tz_names"),
|
||||
QObject::tr("Indiana/Indianapolis", "tz_names"),
|
||||
QObject::tr("Indiana/Knox", "tz_names"),
|
||||
QObject::tr("Indiana/Marengo", "tz_names"),
|
||||
QObject::tr("Indiana/Petersburg", "tz_names"),
|
||||
QObject::tr("Indiana/Tell City", "tz_names"),
|
||||
QObject::tr("Indiana/Vevay", "tz_names"),
|
||||
QObject::tr("Indiana/Vincennes", "tz_names"),
|
||||
QObject::tr("Indiana/Winamac", "tz_names"),
|
||||
QObject::tr("Inuvik", "tz_names"),
|
||||
QObject::tr("Iqaluit", "tz_names"),
|
||||
QObject::tr("Irkutsk", "tz_names"),
|
||||
QObject::tr("Isle of Man", "tz_names"),
|
||||
QObject::tr("Istanbul", "tz_names"),
|
||||
QObject::tr("Jakarta", "tz_names"),
|
||||
QObject::tr("Jamaica", "tz_names"),
|
||||
QObject::tr("Jayapura", "tz_names"),
|
||||
QObject::tr("Jersey", "tz_names"),
|
||||
QObject::tr("Jerusalem", "tz_names"),
|
||||
QObject::tr("Johannesburg", "tz_names"),
|
||||
QObject::tr("Juba", "tz_names"),
|
||||
QObject::tr("Juneau", "tz_names"),
|
||||
QObject::tr("Kabul", "tz_names"),
|
||||
QObject::tr("Kaliningrad", "tz_names"),
|
||||
QObject::tr("Kamchatka", "tz_names"),
|
||||
QObject::tr("Kampala", "tz_names"),
|
||||
QObject::tr("Karachi", "tz_names"),
|
||||
QObject::tr("Kathmandu", "tz_names"),
|
||||
QObject::tr("Kentucky/Louisville", "tz_names"),
|
||||
QObject::tr("Kentucky/Monticello", "tz_names"),
|
||||
QObject::tr("Kerguelen", "tz_names"),
|
||||
QObject::tr("Khandyga", "tz_names"),
|
||||
QObject::tr("Khartoum", "tz_names"),
|
||||
QObject::tr("Kiev", "tz_names"),
|
||||
QObject::tr("Kigali", "tz_names"),
|
||||
QObject::tr("Kinshasa", "tz_names"),
|
||||
QObject::tr("Kiritimati", "tz_names"),
|
||||
QObject::tr("Kirov", "tz_names"),
|
||||
QObject::tr("Kolkata", "tz_names"),
|
||||
QObject::tr("Kosrae", "tz_names"),
|
||||
QObject::tr("Kralendijk", "tz_names"),
|
||||
QObject::tr("Krasnoyarsk", "tz_names"),
|
||||
QObject::tr("Kuala Lumpur", "tz_names"),
|
||||
QObject::tr("Kuching", "tz_names"),
|
||||
QObject::tr("Kuwait", "tz_names"),
|
||||
QObject::tr("Kwajalein", "tz_names"),
|
||||
QObject::tr("La Paz", "tz_names"),
|
||||
QObject::tr("Lagos", "tz_names"),
|
||||
QObject::tr("Libreville", "tz_names"),
|
||||
QObject::tr("Lima", "tz_names"),
|
||||
QObject::tr("Lindeman", "tz_names"),
|
||||
QObject::tr("Lisbon", "tz_names"),
|
||||
QObject::tr("Ljubljana", "tz_names"),
|
||||
QObject::tr("Lome", "tz_names"),
|
||||
QObject::tr("London", "tz_names"),
|
||||
QObject::tr("Longyearbyen", "tz_names"),
|
||||
QObject::tr("Lord Howe", "tz_names"),
|
||||
QObject::tr("Los Angeles", "tz_names"),
|
||||
QObject::tr("Lower Princes", "tz_names"),
|
||||
QObject::tr("Luanda", "tz_names"),
|
||||
QObject::tr("Lubumbashi", "tz_names"),
|
||||
QObject::tr("Lusaka", "tz_names"),
|
||||
QObject::tr("Luxembourg", "tz_names"),
|
||||
QObject::tr("Macau", "tz_names"),
|
||||
QObject::tr("Maceio", "tz_names"),
|
||||
QObject::tr("Macquarie", "tz_names"),
|
||||
QObject::tr("Madeira", "tz_names"),
|
||||
QObject::tr("Madrid", "tz_names"),
|
||||
QObject::tr("Magadan", "tz_names"),
|
||||
QObject::tr("Mahe", "tz_names"),
|
||||
QObject::tr("Majuro", "tz_names"),
|
||||
QObject::tr("Makassar", "tz_names"),
|
||||
QObject::tr("Malabo", "tz_names"),
|
||||
QObject::tr("Maldives", "tz_names"),
|
||||
QObject::tr("Malta", "tz_names"),
|
||||
QObject::tr("Managua", "tz_names"),
|
||||
QObject::tr("Manaus", "tz_names"),
|
||||
QObject::tr("Manila", "tz_names"),
|
||||
QObject::tr("Maputo", "tz_names"),
|
||||
QObject::tr("Mariehamn", "tz_names"),
|
||||
QObject::tr("Marigot", "tz_names"),
|
||||
QObject::tr("Marquesas", "tz_names"),
|
||||
QObject::tr("Martinique", "tz_names"),
|
||||
QObject::tr("Maseru", "tz_names"),
|
||||
QObject::tr("Matamoros", "tz_names"),
|
||||
QObject::tr("Mauritius", "tz_names"),
|
||||
QObject::tr("Mawson", "tz_names"),
|
||||
QObject::tr("Mayotte", "tz_names"),
|
||||
QObject::tr("Mazatlan", "tz_names"),
|
||||
QObject::tr("Mbabane", "tz_names"),
|
||||
QObject::tr("McMurdo", "tz_names"),
|
||||
QObject::tr("Melbourne", "tz_names"),
|
||||
QObject::tr("Menominee", "tz_names"),
|
||||
QObject::tr("Merida", "tz_names"),
|
||||
QObject::tr("Metlakatla", "tz_names"),
|
||||
QObject::tr("Mexico City", "tz_names"),
|
||||
QObject::tr("Midway", "tz_names"),
|
||||
QObject::tr("Minsk", "tz_names"),
|
||||
QObject::tr("Miquelon", "tz_names"),
|
||||
QObject::tr("Mogadishu", "tz_names"),
|
||||
QObject::tr("Monaco", "tz_names"),
|
||||
QObject::tr("Moncton", "tz_names"),
|
||||
QObject::tr("Monrovia", "tz_names"),
|
||||
QObject::tr("Monterrey", "tz_names"),
|
||||
QObject::tr("Montevideo", "tz_names"),
|
||||
QObject::tr("Montserrat", "tz_names"),
|
||||
QObject::tr("Moscow", "tz_names"),
|
||||
QObject::tr("Muscat", "tz_names"),
|
||||
QObject::tr("Nairobi", "tz_names"),
|
||||
QObject::tr("Nassau", "tz_names"),
|
||||
QObject::tr("Nauru", "tz_names"),
|
||||
QObject::tr("Ndjamena", "tz_names"),
|
||||
QObject::tr("New York", "tz_names"),
|
||||
QObject::tr("Niamey", "tz_names"),
|
||||
QObject::tr("Nicosia", "tz_names"),
|
||||
QObject::tr("Nipigon", "tz_names"),
|
||||
QObject::tr("Niue", "tz_names"),
|
||||
QObject::tr("Nome", "tz_names"),
|
||||
QObject::tr("Norfolk", "tz_names"),
|
||||
QObject::tr("Noronha", "tz_names"),
|
||||
QObject::tr("North Dakota/Beulah", "tz_names"),
|
||||
QObject::tr("North Dakota/Center", "tz_names"),
|
||||
QObject::tr("North Dakota/New Salem", "tz_names"),
|
||||
QObject::tr("Nouakchott", "tz_names"),
|
||||
QObject::tr("Noumea", "tz_names"),
|
||||
QObject::tr("Novokuznetsk", "tz_names"),
|
||||
QObject::tr("Novosibirsk", "tz_names"),
|
||||
QObject::tr("Ojinaga", "tz_names"),
|
||||
QObject::tr("Omsk", "tz_names"),
|
||||
QObject::tr("Oral", "tz_names"),
|
||||
QObject::tr("Oslo", "tz_names"),
|
||||
QObject::tr("Ouagadougou", "tz_names"),
|
||||
QObject::tr("Pago Pago", "tz_names"),
|
||||
QObject::tr("Palau", "tz_names"),
|
||||
QObject::tr("Palmer", "tz_names"),
|
||||
QObject::tr("Panama", "tz_names"),
|
||||
QObject::tr("Pangnirtung", "tz_names"),
|
||||
QObject::tr("Paramaribo", "tz_names"),
|
||||
QObject::tr("Paris", "tz_names"),
|
||||
QObject::tr("Perth", "tz_names"),
|
||||
QObject::tr("Phnom Penh", "tz_names"),
|
||||
QObject::tr("Phoenix", "tz_names"),
|
||||
QObject::tr("Pitcairn", "tz_names"),
|
||||
QObject::tr("Podgorica", "tz_names"),
|
||||
QObject::tr("Pohnpei", "tz_names"),
|
||||
QObject::tr("Pontianak", "tz_names"),
|
||||
QObject::tr("Port Moresby", "tz_names"),
|
||||
QObject::tr("Port of Spain", "tz_names"),
|
||||
QObject::tr("Port-au-Prince", "tz_names"),
|
||||
QObject::tr("Porto Velho", "tz_names"),
|
||||
QObject::tr("Porto-Novo", "tz_names"),
|
||||
QObject::tr("Prague", "tz_names"),
|
||||
QObject::tr("Puerto Rico", "tz_names"),
|
||||
QObject::tr("Punta Arenas", "tz_names"),
|
||||
QObject::tr("Pyongyang", "tz_names"),
|
||||
QObject::tr("Qatar", "tz_names"),
|
||||
QObject::tr("Qostanay", "tz_names"),
|
||||
QObject::tr("Qyzylorda", "tz_names"),
|
||||
QObject::tr("Rainy River", "tz_names"),
|
||||
QObject::tr("Rankin Inlet", "tz_names"),
|
||||
QObject::tr("Rarotonga", "tz_names"),
|
||||
QObject::tr("Recife", "tz_names"),
|
||||
QObject::tr("Regina", "tz_names"),
|
||||
QObject::tr("Resolute", "tz_names"),
|
||||
QObject::tr("Reunion", "tz_names"),
|
||||
QObject::tr("Reykjavik", "tz_names"),
|
||||
QObject::tr("Riga", "tz_names"),
|
||||
QObject::tr("Rio Branco", "tz_names"),
|
||||
QObject::tr("Riyadh", "tz_names"),
|
||||
QObject::tr("Rome", "tz_names"),
|
||||
QObject::tr("Rothera", "tz_names"),
|
||||
QObject::tr("Saipan", "tz_names"),
|
||||
QObject::tr("Sakhalin", "tz_names"),
|
||||
QObject::tr("Samara", "tz_names"),
|
||||
QObject::tr("Samarkand", "tz_names"),
|
||||
QObject::tr("San Marino", "tz_names"),
|
||||
QObject::tr("Santarem", "tz_names"),
|
||||
QObject::tr("Santiago", "tz_names"),
|
||||
QObject::tr("Santo Domingo", "tz_names"),
|
||||
QObject::tr("Sao Paulo", "tz_names"),
|
||||
QObject::tr("Sao Tome", "tz_names"),
|
||||
QObject::tr("Sarajevo", "tz_names"),
|
||||
QObject::tr("Saratov", "tz_names"),
|
||||
QObject::tr("Scoresbysund", "tz_names"),
|
||||
QObject::tr("Seoul", "tz_names"),
|
||||
QObject::tr("Shanghai", "tz_names"),
|
||||
QObject::tr("Simferopol", "tz_names"),
|
||||
QObject::tr("Singapore", "tz_names"),
|
||||
QObject::tr("Sitka", "tz_names"),
|
||||
QObject::tr("Skopje", "tz_names"),
|
||||
QObject::tr("Sofia", "tz_names"),
|
||||
QObject::tr("South Georgia", "tz_names"),
|
||||
QObject::tr("Srednekolymsk", "tz_names"),
|
||||
QObject::tr("St Barthelemy", "tz_names"),
|
||||
QObject::tr("St Helena", "tz_names"),
|
||||
QObject::tr("St Johns", "tz_names"),
|
||||
QObject::tr("St Kitts", "tz_names"),
|
||||
QObject::tr("St Lucia", "tz_names"),
|
||||
QObject::tr("St Thomas", "tz_names"),
|
||||
QObject::tr("St Vincent", "tz_names"),
|
||||
QObject::tr("Stanley", "tz_names"),
|
||||
QObject::tr("Stockholm", "tz_names"),
|
||||
QObject::tr("Swift Current", "tz_names"),
|
||||
QObject::tr("Sydney", "tz_names"),
|
||||
QObject::tr("Syowa", "tz_names"),
|
||||
QObject::tr("Tahiti", "tz_names"),
|
||||
QObject::tr("Taipei", "tz_names"),
|
||||
QObject::tr("Tallinn", "tz_names"),
|
||||
QObject::tr("Tarawa", "tz_names"),
|
||||
QObject::tr("Tashkent", "tz_names"),
|
||||
QObject::tr("Tbilisi", "tz_names"),
|
||||
QObject::tr("Tegucigalpa", "tz_names"),
|
||||
QObject::tr("Tehran", "tz_names"),
|
||||
QObject::tr("Thimphu", "tz_names"),
|
||||
QObject::tr("Thule", "tz_names"),
|
||||
QObject::tr("Thunder Bay", "tz_names"),
|
||||
QObject::tr("Tijuana", "tz_names"),
|
||||
QObject::tr("Tirane", "tz_names"),
|
||||
QObject::tr("Tokyo", "tz_names"),
|
||||
QObject::tr("Tomsk", "tz_names"),
|
||||
QObject::tr("Tongatapu", "tz_names"),
|
||||
QObject::tr("Toronto", "tz_names"),
|
||||
QObject::tr("Tortola", "tz_names"),
|
||||
QObject::tr("Tripoli", "tz_names"),
|
||||
QObject::tr("Troll", "tz_names"),
|
||||
QObject::tr("Tunis", "tz_names"),
|
||||
QObject::tr("Ulaanbaatar", "tz_names"),
|
||||
QObject::tr("Ulyanovsk", "tz_names"),
|
||||
QObject::tr("Urumqi", "tz_names"),
|
||||
QObject::tr("Ust-Nera", "tz_names"),
|
||||
QObject::tr("Uzhgorod", "tz_names"),
|
||||
QObject::tr("Vaduz", "tz_names"),
|
||||
QObject::tr("Vancouver", "tz_names"),
|
||||
QObject::tr("Vatican", "tz_names"),
|
||||
QObject::tr("Vienna", "tz_names"),
|
||||
QObject::tr("Vientiane", "tz_names"),
|
||||
QObject::tr("Vilnius", "tz_names"),
|
||||
QObject::tr("Vladivostok", "tz_names"),
|
||||
QObject::tr("Volgograd", "tz_names"),
|
||||
QObject::tr("Vostok", "tz_names"),
|
||||
QObject::tr("Wake", "tz_names"),
|
||||
QObject::tr("Wallis", "tz_names"),
|
||||
QObject::tr("Warsaw", "tz_names"),
|
||||
QObject::tr("Whitehorse", "tz_names"),
|
||||
QObject::tr("Windhoek", "tz_names"),
|
||||
QObject::tr("Winnipeg", "tz_names"),
|
||||
QObject::tr("Yakutat", "tz_names"),
|
||||
QObject::tr("Yakutsk", "tz_names"),
|
||||
QObject::tr("Yangon", "tz_names"),
|
||||
QObject::tr("Yekaterinburg", "tz_names"),
|
||||
QObject::tr("Yellowknife", "tz_names"),
|
||||
QObject::tr("Yerevan", "tz_names"),
|
||||
QObject::tr("Zagreb", "tz_names"),
|
||||
QObject::tr("Zaporozhye", "tz_names"),
|
||||
QObject::tr("Zurich", "tz_names"),
|
||||
QString()
|
||||
};
|
||||
}
|
||||
|
||||
271
calamares/src/libcalamares/locale/cldr-extractor.py
Normal file
271
calamares/src/libcalamares/locale/cldr-extractor.py
Normal file
@@ -0,0 +1,271 @@
|
||||
#! /usr/bin/env python3
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
"""
|
||||
Python3 script to scrape some data out of ICU CLDR supplemental data.
|
||||
|
||||
To use this script, you must have downloaded the CLDR data, e.g.
|
||||
http://unicode.org/Public/cldr/35.1/, and extracted the zip file.
|
||||
Run the script from **inside** the common/ durectory that is created
|
||||
(or fix the hard-coded path).
|
||||
|
||||
The script tries to print C++ code that compiles; if there are encoding
|
||||
problems, it will print some kind of representation of the problematic
|
||||
lines.
|
||||
|
||||
To avoid having to cross-reference multiple XML files, the script
|
||||
cheats: it reads the comments as well to get names. So it looks for
|
||||
pairs of lines like this:
|
||||
|
||||
<likelySubtag from="und_BQ" to="pap_Latn_BQ"/>
|
||||
<!--{ ?; ?; Caribbean Netherlands } => { Papiamento; Latin; Caribbean Netherlands }-->
|
||||
|
||||
It extracts the 2-character country code "BQ" from the sub-tag, and
|
||||
parses the comment to get a language and country name (instead of looking up
|
||||
"pap" and "BQ" in other tables). This may be considered a hack.
|
||||
|
||||
A large collection of exceptions can be found in the two *_mapper tables,
|
||||
which massage the CLDR names to Qt enum values.
|
||||
"""
|
||||
#
|
||||
### END USAGE
|
||||
|
||||
import sys
|
||||
|
||||
# These are languages listed in CLDR that don't match
|
||||
# the enum-values in QLocale::Language.
|
||||
language_mapper = {
|
||||
"?" : "AnyLanguage",
|
||||
"Bangla" : "Bengali",
|
||||
"Kalaallisut" : "Greenlandic",
|
||||
"Haitian Creole" : "Haitian",
|
||||
"Kyrgyz" : "Kirghiz",
|
||||
"Norwegian Bokmål" : "NorwegianBokmal",
|
||||
"Tokelau" : "TokelauLanguage",
|
||||
"Tuvalu" : "TuvaluLanguage",
|
||||
}
|
||||
|
||||
country_mapper = {
|
||||
"Åland Islands" : "AlandIslands",
|
||||
"St. Barthélemy" : "SaintBarthelemy",
|
||||
"Côte d’Ivoire" : "IvoryCoast",
|
||||
"Curaçao" : "CuraSao",
|
||||
"Réunion" : "Reunion",
|
||||
"São Tomé & Príncipe" : "SaoTomeAndPrincipe",
|
||||
"Bosnia & Herzegovina" : "BosniaAndHerzegowina",
|
||||
"Czechia" : "CzechRepublic",
|
||||
"St. Pierre & Miquelon" : "SaintPierreAndMiquelon",
|
||||
"Vatican City" : "VaticanCityState",
|
||||
"South Georgia & South Sandwich Islands" : "SouthGeorgiaAndTheSouthSandwichIslands",
|
||||
"Timor-Leste" : "EastTimor",
|
||||
"Wallis & Futuna" : "WallisAndFutunaIslands",
|
||||
"Myanmar (Burma)" : "Myanmar",
|
||||
"Svalbard & Jan Mayen" : "SvalbardAndJanMayenIslands",
|
||||
"St. Martin" : "SaintMartin",
|
||||
"North Macedonia" : "Macedonia",
|
||||
"Hong Kong SAR China" : "HongKong",
|
||||
"Macao SAR China" : "Macau",
|
||||
"Eurozone" : "AnyCountry", # Not likely for GeoIP
|
||||
"Caribbean Netherlands" : "Bonaire", # Bonaire, Saba, St.Eustatius
|
||||
}
|
||||
|
||||
class CountryData:
|
||||
def __init__(self, country_code, language_name, country_name):
|
||||
"""
|
||||
Takes a 2-letter country name, and enum names from
|
||||
QLocale::Language and QLocale::Country. An empty
|
||||
@p country code is acceptable, for the terminating
|
||||
entry in the data array (and yields a 0,0 code).
|
||||
"""
|
||||
if country_code:
|
||||
assert len(country_code) == 2
|
||||
self.country_code = country_code
|
||||
self.language_enum = language_name
|
||||
self.country_enum = country_name
|
||||
else:
|
||||
self.country_code = ""
|
||||
self.language_enum = "AnyLanguage"
|
||||
self.country_enum = "AnyCountry"
|
||||
|
||||
def __str__(self):
|
||||
if self.country_code:
|
||||
char0 = "'{!s}'".format(self.country_code[0])
|
||||
char1 = "'{!s}'".format(self.country_code[1])
|
||||
else:
|
||||
char0 = "0"
|
||||
char1 = "0"
|
||||
|
||||
return "{!s} QLocale::Language::{!s}, QLocale::Country::{!s}, {!s}, {!s} {!s},".format(
|
||||
"{",
|
||||
self.language_enum,
|
||||
self.country_enum,
|
||||
char0,
|
||||
char1,
|
||||
"}")
|
||||
|
||||
# Must match type name below
|
||||
cpp_classname = "CountryData"
|
||||
|
||||
# Must match the output format of __str__ above
|
||||
cpp_declaration = """
|
||||
struct CountryData
|
||||
{
|
||||
QLocale::Language l;
|
||||
QLocale::Country c;
|
||||
char cc1;
|
||||
char cc2;
|
||||
};
|
||||
"""
|
||||
|
||||
|
||||
def extricate_subtags(l1, l2):
|
||||
"""
|
||||
Given two lines @p l1 and @p l2 which are the <likelySubtag> element-line
|
||||
and the comment-line underneath it, return a CountryData for them,
|
||||
or None if the two lines are not relevant (e.g. not the right subtag from,
|
||||
or 3-letter country codes.
|
||||
"""
|
||||
if 'from="und_' not in l1:
|
||||
return
|
||||
if '{ ?; ?;' not in l2:
|
||||
return
|
||||
|
||||
# This is extremely crude "parsing" which chops up the string
|
||||
# by delimiter and then extracts some substring.
|
||||
l1_parts = l1.split("und_")
|
||||
l2_parts = l2.split(";")
|
||||
|
||||
l1_first_quote = l1_parts[1].find('"')
|
||||
l1_code = l1_parts[1][:l1_first_quote]
|
||||
if len(l1_code) != 2:
|
||||
return
|
||||
|
||||
l2_brace = l2_parts[2].find("{")
|
||||
l2_language = l2_parts[2][l2_brace+1:].strip()
|
||||
l2_brace = l2_parts[2].find("}")
|
||||
l2_country = l2_parts[2][:l2_brace-1].strip()
|
||||
|
||||
# Handle mapped cases
|
||||
l2_language = language_mapper.get(l2_language, l2_language)
|
||||
l2_language = l2_language.replace(" ", "")
|
||||
|
||||
# Handle mapped cases and then do a bunch of standard replacements.
|
||||
l2_country = country_mapper.get(l2_country, l2_country)
|
||||
l2_country = l2_country.replace(" ", "").replace("-", "").replace(".","").replace("&","And")
|
||||
|
||||
return CountryData(l1_code, l2_language, l2_country)
|
||||
|
||||
|
||||
def read_subtags_file():
|
||||
"""
|
||||
Returns a list of CountryData objects from the likelySubtags file.
|
||||
"""
|
||||
data = []
|
||||
|
||||
with open("supplemental/likelySubtags.xml", "rt", encoding="UTF-8") as f:
|
||||
l1 = "a line"
|
||||
while l1:
|
||||
l1 = f.readline()
|
||||
if '<likelySubtag from="und_' not in l1:
|
||||
continue
|
||||
l2 = f.readline()
|
||||
|
||||
if l1:
|
||||
assert "likelySubtag" in l1, l1;
|
||||
assert "<!--" in l2, l2;
|
||||
|
||||
data.append(extricate_subtags(l1, l2))
|
||||
|
||||
data.append(CountryData("", None, None))
|
||||
return [c for c in data if c is not None]
|
||||
|
||||
|
||||
cpp_header_comment = """/* GENERATED FILE DO NOT EDIT
|
||||
*
|
||||
* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 1991-2019 Unicode, Inc.
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: CC0
|
||||
*
|
||||
* This file is derived from CLDR data from Unicode, Inc. Applicable terms
|
||||
* are listed at http://unicode.org/copyright.html , of which the most
|
||||
* important are:
|
||||
*
|
||||
* A. Unicode Copyright
|
||||
* 1. Copyright © 1991-2019 Unicode, Inc. All rights reserved.
|
||||
* B. Definitions
|
||||
* Unicode Data Files ("DATA FILES") include all data files under the directories:
|
||||
* https://www.unicode.org/Public/
|
||||
* C. Terms of Use
|
||||
* 1. Certain documents and files on this website contain a legend indicating
|
||||
* that "Modification is permitted." Any person is hereby authorized,
|
||||
* without fee, to modify such documents and files to create derivative
|
||||
* works conforming to the Unicode® Standard, subject to Terms and
|
||||
* Conditions herein.
|
||||
* 2. Any person is hereby authorized, without fee, to view, use, reproduce,
|
||||
* and distribute all documents and files, subject to the Terms and
|
||||
* Conditions herein.
|
||||
*/
|
||||
|
||||
// BEGIN Generated from CLDR data
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
|
||||
"""
|
||||
|
||||
cpp_footer_comment = """
|
||||
// END Generated from CLDR data
|
||||
"""
|
||||
|
||||
|
||||
def make_identifier(classname):
|
||||
"""
|
||||
Given a class name (e.g. CountryData) return an identifer
|
||||
for the data-table for that class.
|
||||
"""
|
||||
identifier = [ classname[0].lower() ]
|
||||
for c in classname[1:]:
|
||||
if c.isupper():
|
||||
identifier.extend(["_", c.lower()])
|
||||
else:
|
||||
identifier.append(c)
|
||||
|
||||
return "".join(identifier)
|
||||
|
||||
|
||||
def export_class(cls, data):
|
||||
"""
|
||||
Given a @p cls and a list of @p data objects from that class,
|
||||
print (to stdout) a C++ file for that data.
|
||||
"""
|
||||
identifier = make_identifier(cls.cpp_classname)
|
||||
|
||||
with open("{!s}_p.cpp".format(cls.cpp_classname), "wt", encoding="UTF-8") as f:
|
||||
f.write(cpp_header_comment)
|
||||
f.write(cls.cpp_declaration)
|
||||
f.write("\nstatic constexpr int const {!s}_size = {!s};\n".format(
|
||||
identifier,
|
||||
len(data)))
|
||||
f.write("\nstatic const {!s} {!s}_table[] = {!s}\n".format(
|
||||
cls.cpp_classname,
|
||||
identifier,
|
||||
"{"))
|
||||
for d in data:
|
||||
f.write(str(d))
|
||||
f.write("\n")
|
||||
f.write("};\n\n");
|
||||
f.write("static_assert( (sizeof({!s}_table) / sizeof({!s})) == {!s}_size, \"Table size mismatch for {!s}\" );\n\n".format(
|
||||
identifier,
|
||||
cls.cpp_classname,
|
||||
identifier,
|
||||
cls.cpp_classname))
|
||||
f.write(cpp_footer_comment)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
export_class(CountryData, read_subtags_file())
|
||||
83
calamares/src/libcalamares/locale/zone-extractor.py
Normal file
83
calamares/src/libcalamares/locale/zone-extractor.py
Normal file
@@ -0,0 +1,83 @@
|
||||
#! /usr/bin/env python3
|
||||
#
|
||||
# === This file is part of Calamares - <https://calamares.io> ===
|
||||
#
|
||||
# SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
# SPDX-License-Identifier: BSD-2-Clause
|
||||
#
|
||||
"""
|
||||
Python3 script to scrape some data out of zoneinfo/zone.tab.
|
||||
|
||||
To use this script, you must have a zone.tab in a standard location,
|
||||
/usr/share/zoneinfo/zone.tab (this is usual on FreeBSD and Linux).
|
||||
|
||||
Prints out a few tables of zone names for use in translations.
|
||||
"""
|
||||
|
||||
def scrape_file(file, regionset, zoneset):
|
||||
for line in file.readlines():
|
||||
if line.startswith("#"):
|
||||
continue
|
||||
parts = line.split("\t")
|
||||
if len(parts) < 3:
|
||||
continue
|
||||
|
||||
zoneid = parts[2]
|
||||
if not "/" in zoneid:
|
||||
continue
|
||||
|
||||
region, zone = zoneid.split("/", 1)
|
||||
|
||||
zone = zone.strip().replace("_", " ")
|
||||
|
||||
regionset.add(region)
|
||||
assert(zone not in zoneset)
|
||||
zoneset.add(zone)
|
||||
|
||||
def write_set(file, label, set):
|
||||
file.write("/* This returns a reference to local, which is a terrible idea.\n * Good thing it's not meant to be compiled.\n */\n")
|
||||
# Note {{ is an escaped { for Python string formatting
|
||||
file.write("static const QStringList& {!s}_table()\n{{\n\treturn QStringList {{\n".format(label))
|
||||
for x in sorted(set):
|
||||
file.write("""\t\tQObject::tr("{!s}", "{!s}"),\n""".format(x, label))
|
||||
file.write("\t\tQString()\n\t};\n}\n\n")
|
||||
|
||||
cpp_header_comment = """/* GENERATED FILE DO NOT EDIT
|
||||
*
|
||||
* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2009 Arthur David Olson
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: CC0-1.0
|
||||
*
|
||||
* This file is derived from zone.tab, which has its own copyright statement:
|
||||
*
|
||||
* This file is in the public domain, so clarified as of
|
||||
* 2009-05-17 by Arthur David Olson.
|
||||
*
|
||||
* From Paul Eggert (2018-06-27):
|
||||
* This file is intended as a backward-compatibility aid for older programs.
|
||||
* New programs should use zone1970.tab. This file is like zone1970.tab (see
|
||||
* zone1970.tab's comments), but with the following additional restrictions:
|
||||
*
|
||||
* 1. This file contains only ASCII characters.
|
||||
* 2. The first data column contains exactly one country code.
|
||||
*
|
||||
*/
|
||||
|
||||
/** THIS FILE EXISTS ONLY FOR TRANSLATIONS PURPOSES **/
|
||||
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
"""
|
||||
|
||||
if __name__ == "__main__":
|
||||
regions=set()
|
||||
zones=set()
|
||||
with open("/usr/share/zoneinfo/zone.tab", "r") as f:
|
||||
scrape_file(f, regions, zones)
|
||||
with open("ZoneData_p.cpp", "w") as f:
|
||||
f.write(cpp_header_comment)
|
||||
write_set(f, "tz_regions", regions)
|
||||
write_set(f, "tz_names", zones)
|
||||
|
||||
29
calamares/src/libcalamares/modulesystem/Actions.h
Normal file
29
calamares/src/libcalamares/modulesystem/Actions.h
Normal file
@@ -0,0 +1,29 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2014 Teo Mrnjavac <teo@kde.org>
|
||||
* SPDX-FileCopyrightText: 2019 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MODULESYSTEM_ACTIONS_H
|
||||
#define MODULESYSTEM_ACTIONS_H
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace ModuleSystem
|
||||
{
|
||||
|
||||
enum class Action : char
|
||||
{
|
||||
Show,
|
||||
Exec
|
||||
};
|
||||
|
||||
} // namespace ModuleSystem
|
||||
} // namespace Calamares
|
||||
|
||||
#endif
|
||||
135
calamares/src/libcalamares/modulesystem/Config.cpp
Normal file
135
calamares/src/libcalamares/modulesystem/Config.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Config.h"
|
||||
|
||||
#include "Preset.h"
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace ModuleSystem
|
||||
{
|
||||
|
||||
class Config::Private
|
||||
{
|
||||
public:
|
||||
std::unique_ptr< Presets > m_presets;
|
||||
};
|
||||
|
||||
Config::Config( QObject* parent )
|
||||
: QObject( parent )
|
||||
, d( std::make_unique< Private >() )
|
||||
{
|
||||
}
|
||||
|
||||
Config::~Config() {}
|
||||
|
||||
bool
|
||||
Config::isEditable( const QString& fieldName ) const
|
||||
{
|
||||
if ( m_unlocked )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if ( d && d->m_presets )
|
||||
{
|
||||
return d->m_presets->isEditable( fieldName );
|
||||
}
|
||||
else
|
||||
{
|
||||
cWarning() << "Checking isEditable, but no presets are configured.";
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Config::ApplyPresets::ApplyPresets( Calamares::ModuleSystem::Config& c, const QVariantMap& configurationMap )
|
||||
: m_c( c )
|
||||
, m_bogus( true )
|
||||
, m_map( Calamares::getSubMap( configurationMap, "presets", m_bogus ) )
|
||||
{
|
||||
c.m_unlocked = true;
|
||||
if ( !c.d->m_presets )
|
||||
{
|
||||
c.d->m_presets = std::make_unique< Presets >();
|
||||
}
|
||||
}
|
||||
|
||||
Config::ApplyPresets::~ApplyPresets()
|
||||
{
|
||||
m_c.m_unlocked = false;
|
||||
|
||||
// Check that there's no **settings** (from the configuration map)
|
||||
// that have not been consumed by apply() -- if they are there,
|
||||
// that means the configuration map specifies things that the
|
||||
// Config object does not expect.
|
||||
bool haveWarned = false;
|
||||
for ( const auto& k : m_map.keys() )
|
||||
{
|
||||
if ( !m_c.d->m_presets->find( k ).isValid() )
|
||||
{
|
||||
if ( !haveWarned )
|
||||
{
|
||||
cWarning() << "Preset configuration contains unused keys";
|
||||
haveWarned = true;
|
||||
}
|
||||
cDebug() << Logger::SubEntry << "Unused key" << k;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Config::ApplyPresets&
|
||||
Config::ApplyPresets::apply( const char* fieldName )
|
||||
{
|
||||
const auto prop = m_c.property( fieldName );
|
||||
if ( !prop.isValid() )
|
||||
{
|
||||
cWarning() << "Applying invalid property" << fieldName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const QString key( fieldName );
|
||||
if ( key.isEmpty() )
|
||||
{
|
||||
cWarning() << "Applying empty field";
|
||||
return *this;
|
||||
}
|
||||
|
||||
if ( m_c.d->m_presets->find( key ).isValid() )
|
||||
{
|
||||
cWarning() << "Applying duplicate property" << fieldName;
|
||||
return *this;
|
||||
}
|
||||
|
||||
if ( m_map.contains( key ) )
|
||||
{
|
||||
// Key has an explicit setting
|
||||
QVariantMap m = Calamares::getSubMap( m_map, key, m_bogus );
|
||||
QVariant value = m[ "value" ];
|
||||
bool editable = Calamares::getBool( m, "editable", true );
|
||||
|
||||
if ( value.isValid() )
|
||||
{
|
||||
m_c.setProperty( fieldName, value );
|
||||
}
|
||||
m_c.d->m_presets->append( PresetField { key, value, editable } );
|
||||
}
|
||||
else
|
||||
{
|
||||
// There is no setting, but since we apply() this field,
|
||||
// we do know about it; put in a preset so that looking
|
||||
// it up won't complani.
|
||||
m_c.d->m_presets->append( PresetField { key, QVariant(), true } );
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace ModuleSystem
|
||||
} // namespace Calamares
|
||||
147
calamares/src/libcalamares/modulesystem/Config.h
Normal file
147
calamares/src/libcalamares/modulesystem/Config.h
Normal file
@@ -0,0 +1,147 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2021 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
* Calamares is Free Software: see the License-Identifier above.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef CALAMARES_MODULESYSTEM_CONFIG_H
|
||||
#define CALAMARES_MODULESYSTEM_CONFIG_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <QObject>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace ModuleSystem
|
||||
{
|
||||
/** @brief Base class for Config-objects
|
||||
*
|
||||
* This centralizes the things every Config-object should
|
||||
* do and provides one source of preset-data. A Config-object
|
||||
* for a module can **optionally** inherit from this class
|
||||
* to get presets-support.
|
||||
*
|
||||
* TODO:3.3 This is not optional
|
||||
* TODO:3.3 Put consistent i18n for Configurations in here too
|
||||
*/
|
||||
class DLLEXPORT Config : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Config( QObject* parent = nullptr );
|
||||
~Config() override;
|
||||
|
||||
/** @brief Set the configuration from the config file
|
||||
*
|
||||
* Subclasses must implement this to load configuration data;
|
||||
* that subclass **should** also call loadPresets() with the
|
||||
* same map, to pick up the "presets" key consistently.
|
||||
*/
|
||||
virtual void setConfigurationMap( const QVariantMap& ) = 0;
|
||||
|
||||
public Q_SLOTS:
|
||||
/** @brief Checks if a @p fieldName is editable according to presets
|
||||
*
|
||||
* If the field is named as a preset, **and** the field is set
|
||||
* to not-editable, returns @c false. Otherwise, return @c true.
|
||||
* Calling this with an unknown field (one for which no presets
|
||||
* are accepted) will print a warning and return @c true.
|
||||
*
|
||||
* @see CONFIG_PREVENT_EDITING
|
||||
*
|
||||
* Most setters will call isEditable() to check if the field should
|
||||
* be editable. Do not count on the setter not being called: the
|
||||
* UI might not have set the field to fixed / constant / not-editable
|
||||
* and then you can have the setter called by changes in the UI.
|
||||
*
|
||||
* To prevent the UI from changing **and** to make sure that the UI
|
||||
* reflects the unchanged value (rather than the changed value it
|
||||
* sent to the Config object), use CONFIG_PREVENT_EDITING, like so:
|
||||
*
|
||||
* CONFIG_PREVENT_EDITING( type, "propertyName" );
|
||||
*
|
||||
* The ; is necessary. <type> is the type of the property; for instance
|
||||
* QString. The name of the property is a (constant) string. The
|
||||
* macro will return (out of the setter it is used in) if the field
|
||||
* is not editable, and will send a notification event with the old
|
||||
* value as soon as the event loop resumes.
|
||||
*/
|
||||
bool isEditable( const QString& fieldName ) const;
|
||||
|
||||
protected:
|
||||
friend class ApplyPresets;
|
||||
/** @brief "Builder" class for presets
|
||||
*
|
||||
* Derived classes should instantiate this (with themselves,
|
||||
* and the whole configuration map that is passed to
|
||||
* setConfigurationMap()) and then call .apply() to apply
|
||||
* the presets specified in the configuration to the **named**
|
||||
* QObject properties.
|
||||
*/
|
||||
class ApplyPresets
|
||||
{
|
||||
public:
|
||||
/** @brief Create a preset-applier for this config
|
||||
*
|
||||
* The @p configurationMap should be the one passed in to
|
||||
* setConfigurationMap() . Presets are extracted from the
|
||||
* standard key *presets* and can be applied to the configuration
|
||||
* with apply() or operator<<.
|
||||
*/
|
||||
ApplyPresets( Config& c, const QVariantMap& configurationMap );
|
||||
~ApplyPresets();
|
||||
|
||||
/** @brief Add a preset for the given @p fieldName
|
||||
*
|
||||
* This checks for preset-entries in the configuration map that was
|
||||
* passed in to the constructor.
|
||||
*/
|
||||
ApplyPresets& apply( const char* fieldName );
|
||||
/** @brief Alternate way of writing apply()
|
||||
*/
|
||||
ApplyPresets& operator<<( const char* fieldName ) { return apply( fieldName ); }
|
||||
|
||||
private:
|
||||
Config& m_c;
|
||||
bool m_bogus = true;
|
||||
const QVariantMap m_map;
|
||||
};
|
||||
|
||||
private:
|
||||
class Private;
|
||||
std::unique_ptr< Private > d;
|
||||
bool m_unlocked = false;
|
||||
};
|
||||
} // namespace ModuleSystem
|
||||
} // namespace Calamares
|
||||
|
||||
/// @see Config::isEditable()
|
||||
//
|
||||
// This needs to be a macro, because Q_ARG() is a macro that stringifies
|
||||
// the type name.
|
||||
#define CONFIG_PREVENT_EDITING( type, fieldName ) \
|
||||
do \
|
||||
{ \
|
||||
if ( !isEditable( QStringLiteral( fieldName ) ) ) \
|
||||
{ \
|
||||
auto prop = property( fieldName ); \
|
||||
const auto& metaobject = metaObject(); \
|
||||
auto metaprop = metaobject->property( metaobject->indexOfProperty( fieldName ) ); \
|
||||
if ( metaprop.hasNotifySignal() ) \
|
||||
{ \
|
||||
metaprop.notifySignal().invoke( this, Qt::QueuedConnection, Q_ARG( type, prop.value< type >() ) ); \
|
||||
} \
|
||||
return; \
|
||||
} \
|
||||
} while ( 0 )
|
||||
|
||||
|
||||
#endif
|
||||
171
calamares/src/libcalamares/modulesystem/Descriptor.cpp
Normal file
171
calamares/src/libcalamares/modulesystem/Descriptor.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
/* === This file is part of Calamares - <https://calamares.io> ===
|
||||
*
|
||||
* SPDX-FileCopyrightText: 2020 Adriaan de Groot <groot@kde.org>
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#include "Descriptor.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
#include "utils/Variant.h"
|
||||
|
||||
namespace Calamares
|
||||
{
|
||||
namespace ModuleSystem
|
||||
{
|
||||
|
||||
const NamedEnumTable< Type >&
|
||||
typeNames()
|
||||
{
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
static const NamedEnumTable< Type > table{
|
||||
{ QStringLiteral( "job" ), Type::Job },
|
||||
{ QStringLiteral( "view" ), Type::View },
|
||||
{ QStringLiteral( "viewmodule" ), Type::View },
|
||||
{ QStringLiteral( "jobmodule" ), Type::Job }
|
||||
};
|
||||
// *INDENT-ON*
|
||||
// clang-format on
|
||||
return table;
|
||||
}
|
||||
|
||||
const NamedEnumTable< Interface >&
|
||||
interfaceNames()
|
||||
{
|
||||
// *INDENT-OFF*
|
||||
// clang-format off
|
||||
static const NamedEnumTable< Interface > table {
|
||||
{ QStringLiteral("process"), Interface::Process },
|
||||
{ QStringLiteral("qtplugin"), Interface::QtPlugin },
|
||||
{ QStringLiteral("python"), Interface::Python },
|
||||
};
|
||||
// *INDENT-ON*
|
||||
// clang-format on
|
||||
return table;
|
||||
}
|
||||
|
||||
Descriptor::Descriptor() {}
|
||||
|
||||
Descriptor
|
||||
Descriptor::fromDescriptorData( const QVariantMap& moduleDesc, const QString& descriptorPath )
|
||||
{
|
||||
Descriptor d;
|
||||
Logger::Once o;
|
||||
|
||||
{
|
||||
bool typeOk = false;
|
||||
QString typeValue = moduleDesc.value( "type" ).toString();
|
||||
Type t = typeNames().find( typeValue, typeOk );
|
||||
if ( !typeOk )
|
||||
{
|
||||
if ( o )
|
||||
{
|
||||
cWarning() << o << "Descriptor file" << descriptorPath;
|
||||
}
|
||||
cWarning() << o << "Module descriptor contains invalid *type*" << typeValue;
|
||||
}
|
||||
|
||||
bool interfaceOk = false;
|
||||
QString interfaceValue = moduleDesc.value( "interface" ).toString();
|
||||
Interface i = interfaceNames().find( interfaceValue, interfaceOk );
|
||||
if ( !interfaceOk )
|
||||
{
|
||||
if ( o )
|
||||
{
|
||||
cWarning() << o << "Descriptor file" << descriptorPath;
|
||||
}
|
||||
cWarning() << o << "Module descriptor contains invalid *interface*" << interfaceValue;
|
||||
}
|
||||
|
||||
d.m_name = moduleDesc.value( "name" ).toString();
|
||||
if ( typeOk && interfaceOk && !d.m_name.isEmpty() )
|
||||
{
|
||||
d.m_type = t;
|
||||
d.m_interface = i;
|
||||
d.m_isValid = true;
|
||||
}
|
||||
}
|
||||
if ( !d.m_isValid )
|
||||
{
|
||||
return d;
|
||||
}
|
||||
|
||||
d.m_isEmergeny = Calamares::getBool( moduleDesc, "emergency", false );
|
||||
d.m_hasConfig = !Calamares::getBool( moduleDesc, "noconfig", false ); // Inverted logic during load
|
||||
d.m_requiredModules = Calamares::getStringList( moduleDesc, "requiredModules" );
|
||||
d.m_weight = int( Calamares::getInteger( moduleDesc, "weight", -1 ) );
|
||||
|
||||
QStringList consumedKeys { "type", "interface", "name", "emergency", "noconfig", "requiredModules", "weight" };
|
||||
|
||||
switch ( d.interface() )
|
||||
{
|
||||
case Interface::QtPlugin:
|
||||
d.m_script = Calamares::getString( moduleDesc, "load" );
|
||||
consumedKeys << "load";
|
||||
break;
|
||||
case Interface::Python:
|
||||
d.m_script = Calamares::getString( moduleDesc, "script" );
|
||||
if ( d.m_script.isEmpty() )
|
||||
{
|
||||
if ( o )
|
||||
{
|
||||
cWarning() << o << "Descriptor file" << descriptorPath;
|
||||
}
|
||||
cWarning() << o << "Module descriptor contains no *script*" << d.name();
|
||||
d.m_isValid = false;
|
||||
}
|
||||
consumedKeys << "script";
|
||||
break;
|
||||
case Interface::Process:
|
||||
d.m_script = Calamares::getString( moduleDesc, "command" );
|
||||
d.m_processTimeout = int( Calamares::getInteger( moduleDesc, "timeout", 30 ) );
|
||||
d.m_processChroot = Calamares::getBool( moduleDesc, "chroot", false );
|
||||
if ( d.m_processTimeout < 0 )
|
||||
{
|
||||
d.m_processTimeout = 0;
|
||||
}
|
||||
if ( d.m_script.isEmpty() )
|
||||
{
|
||||
if ( o )
|
||||
{
|
||||
cWarning() << o << "Descriptor file" << descriptorPath;
|
||||
}
|
||||
cWarning() << o << "Module descriptor contains no *script*" << d.name();
|
||||
d.m_isValid = false;
|
||||
}
|
||||
consumedKeys << "command"
|
||||
<< "timeout"
|
||||
<< "chroot";
|
||||
break;
|
||||
}
|
||||
|
||||
if ( !d.m_isValid )
|
||||
{
|
||||
return d;
|
||||
}
|
||||
|
||||
QStringList superfluousKeys;
|
||||
for ( auto kv = moduleDesc.keyBegin(); kv != moduleDesc.keyEnd(); ++kv )
|
||||
{
|
||||
if ( !consumedKeys.contains( *kv ) )
|
||||
{
|
||||
superfluousKeys << *kv;
|
||||
}
|
||||
}
|
||||
if ( !superfluousKeys.isEmpty() )
|
||||
{
|
||||
if ( o )
|
||||
{
|
||||
cWarning() << o << "Descriptor file" << descriptorPath;
|
||||
}
|
||||
cWarning() << o << "Module descriptor contains extra keys:" << Logger::DebugList( superfluousKeys );
|
||||
d.m_isValid = false;
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
} // namespace ModuleSystem
|
||||
} // namespace Calamares
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user