First commit
There was history before but now there is no more.
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
build/
|
||||
*.qmlc
|
109
Event.qml
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
// Event‐specific 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
|
@ -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 don’t 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,31 @@
|
|||
# Fuzbal
|
||||
|
||||
Friendly Usable Zero‐Bullshit Analyzer & Labeler: a keyboard‐driven 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 production‐ready. 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 built‐in 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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 // don’t 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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.
|
13
icons/breeze/actions/symbolic/document-open.svg
Normal 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 |
13
icons/breeze/actions/symbolic/document-save.svg
Normal 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 |
13
icons/breeze/actions/symbolic/edit-clear.svg
Normal 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 |
14
icons/breeze/actions/symbolic/edit-delete.svg
Normal 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 |
8
icons/breeze/actions/symbolic/media-playback-pause.svg
Normal 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
|
@ -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
|
13
icons/breeze/status/symbolic/audio-volume-high.svg
Normal 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 |
17
icons/breeze/status/symbolic/audio-volume-low.svg
Normal 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 |
17
icons/breeze/status/symbolic/audio-volume-medium.svg
Normal 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 |
5
icons/breeze/status/symbolic/audio-volume-muted.svg
Normal 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
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
@ -0,0 +1,2 @@
|
|||
[Controls]
|
||||
Style=Fusion
|
56
tags.json
Normal 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
|
@ -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>&Annotate</source>
|
||||
<translation>&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>&Filter</source>
|
||||
<translation>&Filtriraj</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../Sidebar.qml" line="229"/>
|
||||
<source>Alt+F</source>
|
||||
<translation></translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
30
util.js
Normal 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
|
||||
}
|