First commit

There was history before but now there is no more.
This commit is contained in:
Timotej Lazar 2021-06-14 19:09:53 +02:00
commit 8d57bfb1ae
No known key found for this signature in database
GPG key ID: B6F38793D143456F
35 changed files with 2231 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
build/
*.qmlc

109
Event.qml Normal file
View file

@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
import 'util.js' as Util
// This is the delegate for event list items.
Pane {
id: control
property int time
property alias tag: tag.text
property alias fields: inputs.model
property bool editing: false
signal remove
clip: true
height: visible ? stuff.height + 2*padding : 0 // TODO fix filtering and remove this
padding: 2
// set to current value or default
function reset() {
for (var i = 0; i < fields.count; i++) {
const child = inputs.itemAt(i)
if (child && child.item)
child.item.set(fields.get(i).value)
}
}
function store() {
for (var i = 0; i < fields.count; i++)
fields.setProperty(i, 'value', inputs.itemAt(i).item.value)
}
// Pass keys to each field input in order.
Keys.forwardTo: Array.from({ length: inputs.count }, (_, i) => inputs.itemAt(i).item)
Behavior on height { NumberAnimation { duration: 50 } }
ColumnLayout {
id: stuff
anchors { left: parent.left; right: parent.right; margins: 5 }
RowLayout {
Label {
text: new Date(time).toISOString().substr(12, 9)
font.pixelSize: 10
Layout.alignment: Qt.AlignBaseline
}
Label {
id: tag
font.weight: Font.DemiBold
Layout.alignment: Qt.AlignBaseline
}
Label {
text: {
var str = ''
for (var i = 0; i < fields.count; i++) {
const field = fields.get(i)
if (field.value && field.type !== 'TextArea')
str += (field.type === 'Bool' ? field.name : field.value) + ' '
}
return str
}
elide: Text.ElideRight
textFormat: Text.PlainText
Layout.fillWidth: true
Layout.alignment: Qt.AlignBaseline
}
}
// Eventspecific inputs.
GridLayout {
id: fieldset
flow: GridLayout.TopToBottom
rows: inputs.count
columnSpacing: 10
visible: editing
// Labels.
Repeater {
model: inputs.model
delegate: Label {
text: Util.addShortcut(model.name, model.key)
Layout.alignment: Qt.AlignRight
}
}
// Inputs.
Repeater {
id: inputs
delegate: Loader {
source: 'qrc:/Fields/' + model.type + '.qml'
Layout.fillHeight: true
Layout.fillWidth: true
Binding {
target: item; property: 'definition'
value: model
}
}
}
}
}
}

155
Events.qml Normal file
View file

@ -0,0 +1,155 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
import QtQml.Models 2.1
import 'util.js' as Util
ListView {
id: control
property bool editing: false
property var tags: []
signal changed
clip: true
focus: true
keyNavigationEnabled: true
highlightMoveDuration: 0
highlightResizeDuration: 0
ScrollBar.vertical: ScrollBar { anchors.right: parent.right }
// Create a new blank event, insert it and start editing.
function create(time, tag, fields) {
const index = Util.find(list, 'time', time)
list.insert(index, {
'time': time,
'tag': tag,
'fields': fields,
})
currentIndex = index
if (fields.length > 0)
editing = true
changed()
}
function clear() {
list.clear()
}
function load(json) {
// Return list of fields for the given tag.
function getFields(name) {
for (var i = 0; i < tags.length; i++)
if (tags[i].tag === name)
return tags[i].fields
return []
}
for (var i = 0; i < json.length; i++) {
const event = json[i]
var fields = getFields(event.tag)
for (var j = 0; j < fields.length; j++)
fields[j].value = event.fields[fields[j].name]
list.append({ 'time': event.time, 'tag': event.tag, 'fields': fields })
}
forceActiveFocus()
}
function save() {
var data = []
for (var i = 0; i < list.count; i++) {
const event = list.get(i)
var fields = {}
for (var j = 0; j < event.fields.count; j++) {
const field = event.fields.get(j)
fields[field.name] = field.value
}
data.push({ 'time': event.time, 'tag': event.tag, 'fields': fields })
}
return data
}
onCurrentIndexChanged: editing = false
Keys.onPressed: {
switch (event.key) {
case Qt.Key_Enter:
case Qt.Key_Return:
if (editing) {
currentItem.store()
changed()
editing = false
} else {
if (currentItem.fields.count > 0)
editing = true
}
break
case Qt.Key_Escape:
if (editing) {
currentItem.reset()
editing = false
}
break
case Qt.Key_Delete:
editing = false
if (currentIndex >= 0 && currentIndex < list.count) {
list.remove(currentIndex)
changed()
}
break
case Qt.Key_Tab:
case Qt.Key_Backtab:
// swallow tabs so we dont lose focus when editing
break
default:
return
}
event.accepted = true
}
model: ListModel {
id: list
dynamicRoles: true
}
delegate: Event {
id: item
time: model.time
tag: model.tag
fields: model.fields
width: control.width
editing: control.editing && ListView.isCurrentItem
background: Rectangle {
anchors.fill: parent
color: border.width > 0 ? Util.alphize(border.color, 0.1) :
(index % 2 === 0 ? palette.base : palette.alternateBase)
border {
color: editing ? palette.highlight : palette.dark
width: item.ListView.isCurrentItem ? 1 : 0
}
radius: border.width
}
Connections {
enabled: ListView.currentIndex === index
function onHeightChanged() {
control.positionViewAtIndex(index, ListView.Contain)
}
}
onEditingChanged: {
reset()
if (editing)
forceActiveFocus()
}
onRemove: {
list.remove(ObjectModel.index)
}
}
}

27
Fields/Bool.qml Normal file
View file

@ -0,0 +1,27 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
Row {
id: control
width: parent.width
property var definition
property alias value: input.checked
Keys.onPressed: {
if (event.text === definition.key) {
value = !value
event.accepted = true
}
}
function set(val) { value = val || false }
CheckBox {
id: input
focusPolicy: Qt.NoFocus
padding: 0
font.capitalization: Font.SmallCaps
}
}

59
Fields/Enum.qml Normal file
View file

@ -0,0 +1,59 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import '../util.js' as Util
Column {
id: control
property var definition
property int index: -1
readonly property string value: index >= 0 ? definition.values.get(index).name : ''
function set(val) {
for (var i = 0; i < definition.values.count; i++) {
if (definition.values.get(i).name === val) {
index = i
return true
}
}
index = -1
}
Keys.onPressed: {
for (var i = 0; i < definition.values.count; i++) {
if (definition.values.get(i).key === event.text) {
index = (index === i ? -1 : i)
event.accepted = true
break
}
}
}
Flow {
spacing: 5
width: parent.width
ButtonGroup { id: buttons }
Repeater {
model: definition.values
delegate: Button {
ButtonGroup.group: buttons
checkable: true
checked: control.index === index
focusPolicy: Qt.NoFocus
implicitWidth: implicitContentWidth + leftPadding + rightPadding
padding: 0
leftPadding: 5
rightPadding: leftPadding
onClicked: control.index = (control.index === index ? -1 : index)
text: Util.addShortcut(name, key)
}
}
}
}

50
Fields/Text.qml Normal file
View file

@ -0,0 +1,50 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
Label {
id: control
property var definition
property alias value: control.text
Keys.onPressed: {
if (event.text === definition.key) {
popup.open()
event.accepted = true
}
}
function set(val) { value = val || '' }
elide: Text.ElideRight
Popup {
id: popup
width: control.width
height: control.height
padding: 0
onOpened: {
input.text = value
input.forceActiveFocus()
}
TextInput {
id: input
clip: true
padding: 2
topPadding: 0
bottomPadding: 0
width: parent.width
onAccepted: {
value = input.text.trim()
popup.close()
}
}
}
}

54
Fields/TextArea.qml Normal file
View file

@ -0,0 +1,54 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
Label {
id: control
property var definition
property alias value: control.text
Keys.onPressed: {
if (event.text === definition.key) {
popup.open()
event.accepted = true
}
}
function set(val) { value = (val || '').trim() }
wrapMode: Text.Wrap
Popup {
id: popup
width: control.width
height: input.height
padding: 0
onOpened: {
input.text = value
input.forceActiveFocus()
}
TextArea {
id: input
padding: 2
topPadding: 0
bottomPadding: 0
width: parent.width
wrapMode: TextEdit.Wrap
Keys.onPressed: {
if (event.modifiers === Qt.NoModifier) {
if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {
value = input.text.trim()
popup.close()
}
}
}
}
}
}

34
Filter.qml Normal file
View file

@ -0,0 +1,34 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
GridLayout {
property var tags: []
function check(item) {
if (item.tag === tags.currentText)
return true
return false
}
signal changed
columns: 2
Label { text: qsTr('Tag') }
ComboBox {
model: tags
textRole: 'tag'
Layout.fillWidth: true
onCurrentTextChanged: changed()
}
Label {
text: qsTr('Filters are not implemented yet! 😊')
wrapMode: Text.Wrap
Layout.fillWidth: true
Layout.columnSpan: 2
}
}

31
README.md Normal file
View file

@ -0,0 +1,31 @@
# Fuzbal
Friendly Usable ZeroBullshit Analyzer & Labeler: a keyboarddriven utility for tagging events in video clips, created for analyzing football matches but likely useful for other kinds of videos.
While functional, this project is not yet productionready. While unlikely, it might eat your files or your cake.
## Usage
Open a video. Press `space` to start or stop video playback. Seek with `←` and `→`. Use `,` and `.` to decrease and increase the playback rate, and `=` to reset it.
To add a new event, press the key for the corresponding tag and fill out event details. Custom tags can be defined as a JSON array and loaded at runtime. See `tags.json` for the builtin example showcasing all supported field types.
Events for `video.mp4` are saved in JSON format in the file `video.mp4.events`. Saved file includes tag definitions, which are loaded automatically when the file is opened. Event timestamps are stored with millisecond precision.
## Compiling
Qt≥5.14 is required. Once Debian catches up, this might be enough:
# apt install git qtmultimedia-dev qtquickcontrols2-dev qml-module-qtmultimedia qml-module-qtquick-dialogs
One or more of the `gst-plugins` packages are needed at runtime to play videos. Build with:
$ mkdir build && cd build
$ qmake ..
$ make
This should create the `fuzbal` binary.
## License
This project is released into the public domain. Breeze icons are distributed under LGPL3+. See `UNLICENSE` and `icons/breeze/LICENSE` for details.

245
Sidebar.qml Normal file
View file

@ -0,0 +1,245 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
import Qt.labs.platform 1.1
Page {
id: control
property bool modified: false
property Video video
function clear() {
description.clear()
events.clear()
}
function save() {
modified = false
return {
meta: {
version: Qt.application.version,
video: video.source.toString(),
description: description.text
},
tags: tags.model,
events: events.save()
}
}
function load(data) {
if (data.meta.description !== undefined)
description.text = data.meta.description
if (data.tags !== undefined)
tags.model = data.tags
events.load(data.events)
modified = false
}
FileDialog {
id: videoDialog
title: qsTr('Open video')
onAccepted: {
clear()
video.source = currentFile
const events = io.read(video.source+'.events')
if (events)
load(JSON.parse(events))
}
}
FileDialog {
id: tagsDialog
title: qsTr('Load tags')
nameFilters: [qsTr('JSON files (*.json)'), qsTr('All files (*)')]
onAccepted: tags.model = JSON.parse(io.read(currentFile))
}
Keys.forwardTo: [tags, video]
header: ToolBar {
horizontalPadding: 0
RowLayout {
anchors.fill: parent
ToolButton {
action: Action {
icon.name: 'document-open'
shortcut: StandardKey.Open
onTriggered: videoDialog.open()
}
focusPolicy: Qt.NoFocus
}
Label {
text: video.loaded ? video.source : ''
elide: Text.ElideLeft
Layout.fillWidth: true
}
ToolButton {
action: Action {
onTriggered: io.write(video.source+'.events', JSON.stringify(save()))
shortcut: StandardKey.Save
icon.name: 'document-save'
enabled: video.loaded && control.modified
}
visible: video.loaded
opacity: enabled ? 1 : 0.25
focusPolicy: Qt.NoFocus
}
}
}
ColumnLayout {
anchors.fill: parent
// Description box.
ColumnLayout {
spacing: 0
RowLayout {
Label {
text: qsTr('Description')
Layout.fillWidth: true
}
Label { text: description.enabled ? qsTr('') : qsTr('+') }
TapHandler { onTapped: description.enabled = !description.enabled }
}
ScrollView {
Layout.fillWidth: true
Layout.preferredHeight: 100
contentWidth: parent.availableWidth
padding: 1
visible: description.enabled
background: Frame { }
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
TextArea {
id: description
background: Rectangle { color: palette.base }
leftPadding: padding
selectByMouse: true
wrapMode: Text.Wrap
onTextChanged: modified = true
KeyNavigation.priority: KeyNavigation.BeforeItem
KeyNavigation.tab: events
}
}
}
// Events list.
ColumnLayout {
spacing: 0
Label { text: qsTr('Events') }
Frame {
padding: 1
Layout.fillWidth: true
Layout.fillHeight: true
Events {
id: events
anchors.fill: parent
focus: true
tags: tags.model
onEditingChanged: video.pause(editing)
onChanged: modified = true
MouseArea {
anchors.fill: parent
enabled: !parent.editing
onPressed: {
const index = events.indexAt(mouse.x, mouse.y)
if (index !== -1) {
events.currentIndex = index
video.seek(events.itemAtIndex(index).time)
}
forceActiveFocus()
}
}
}
}
}
Page {
Layout.fillWidth: true
Layout.fillHeight: false
StackLayout {
currentIndex: bar.currentIndex
implicitHeight: children[currentIndex].implicitHeight
width: parent.width
Frame {
padding: 5
enabled: visible
Layout.fillWidth: true
ColumnLayout {
width: parent.width
spacing: 0
RowLayout {
Label {
text: qsTr('Tags')
Layout.fillWidth: true
}
ToolButton {
icon.name: 'document-open'
Layout.alignment: Qt.AlignTop
onClicked: tagsDialog.open()
focusPolicy:Qt.NoFocus
}
}
Tags {
id: tags
model: JSON.parse(io.read('qrc:/tags.json'))
enabled: video.loaded && !events.editing
onClicked: events.create(video.time, tag, fields)
Layout.fillWidth: true
}
}
}
Frame {
padding: 5
enabled: visible
Layout.fillWidth: true
Filter {
id: filter
tags: tags.model
width: parent.width
onChanged: print('filter changed')
}
}
}
footer: TabBar {
id: bar
Layout.fillWidth: true
ActionGroup { id: tabActions }
Repeater {
model: [
{ text: qsTr('&Annotate'), shortcut: qsTr('Alt+A') },
{ text: qsTr('&Filter'), shortcut: qsTr('Alt+F') }
]
delegate: TabButton {
action: Action {
ActionGroup.group: tabActions
shortcut: modelData.shortcut
}
text: modelData.text
focusPolicy: Qt.NoFocus
padding: 5
onClicked: TabBar.tabBar.setCurrentIndex(index)
}
}
}
}
}
}

47
Tags.qml Normal file
View file

@ -0,0 +1,47 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
import 'util.js' as Util
// Tag list.
Page {
id: control
property alias model: tags.model
signal clicked(string tag, var fields)
Keys.enabled: enabled
Keys.onPressed: {
for (var i = 0; i < model.length; i++) {
const tag = model[i]
if (tag.key === event.text) {
clicked(tag.tag, tag.fields)
return
}
}
event.accepted = false
}
RowLayout {
width: parent.width
Flow {
spacing: 5
Layout.fillWidth: true
Repeater {
id: tags
delegate: Button {
text: Util.addShortcut(modelData.tag, modelData.key)
onClicked: control.clicked(modelData.tag, modelData.fields)
focusPolicy: Qt.NoFocus
implicitWidth: implicitContentWidth + 2*padding
}
}
}
}
}

24
UNLICENSE Normal file
View file

@ -0,0 +1,24 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

150
Video.qml Normal file
View file

@ -0,0 +1,150 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.14
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
import QtMultimedia 5.11
Page {
property bool loaded:
media.status !== MediaPlayer.NoMedia &&
media.status !== MediaPlayer.InvalidMedia &&
media.status !== MediaPlayer.UnknownStatus
property alias source: media.source
property alias time: media.position
function pause(yes) {
if (yes === undefined)
yes = media.playbackState === MediaPlayer.PlayingState
if (yes)
media.pause()
else
media.play()
}
function seek(offset, relative) {
if (relative)
offset += media.position
media.seek(offset)
}
Keys.onPressed: {
switch (event.key) {
// (Un)pause video.
case Qt.Key_Space:
pause()
break
// Seek video.
case Qt.Key_Left:
seek(-500, true)
break
case Qt.Key_Right:
seek(500, true)
break
// Change playback rate.
case Qt.Key_Equal:
rate.reset()
break
case Qt.Key_Comma:
rate.decrease()
break
case Qt.Key_Period:
rate.increase()
break
default:
return // dont accept the event
}
event.accepted = true
}
// Video.
ColumnLayout {
spacing: 0
anchors.fill: parent
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
color: 'black'
clip: true
VideoOutput {
anchors.fill: parent
fillMode: VideoOutput.PreserveAspectFit
source: media
transform: Scale {
id: zoom
property real scale: 1.0
xScale: scale
yScale: scale
origin.x: wheel.point.position.x
origin.y: wheel.point.position.y
}
MediaPlayer {
id: media
notifyInterval: 100
playbackRate: Number.fromLocaleString(rate.displayText)
volume: QtMultimedia.convertVolume(
volume.value,
QtMultimedia.LogarithmicVolumeScale,
QtMultimedia.LinearVolumeScale)
}
TapHandler {
acceptedButtons: Qt.RightButton
onTapped: pause()
}
WheelHandler {
id: wheel
onWheel: zoom.scale = Math.max(1.0, (event.angleDelta.y > 0 ? 1.1 : 0.9) * zoom.scale)
}
}
}
// Video controls.
RowLayout {
Layout.margins: 5
Button {
icon.name: 'media-playback-pause'
implicitWidth: implicitHeight
checkable: true
checked: media.playbackState !== MediaPlayer.PlayingState
onClicked: checked ? media.pause() : media.play()
}
Label { text: new Date(media.position).toISOString().substr(12, 9) }
Slider {
Layout.fillWidth: true
from: 0; to: media.duration
value: media.position
onMoved: media.seek(value)
}
Label { text: new Date(media.duration).toISOString().substr(12, 7) }
Volume {
id: volume
muted: media.muted
focusPolicy: Qt.NoFocus
}
// Playback speed control.
SpinBox {
id: rate
implicitWidth: 80
focusPolicy: Qt.NoFocus
from: 25; to: 250; stepSize: 25
value: 100
function reset() { value = 100 }
textFromValue: function (value, locale) {
return (value / 100).toLocaleString(locale, 'f', 2)
}
}
}
}
}

30
Volume.qml Normal file
View file

@ -0,0 +1,30 @@
// SPDX-License-Identifier: Unlicense
import QtQuick.Controls 2.13
Button {
property bool muted
property alias value: volume.value
implicitWidth: implicitHeight
icon.name: 'audio-volume-' +
(muted ? 'muted' :
(value < 0.33 ? 'low' :
(value < 0.66 ? 'medium' : 'high')))
checkable: true
checked: popup.opened
onClicked: popup.opened ? popup.close() : popup.open()
Popup {
id: popup
y: -height
height: 100
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
Slider {
id: volume
anchors.fill: parent
orientation: Qt.Vertical
}
}
}

17
fuzbal.pro Normal file
View file

@ -0,0 +1,17 @@
# SPDX-License-Identifier: Unlicense
QT += multimedia qml quick quickcontrols2 svg widgets
CONFIG += embed_translations lrelease
DEFINES += GIT_VERSION=\\\"$$system(git -C "$$_PRO_FILE_PWD_" describe --always --tags)\\\"
SOURCES += \
main.cpp
HEADERS += \
io.h
RESOURCES += main.qrc icons.qrc
TRANSLATIONS += translations/fuzbal_sl.ts

15
icons.qrc Normal file
View file

@ -0,0 +1,15 @@
<!-- SPDX-License-Identifier: Unlicense -->
<RCC>
<qresource prefix="/">
<file>icons/breeze/index.theme</file>
<file>icons/breeze/actions/symbolic/document-open.svg</file>
<file>icons/breeze/actions/symbolic/document-save.svg</file>
<file>icons/breeze/actions/symbolic/edit-clear.svg</file>
<file>icons/breeze/actions/symbolic/edit-delete.svg</file>
<file>icons/breeze/actions/symbolic/media-playback-pause.svg</file>
<file>icons/breeze/status/symbolic/audio-volume-high.svg</file>
<file>icons/breeze/status/symbolic/audio-volume-low.svg</file>
<file>icons/breeze/status/symbolic/audio-volume-medium.svg</file>
<file>icons/breeze/status/symbolic/audio-volume-muted.svg</file>
</qresource>
</RCC>

165
icons/breeze/LICENSE Normal file
View file

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 3 L 2 6 L 2 7 L 2 13 L 2 14 L 14 14 L 14 13 L 14 6 L 14 5 L 14 4 L 9.0078125 4 L 7.0078125 2 L 7 2.0078125 L 7 2 L 3 2 L 2 2 z M 3 3 L 6.5917969 3 L 7.59375 4 L 7 4 L 7 4.0078125 L 6.9921875 4 L 4.9921875 6 L 3 6 L 3 3 z M 3 7 L 13 7 L 13 13 L 3 13 L 3 7 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 603 B

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 2 2 L 2 14 L 3 14 L 4 14 L 10 14 L 11 14 L 12 14 L 14 14 L 14 4.28125 L 11.71875 2 L 11.6875 2 L 11 2 L 4 2 L 3 2 L 2 2 z M 3 3 L 4 3 L 5 3 L 5 6 L 5 7 L 11 7 L 11 6 L 11 3 L 11.28125 3 L 13 4.71875 L 13 5 L 13 13 L 12 13 L 12 9 L 11 9 L 5 9 L 3.96875 9 L 3.96875 13 L 3 13 L 3 3 z M 6 3 L 7.90625 3 L 7.90625 6 L 6 6 L 6 3 z M 5 10 L 6 10 L 10 10 L 11 10 L 11 13 L 10 13 L 6 13 L 5 13 L 5 10 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 733 B

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 6 2 L 0.70703125 7.2929688 L 0 8 L 0.70703125 8.7070312 L 6 14 L 15 14 L 16 14 L 16 2 L 15 2 L 6 2 z M 8.0019531 5 L 10.011719 7.0097656 L 12.021484 5 L 13.011719 5.9902344 L 11.001953 8 L 13.011719 10.009766 L 12.021484 11 L 10.011719 8.9902344 L 8.0019531 11 L 7.0117188 10.009766 L 9.0214844 8 L 7.0117188 5.9902344 L 8.0019531 5 z "
class="ColorScheme-Text"
/>
</svg>

After

Width:  |  Height:  |  Size: 673 B

View file

@ -0,0 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-NegativeText {
color:#da4453;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
class="ColorScheme-NegativeText"
d="m5 2v2h1v-1h4v1h1v-2h-5zm-3 3v1h2v8h8v-8h2v-1zm3 1h6v7h-6z"
/>
</svg>

After

Width:  |  Height:  |  Size: 417 B

View file

@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
<path d="m2 2v12h4v-12zm8 0v12h4v-12z" class="ColorScheme-Text" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 290 B

580
icons/breeze/index.theme Normal file
View file

@ -0,0 +1,580 @@
[Icon Theme]
Name=Breeze
Name[ar]=نسيم
Name[ast]=Breeze
Name[ca]=Brisa
Name[ca@valencia]=Brisa
Name[cs]=Breeze
Name[da]=Breeze
Name[de]=Breeze
Name[el]=Breeze
Name[en_GB]=Breeze
Name[es]=Brisa
Name[et]=Breeze
Name[eu]=Breeze
Name[fi]=Breeze
Name[fr]=Breeze
Name[gd]=Oiteag
Name[gl]=Breeze
Name[hu]=Breeze
Name[ia]=Breeze
Name[id]=Breeze
Name[it]=Brezza
Name[ko]=Breeze
Name[lt]=Breeze
Name[nl]=Breeze
Name[nn]=Breeze
Name[pl]=Bryza
Name[pt]=Brisa
Name[pt_BR]=Breeze
Name[ru]=Breeze
Name[sk]=Vánok
Name[sl]=Sapica (Breeze)
Name[sr]=Поветарац
Name[sr@ijekavian]=Поветарац
Name[sr@ijekavianlatin]=Povetarac
Name[sr@latin]=Povetarac
Name[sv]=Breeze
Name[tg]=Насим
Name[uk]=Breeze
Name[x-test]=xxBreezexx
Name[zh_CN]=微风
Name[zh_TW]=Breeze
Comment=Breeze by the KDE VDG
Comment[ast]=Breeze pol VDG de KDE
Comment[ca]=Brisa, creat pel VDG del KDE
Comment[ca@valencia]=Brisa pel VDG del KDE
Comment[cs]=Breeze od KDE VDG
Comment[da]=Breeze af KDE VDG
Comment[de]=Breeze von der KDE VDG
Comment[en_GB]=Breeze by the KDE VDG
Comment[es]=Brisa por KDE VDG
Comment[et]=Breeze KDE VDG-lt
Comment[eu]=Breeze, KDE VDGk egina
Comment[fi]=Breeze KDE VDG:ltä
Comment[fr]=Breeze, par KDE VDG
Comment[gl]=Breeze de KDE VDG
Comment[hu]=Breeze a KDE VDG-től
Comment[ia]=Breeze (Brisa) per le KDE VDG
Comment[id]=Breeze oleh KDE VDG
Comment[it]=Brezza del KDE VDG
Comment[ko]=KDE 시각 디자인 그룹에서 제작한 Breeze
Comment[lt]=Breeze pagal KDE VDG
Comment[nl]=Breeze door de KDE VDG
Comment[nn]=Breeze frå KDE VDG
Comment[pl]=Bryza autorstwa KDE VDG
Comment[pt]=Brisa da VDG do KDE
Comment[pt_BR]=Breeze pelo KDE VDG
Comment[ru]=Breeze от KDE VDG
Comment[sk]=Vánok od KDE VDG
Comment[sl]=Breeze od KDE VDG
Comment[sv]=Breeze av KDE:s visuella designgrupp
Comment[tg]=Насим аз KDE VDG
Comment[uk]=Breeze, автори — KDE VDG
Comment[x-test]=xxBreeze by the KDE VDGxx
Comment[zh_CN]=微风,由 KDE VDG 创作
Comment[zh_TW]=由 KDE VDG 團隊製作的 Breeze
DisplayDepth=32
Inherits=hicolor
Example=folder
FollowsColorScheme=true
DesktopDefault=48
DesktopSizes=16,22,32,48,64,128,256
ToolbarDefault=22
ToolbarSizes=16,22,32,48
MainToolbarDefault=22
MainToolbarSizes=16,22,32,48
SmallDefault=16
SmallSizes=16,22,32,48
PanelDefault=48
PanelSizes=16,22,32,48,64,128,256
DialogDefault=32
DialogSizes=16,22,32,48,64,128,256
KDE-Extensions=.svg
########## Directories
########## ordered by category and alphabetically
Directories=actions/12,actions/16,actions/22,actions/24,actions/32,actions/64,animations/16,animations/22,apps/16,apps/22,apps/32,apps/48,preferences/32,applets/22,applets/48,applets/64,applets/128,applets/256,categories/32,devices/16,devices/22,devices/64,emblems/8,emblems/16,emblems/22,emotes/22,mimetypes/16,mimetypes/22,mimetypes/32,mimetypes/64,places/16,places/22,places/32,places/64,status/16,status/22,status/24,status/32,status/64,actions/symbolic,devices/symbolic,emblems/symbolic,places/symbolic,status/symbolic
ScaledDirectories=actions/16@2x,actions/22@2x,actions/24@2x,actions/32@2x,animations/16@2x,apps/16@2x,apps/22@2x,devices/16@2x,devices/22@2x,emblems/16@2x,emblems/22@2x,emotes/22@2x,mimetypes/16@2x,mimetypes/22@2x,places/16@2x,places/22@2x,status/16@2x,status/22@2x
########## Actions
########## ordered by size
#12x12 - Fixed size - For Inkscape
[actions/12]
Size=12
Context=Actions
Type=Fixed
#16x16 - Fixed size - For use in sidebar(s) smaller toolbar(s) >!!!ONLY!!!<: e.g. Kate movable sidebar/toolbar (search and replace, current project, etc.) or Juk tree view - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/16]
Size=16
Context=Actions
Type=Fixed
#16x16@2x - Fixed size - For use in sidebar(s) smaller toolbar(s) >!!!ONLY!!!<: e.g. Kate movable sidebar/toolbar (search and replace, current project, etc.) or Juk tree view - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/16@2x]
Size=16
Scale=2
Context=Actions
Type=Fixed
#22x22 - Fixed size - For toolbar icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/22]
Size=22
Context=Actions
Type=Fixed
#22x22@2x - Fixed size - For toolbar icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/22@2x]
Size=22
Scale=2
Context=Actions
Type=Fixed
#24x24 - Fixed size - GTK icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/24]
Size=24
Context=Actions
Type=Fixed
#24x24@2x - Fixed size - GTK icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/24@2x]
Size=24
Scale=2
Context=Actions
Type=Fixed
#32x32 - Fixed size - For toolbar icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/32]
Size=32
Context=Actions
Type=Scalable
MinSize=32
MaxSize=256
#32x32@2x - Fixed size - For toolbar icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/32@2x]
Size=32
Scale=2
Context=Actions
Type=Scalable
MinSize=32
MaxSize=256
#64x64 - Fixed size - For toolbar icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[actions/64]
Size=64
Context=Actions
Type=Scalable
MinSize=32
MaxSize=256
########## Animations
########## ordered by size
#16x16 - Fixed size - Application icon(s) for Dolphin sidebar - OPTIONAL + DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[animations/16]
Size=16
Context=Animations
Type=Fixed
#16x16@2x - Fixed size - Application icon(s) for Dolphin sidebar - OPTIONAL + DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[animations/16@2x]
Size=16
Scale=2
Context=Animations
Type=Fixed
#22x22 - Scalable
[animations/22]
Size=22
Context=Animations
Type=Scalable
MinSize=22
MaxSize=256
########## Apps
########## ordered by size
#16x16 - Fixed size - Application icon(s) for Dolphin sidebar - OPTIONAL + DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[apps/16]
Size=16
Context=Applications
Type=Fixed
#16x16@2x - Fixed size - Application icon(s) for Dolphin sidebar - OPTIONAL + DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[apps/16@2x]
Size=16
Scale=2
Context=Applications
Type=Fixed
#22x22 - Fixed size - Workaround icon(s) for toolbar(s) button(s) e.g. Dolphin Open Terminal/About Dolphin/About KDE buttons - WRONG_ICON_USAGE_BY_APP - Monochrome
[apps/22]
Size=22
Context=Applications
Type=Fixed
#22x22@2x - Fixed size - Workaround icon(s) for toolbar(s) button(s) e.g. Dolphin Open Terminal/About Dolphin/About KDE buttons - WRONG_ICON_USAGE_BY_APP - Monochrome
[apps/22@2x]
Size=22
Scale=2
Context=Applications
Type=Fixed
#32x32 - Fixed size - For System Settings icons >!!!ONLY!!!< - Scalable to the following sizes: 32x32 (default), 64x64, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[apps/32]
Size=32
Context=Applications
Type=Fixed
#48x48 - Scalable - For application icons >!!!ONLY!!!< - Scalable to the following sizes: 48x48 (default), 96x96 and 24x24 (not recommended) - DO_NOT_USE_ANYWHERE_ELSE - Color
[apps/48]
Size=48
Context=Applications
Type=Scalable
MinSize=48
MaxSize=256
#32x32 - Fixed size - For System Settings icons >!!!ONLY!!!< - Scalable to the following sizes: 32x32 (default), 64x64, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[preferences/32]
Size=32
Context=Applications
Type=Scalable
MinSize=32
MaxSize=256
#256x256 - Color for applets
[applets/22]
Size=22
Context=Status
Type=Scalable
MinSize=22
MaxSize=256
#256x256 - Color for applets
[applets/48]
Size=48
Context=Status
Type=Scalable
MinSize=32
MaxSize=256
#256x256 - Animation icons for kwin desktop effects
[applets/64]
Size=64
Context=Status
Type=Scalable
MinSize=32
MaxSize=256
#256x256 - Color
[applets/128]
Size=128
Context=Applications
Type=Scalable
MinSize=32
MaxSize=256
#256x256 - Scalable - For applets / widgets icons >!!!ONLY!!! - DO_NOT_USE_ANYWHERE_ELSE - Color
[applets/256]
Size=256
Context=Applications
Type=Scalable
MinSize=48
MaxSize=256
########## Categories
########## ordered by size
#32x32 - Fixed size - For categories icons >!!!ONLY!!!< - Used in Kickoff (KDE 4.x.x) and Lancelot. Also used in MATE and Cinnamon (just FYI) - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[categories/32]
Size=32
Context=Categories
Type=Scalable
MinSize=32
MaxSize=256
########## Devices
########## ordered by size
#16x16 - Fixed size - For small device icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[devices/16]
Size=16
Context=Devices
Type=Fixed
#16x16@2x - Fixed size - For small device icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[devices/16@2x]
Size=16
Scale=2
Context=Devices
Type=Fixed
#22x22 - Fixed size - For small device icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[devices/22]
Size=22
Context=Devices
Type=Fixed
#22x22@2x - Fixed size - For small device icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[devices/22@2x]
Size=22
Scale=2
Context=Devices
Type=Fixed
#64x64 - Scalable - For device icons >!!!ONLY!!!< - Scalable to the following sizes: 64x64 (default), 32x32, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[devices/64]
Size=64
Context=Devices
Type=Scalable
MinSize=24
MaxSize=256
########## Emblems
########## ordered by size
#8x8 - Fixed size - File system emblems - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[emblems/8]
Size=8
Context=Emblems
Type=Fixed
#16x16 - Fixed size - File system emblems - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[emblems/16]
Size=16
Context=Emblems
Type=Fixed
#16x16@2x - Fixed size - File system emblems - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[emblems/16@2x]
Size=16
Scale=2
Context=Emblems
Type=Fixed
#22x22 - Fixed size - File system emblems - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[emblems/22]
Size=22
Context=Emblems
Type=Fixed
#22x22@2x - Fixed size - File system emblems - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[emblems/22@2x]
Size=22
Scale=2
Context=Emblems
Type=Fixed
########## Emoticons
########## ordered by size
#22x22 - Fixed size - Emoticons - DO_NOT_USE_ANYWHERE_ELSE - Color/flat
[emotes/22]
Size=22
Context=Emotes
Type=Fixed
#22x22@2x - Fixed size - Emoticons - DO_NOT_USE_ANYWHERE_ELSE - Color/flat
[emotes/22@2x]
Size=22
Scale=2
Context=Emotes
Type=Fixed
########## Mimetypes
########## ordered by size
#16x16 - Fixed size - For small file type icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[mimetypes/16]
Size=16
Context=MimeTypes
Type=Fixed
MinSize=16
#16x16@2x - Fixed size - For small file type icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[mimetypes/16@2x]
Size=16
Scale=2
Context=MimeTypes
Type=Fixed
MinSize=16
#22x22 - Fixed size - For small file type icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[mimetypes/22]
Size=22
Context=MimeTypes
Type=Scalable
MinSize=22
MaxSize=24
#22x22@2x - Fixed size - For small file type icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[mimetypes/22@2x]
Size=22
Scale=2
Context=MimeTypes
Type=Scalable
MinSize=22
MaxSize=24
#32x32 - Scalable - For file type icons >!!!ONLY!!!< - Scalable to the following sizes: 64x64 (default), 32x32, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[mimetypes/32]
Size=32
Context=MimeTypes
Type=Scalable
MinSize=32
MaxSize=48
#64x64 - Scalable - For file type icons >!!!ONLY!!!< - Scalable to the following sizes: 64x64 (default), 32x32, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[mimetypes/64]
Size=64
Context=MimeTypes
Type=Scalable
MinSize=64
MaxSize=256
########## Places
########## ordered by size
#16x16 - Fixed size - For small folder icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[places/16]
Size=16
Context=Places
Type=Fixed
MinSize=16
#16x16@2x - Fixed size - For small folder icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[places/16@2x]
Size=16
Scale=2
Context=Places
Type=Fixed
MinSize=16
#22x22 - Fixed size - Workaround icon(s) for toolbar(s) button(s) e.g. KMail trash icon - WRONG_ICON_USAGE_BY_APP - Monochrome
[places/22]
Size=22
Context=Places
Type=Fixed
#22x22@2x - Fixed size - Workaround icon(s) for toolbar(s) button(s) e.g. KMail trash icon - WRONG_ICON_USAGE_BY_APP - Monochrome
[places/22@2x]
Size=22
Scale=2
Context=Places
Type=Fixed
#32x32 - Scalable - For folder icons >!!!ONLY!!!< - Scalable to the following sizes: 64x64 (default), 32x32, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[places/32]
Size=32
Context=Places
Type=Scalable
MinSize=24
MaxSize=48
#64x64 - Scalable - For folder icons >!!!ONLY!!!< - Scalable to the following sizes: 64x64 (default), 32x32, 128x128, 256x256 - DO_NOT_USE_ANYWHERE_ELSE - Color
[places/64]
Size=64
Context=Places
Type=Scalable
MinSize=32
MaxSize=256
########## Status
########## ordered by size
#16x16 - Fixed size - For IM status icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[status/16]
Size=16
Context=Status
Type=Fixed
#16x16@2x - Fixed size - For IM status icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[status/16@2x]
Size=16
Scale=2
Context=Status
Type=Fixed
#22x22 - Fixed size - Icon(s) for Plasma theme/System Tray. Not particularly used on Plasma. - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[status/22]
Size=22
Context=Status
Type=Fixed
#22x22@2x - Fixed size - Icon(s) for Plasma theme/System Tray. Not particularly used on Plasma. - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[status/22@2x]
Size=22
Scale=2
Context=Status
Type=Fixed
#24x24 - Fixed size - for GTK apps. - WRONG_ICON_USAGE_BY_APP - Monochrome
[status/24]
Size=24
Context=Status
Type=Fixed
#32x32 - Fixed size - Icon(s) for Plasma theme/System Tray. Not particularly used on Plasma. - DO_NOT_USE_ANYWHERE_ELSE - Monochrome
[status/32]
Size=32
Context=Status
Type=Fixed
#64x64 - Fixed size - For dialog icons >!!!ONLY!!!< - DO_NOT_USE_ANYWHERE_ELSE - Color
[status/64]
Size=64
Context=Status
Type=Scalable
MinSize=22
MaxSize=256
# Gnome symbolic icons
[actions/symbolic]
Context=Actions
Size=16
MinSize=8
MaxSize=512
Type=Scalable
[devices/symbolic]
Context=Devices
Size=16
MinSize=8
MaxSize=512
Type=Scalable
[emblems/symbolic]
Context=Emblems
Size=16
MinSize=8
MaxSize=512
Type=Scalable
[places/symbolic]
Context=Places
Size=16
MinSize=8
MaxSize=512
Type=Scalable
[status/symbolic]
Context=Status
Size=16
MinSize=8
MaxSize=512
Type=Scalable
########## EOF

View file

@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="opacity:1;fill:currentColor;fill-opacity:1;stroke:none"
d="M 8 2 L 4 6 L 4 10 L 8 14 L 9 14 L 9 2 L 8 2 z M 12.064453 3 L 11.306641 3.4804688 C 12.423224 4.8284501 13.01704 6.3963674 13.017578 7.9980469 C 13.013278 9.5983362 12.416776 11.164113 11.298828 12.509766 L 12.068359 13 C 13.32703 11.513919 13.998369 9.7754967 14 7.9980469 C 13.996227 6.2216198 13.323562 4.4846369 12.064453 3 z M 10.767578 4.4609375 L 10 4.9472656 C 10.698954 5.8743941 11.06889 6.9282736 11.072266 8.0019531 C 11.069966 9.0761572 10.700653 10.130745 10.001953 11.058594 L 10.769531 11.544922 C 11.607241 10.476929 12.05168 9.2516769 12.054688 8.0019531 C 12.050587 6.7527495 11.60553 5.5282095 10.767578 4.4609375 z M 2 6 L 2 10 L 3 10 L 3 6 L 2 6 z "
class="ColorScheme-Text"/>
</svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 8,2 4,6 4,10 8,14 9,14 9,2 8,2 Z M 2,6 2,10 3,10 3,6 2,6 Z"
class="ColorScheme-Text"/>
<path
style="fill:currentColor;fill-opacity:0.25;stroke:none"
d="M 12.064453 3 L 11.306641 3.4804688 C 12.423224 4.8284502 13.01704 6.3963674 13.017578 7.9980469 C 13.013278 9.5983362 12.416776 11.164113 11.298828 12.509766 L 12.068359 13 C 13.32703 11.513919 13.998369 9.7754967 14 7.9980469 C 13.996227 6.2216198 13.323562 4.4846369 12.064453 3 z M 10.767578 4.4609375 L 10 4.9472656 C 10.698954 5.8743941 11.06889 6.9282736 11.072266 8.0019531 C 11.069966 9.0761572 10.700653 10.130745 10.001953 11.058594 L 10.769531 11.544922 C 11.607241 10.476929 12.051681 9.2516769 12.054688 8.0019531 C 12.050588 6.7527495 11.60553 5.5282095 10.767578 4.4609375 z "
class="ColorScheme-Text"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,17 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<defs id="defs3051">
<style type="text/css" id="current-color-scheme">
.ColorScheme-Text {
color:#232629;
}
</style>
</defs>
<path
style="fill:currentColor;fill-opacity:1;stroke:none"
d="M 8 2 L 4 6 L 4 10 L 8 14 L 9 14 L 9 2 L 8 2 z M 10.767578 4.4609375 L 10 4.9472656 C 10.698954 5.8743941 11.06889 6.9282736 11.072266 8.0019531 C 11.069966 9.0761572 10.700653 10.130745 10.001953 11.058594 L 10.769531 11.544922 C 11.607241 10.476929 12.051681 9.2516769 12.054688 8.0019531 C 12.050588 6.7527495 11.60553 5.5282095 10.767578 4.4609375 z M 2 6 L 2 10 L 3 10 L 3 6 L 2 6 z "
class="ColorScheme-Text"/>
<path
style="fill:currentColor;fill-opacity:0.25;stroke:none"
d="m 12.064625,3 -0.757812,0.4804688 c 1.116583,1.3479814 1.710399,2.9158986 1.710937,4.5175781 -0.0043,1.6002893 -0.600802,3.1660661 -1.71875,4.5117191 L 12.068531,13 C 13.327202,11.513919 13.998541,9.7754967 14.000172,7.9980469 13.996399,6.2216198 13.323734,4.4846369 12.064625,3 Z"
class="ColorScheme-Text"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,5 @@
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<style id="current-color-scheme" type="text/css">.ColorScheme-Text{color:#232629;}.ColorScheme-NegativeText{color:#da4453;}</style>
<path class="ColorScheme-Text" d="m8 2-4 4v4l4 4h1v-12zm-6 4v4h1v-4z" fill="currentColor"/>
<path class="ColorScheme-NegativeText" d="m15.98633 8.0000197a3.0000002 3.0000002 0 0 1-0.55664 1.736328l0.0019 0.0019-0.01172 0.01176a3.0000002 3.0000002 0 0 1-0.677727 0.6777123l-0.01758 0.01758-0.0039-0.0038a3.0000002 3.0000002 0 0 1-1.734376 0.55852 3.0000002 3.0000002 0 0 1-2.9999993-3.0000003 3.0000002 3.0000002 0 0 1 0.5566403-1.736328l-0.0019-0.0019 0.01164-0.01164a3.0000002 3.0000002 0 0 1 0.677758-0.677914l0.01758-0.01758 0.0039 0.0038a3.0000002 3.0000002 0 0 1 1.734424-0.558478 3.0000002 3.0000002 0 0 1 3 3zm-1 0a2.0000002 2.0000002 0 0 0-2-1.999999 2.0000002 2.0000002 0 0 0-1.013672 0.279295l2.734376 2.734375a2.0000002 2.0000002 0 0 0 0.279296-1.013671zm-0.986328 1.720703-2.734375-2.734374a2.0000002 2.0000002 0 0 0-0.279297 1.013671 2.0000002 2.0000002 0 0 0 2 1.9999993 2.0000002 2.0000002 0 0 0 1.013672-0.2792963z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

37
io.h Normal file
View file

@ -0,0 +1,37 @@
// SPDX-License-Identifier: Unlicense
#ifndef IO_H
#define IO_H
#include <QFile>
#include <QObject>
#include <QString>
#include <QUrl>
#include <QtDebug>
class IO : public QObject {
Q_OBJECT
public slots:
void write(const QUrl &url, const QString &data) {
QFile file{urlToPath(url)};
if (file.open(QIODevice::WriteOnly | QIODevice::Text))
file.write(data.toUtf8());
else
qWarning() << "error opening file" << url;
}
QString read(const QUrl &url) {
QFile file{urlToPath(url)};
if (file.open(QIODevice::ReadOnly))
return file.readAll();
qWarning() << "error opening file" << url;
return {};
}
private:
static const QString urlToPath(const QUrl &path) {
return path.scheme() == "qrc" ? (":" + path.path()) : path.toLocalFile();
}
};
#endif

54
main.cpp Normal file
View file

@ -0,0 +1,54 @@
// SPDX-License-Identifier: Unlicense
#include <exception>
#include <QApplication>
#include <QCommandLineParser>
#include <QIcon>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQmlEngine>
#include <QTextCodec>
#include <QTextStream>
#include <QTranslator>
#include <QtDebug>
#include <io.h>
int main(int argc, char *argv[])
try {
if (QIcon::themeName().isEmpty()) {
QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << ":/icons");;
QIcon::setThemeName("breeze");
}
QApplication app{argc, argv};
app.setOrganizationName("fuzbal");
app.setApplicationName("fuzbal");
app.setApplicationVersion(GIT_VERSION);
QTranslator translator;
translator.load(QLocale(), "fuzbal", "_", ":/i18n");
app.installTranslator(&translator);
QCommandLineParser parser;
parser.setApplicationDescription("Friendly Usable Zero-Bullshit Annotator & Labeler");
parser.addHelpOption();
parser.addVersionOption();
parser.process(app);
IO io;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("io", &io);
engine.load(QUrl{"qrc:/main.qml"});
return app.exec();
} catch (std::exception &e) {
qCritical() << "critical error:" << e.what();
return 1;
} catch (...) {
qCritical() << "critical error";
return 1;
}

40
main.qml Normal file
View file

@ -0,0 +1,40 @@
// SPDX-License-Identifier: Unlicense
import QtQuick 2.12
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.6
ApplicationWindow {
visible: true
width: 1024
height: 768
SplitView {
anchors.fill: parent
hoverEnabled: true
handle: Rectangle {
implicitWidth: sidebar.rightPadding
implicitHeight: implicitWidth
color: SplitHandle.pressed ? palette.dark :
(SplitHandle.hovered ? palette.mid : 'transparent')
}
Video {
id: video
SplitView.fillWidth: true
SplitView.minimumWidth: parent.width/2
}
Sidebar {
id: sidebar
video: video
padding: 5
leftPadding: 0
focus: true
SplitView.fillHeight: true
SplitView.preferredWidth: 300
SplitView.minimumWidth: 200
}
}
}

20
main.qrc Normal file
View file

@ -0,0 +1,20 @@
<!-- SPDX-License-Identifier: Unlicense -->
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>qtquickcontrols2.conf</file>
<file>tags.json</file>
<file>util.js</file>
<file>Event.qml</file>
<file>Events.qml</file>
<file>Fields/Bool.qml</file>
<file>Fields/Enum.qml</file>
<file>Fields/Text.qml</file>
<file>Fields/TextArea.qml</file>
<file>Filter.qml</file>
<file>Tags.qml</file>
<file>Sidebar.qml</file>
<file>Video.qml</file>
<file>Volume.qml</file>
</qresource>
</RCC>

2
qtquickcontrols2.conf Normal file
View file

@ -0,0 +1,2 @@
[Controls]
Style=Fusion

56
tags.json Normal file
View file

@ -0,0 +1,56 @@
[
{
"tag": "pass", "key": "p", "fields": [
{ "name": "player", "type": "Text", "key": "p" },
{
"name": "type", "type": "Enum",
"values": [
{ "name": "long", "key": "l" },
{ "name": "short", "key": "s" }
]
},
{ "name": "success", "type": "Bool", "key": "u", "value": true },
{ "name": "comment", "type": "TextArea", "key": "c" }
]
},
{
"tag": "shot", "key": "s", "fields": [
{ "name": "player", "type": "Text", "key": "p" },
{
"name": "outcome", "type": "Enum",
"values": [
{ "name": "goal", "key": "g" },
{ "name": "block", "key": "b" },
{ "name": "deflect", "key": "d" },
{ "name": "out", "key": "o" }
]
},
{ "name": "comment", "type": "TextArea", "key": "c" }
]
},
{
"tag": "foul", "key": "f", "fields": [
{ "name": "player", "type": "Text", "key": "p" },
{ "name": "opponent", "type": "Text", "key": "o" },
{ "name": "comment", "type": "TextArea", "key": "c" }
]
},
{
"tag": "offside", "key": "o", "fields": [
{ "name": "player", "type": "Text", "key": "p" },
{ "name": "comment", "type": "TextArea", "key": "c" }
]
},
{
"tag": "assist", "key": "a", "fields": [
{ "name": "player", "type": "Text", "key": "p" },
{ "name": "comment", "type": "TextArea", "key": "c" }
]
},
{
"tag": "offense", "key": "O", "fields": []
},
{
"tag": "defense", "key": "D", "fields": []
}
]

85
translations/fuzbal_sl.ts Normal file
View file

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="sl_SI">
<context>
<name>Filter</name>
<message>
<location filename="../Filter.qml" line="20"/>
<source>Tag</source>
<translation>Oznaka</translation>
</message>
<message>
<location filename="../Filter.qml" line="29"/>
<source>Filters are not implemented yet! 😊</source>
<translation>Filtriranje še ne deluje! 😊</translation>
</message>
</context>
<context>
<name>Sidebar</name>
<message>
<location filename="../Sidebar.qml" line="43"/>
<source>Open video</source>
<translation>Odpri video</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="55"/>
<source>Load tags</source>
<translation>Naloži oznake</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="56"/>
<source>JSON files (*.json)</source>
<translation>Datoteke JSON (*.json)</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="56"/>
<source>All files (*)</source>
<translation>Vse datoteke (*)</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="101"/>
<source>Description</source>
<translation>Opis</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="104"/>
<source></source>
<translation></translation>
</message>
<message>
<location filename="../Sidebar.qml" line="104"/>
<source>+</source>
<translation></translation>
</message>
<message>
<location filename="../Sidebar.qml" line="136"/>
<source>Events</source>
<translation>Dogodki</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="188"/>
<source>Tags</source>
<translation>Oznake</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="228"/>
<source>&amp;Annotate</source>
<translation>&amp;Označi</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="228"/>
<source>Alt+A</source>
<translation>Alt+O</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="229"/>
<source>&amp;Filter</source>
<translation>&amp;Filtriraj</translation>
</message>
<message>
<location filename="../Sidebar.qml" line="229"/>
<source>Alt+F</source>
<translation></translation>
</message>
</context>
</TS>

30
util.js Normal file
View file

@ -0,0 +1,30 @@
// SPDX-License-Identifier: Unlicense
// If text contains key, make it stand out; otherwise, append [key] to text.
function addShortcut(text, key) {
if (!key)
return text
else if (text.indexOf(key) < 0)
return `${text} [<b>${key}</b>]`
else
return text.replace(new RegExp('\(' + key + '\)'), '<b>$1</b>')
}
// Set alpha value for color.
function alphize(color, alpha) {
return Qt.hsla(color.hslHue, color.hslSaturation, color.hslLightness, alpha)
}
// Return the last event in list with property not greater than value.
function find(list, property, value) {
var low = 0
var high = list.count - 1
while (low <= high) {
var mid = Math.floor((low + high) / 2)
if (list.get(mid)[property] <= value)
low = mid + 1
else
high = mid - 1
}
return low
}