mirror of
https://github.com/gen2brain/cbconvert
synced 2025-10-14 10:38:51 +02:00
add GUI
This commit is contained in:
BIN
gui/assets/icon.ico
Normal file
BIN
gui/assets/icon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
BIN
gui/assets/icon.png
Normal file
BIN
gui/assets/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
965
gui/assets/main.qml
Normal file
965
gui/assets/main.qml
Normal file
@@ -0,0 +1,965 @@
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2
|
||||
import QtQuick.Layouts 1.2
|
||||
import QtQuick.Window 2.2
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
ApplicationWindow {
|
||||
id: applicationWindow
|
||||
visible: true
|
||||
width: 800
|
||||
height: 600
|
||||
title: "CBconvert"
|
||||
|
||||
property int margin: 15
|
||||
property int screenWidth: Screen.width
|
||||
property int screenHeight: Screen.height
|
||||
|
||||
function updateImage() {
|
||||
imagePreview.source = ""
|
||||
sizePreview.text = ""
|
||||
if(groupBoxPreview.checked) {
|
||||
if(c.len > 0) {
|
||||
imagePreview.source = "image://cover/" + c.get(listView.currentIndex).path
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: splitView1
|
||||
anchors.rightMargin: margin
|
||||
anchors.leftMargin: margin
|
||||
anchors.bottomMargin: margin
|
||||
anchors.topMargin: margin
|
||||
anchors.fill: parent
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout1
|
||||
anchors.bottom: rowLayout2.top
|
||||
anchors.right: parent.right
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.bottomMargin: 15
|
||||
Layout.minimumHeight: 250
|
||||
|
||||
ScrollView {
|
||||
id: scrollview
|
||||
anchors.right: columnButtons.left
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
anchors.rightMargin: margin
|
||||
frameVisible: true
|
||||
highlightOnFocus: false
|
||||
verticalScrollBarPolicy: 2
|
||||
flickableItem.interactive: true
|
||||
focus: true
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
anchors.topMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 1
|
||||
focus: true
|
||||
|
||||
model: c.len
|
||||
|
||||
header: Rectangle {
|
||||
height: 20
|
||||
width: ListView.view.width
|
||||
color: "#DCDCDC"
|
||||
|
||||
Text {
|
||||
id: nameHeader
|
||||
text: 'Name'
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.byName()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeHeader
|
||||
text: 'Type'
|
||||
width: 40
|
||||
anchors.right: sizeHeader.left
|
||||
anchors.rightMargin: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.byType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizeHeader
|
||||
text: 'Size'
|
||||
width: 40
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
c.bySize()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
delegate: Item {
|
||||
id: item1
|
||||
width: ListView.view.width
|
||||
height: 20
|
||||
|
||||
Text {
|
||||
id: nameItem
|
||||
text: c.get(index).name
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: parent.width - 170
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
id: typeItem
|
||||
width: 40
|
||||
text: c.get(index).type
|
||||
anchors.right: sizeItem.left
|
||||
anchors.rightMargin: 40
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizeItem
|
||||
width: 40
|
||||
text: c.get(index).sizeHuman
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 15
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
listView.currentIndex = index
|
||||
listView.forceActiveFocus()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
highlight: Rectangle {
|
||||
width: ListView.view ? ListView.view.width : undefined
|
||||
color: "#326686"
|
||||
opacity: 0.2
|
||||
y: listView.currentItem.y
|
||||
Behavior on y {
|
||||
SpringAnimation {
|
||||
spring: 3
|
||||
damping: 0.2
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onCurrentItemChanged: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtons
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtonsFiles
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonAddFile
|
||||
objectName: "buttonAddFile"
|
||||
text: "Add &Files"
|
||||
onClicked: {
|
||||
fileDialogFile.open()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonAddDir
|
||||
objectName: "buttonAddDir"
|
||||
text: "Add &Dir"
|
||||
onClicked: {
|
||||
fileDialogDir.open()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonRemove
|
||||
objectName: "buttonRemove"
|
||||
text: "Remove"
|
||||
enabled: (c.len !== 0) ? true : false
|
||||
onClicked: {
|
||||
c.remove(listView.currentIndex)
|
||||
updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonRemoveAll
|
||||
objectName: "buttonRemoveAll"
|
||||
text: "Remove All"
|
||||
enabled: (c.len !== 0) ? true : false
|
||||
onClicked: {
|
||||
c.removeAll()
|
||||
updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnButtonsActions
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonThumbnail
|
||||
objectName: "buttonThumbnail"
|
||||
text: "Thumbnail"
|
||||
anchors.bottom: buttonCover.top
|
||||
anchors.bottomMargin: 5
|
||||
tooltip: "Extract Thumbnail (freedesktop spec.)"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonCover
|
||||
objectName: "buttonCover"
|
||||
text: "Cover"
|
||||
anchors.bottom: buttonConvert.top
|
||||
anchors.bottomMargin: 15
|
||||
tooltip: "Extract Cover"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonConvert
|
||||
objectName: "buttonConvert"
|
||||
text: "&Convert"
|
||||
tooltip: "Convert archives and documents"
|
||||
enabled: (textFieldOutDir.text != "" && c.len !== 0) ? true : false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: rowLayout2
|
||||
spacing: 0
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.left: parent.left
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLeft
|
||||
anchors.right: columnMiddle.left
|
||||
anchors.rightMargin: margin
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
Layout.minimumWidth: 200
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxPreview
|
||||
checkable: true
|
||||
flat: true
|
||||
anchors.fill: parent
|
||||
title: "Preview"
|
||||
|
||||
Image {
|
||||
id: imagePreview
|
||||
anchors.fill: parent
|
||||
fillMode: Image.PreserveAspectFit
|
||||
asynchronous: true
|
||||
cache: false
|
||||
}
|
||||
|
||||
BusyIndicator {
|
||||
running: imagePreview.status === Image.Loading
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
onCheckedChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: sizePreview
|
||||
objectName: "sizePreview"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: groupBoxPreview.bottom
|
||||
anchors.topMargin: -5
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnMiddle
|
||||
anchors.right: columnRight.left
|
||||
anchors.rightMargin: margin
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
Layout.fillHeight: true
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxInput
|
||||
flat: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
title: "Input"
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnInput
|
||||
anchors.bottomMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxRecursive
|
||||
objectName: "checkBoxRecursive"
|
||||
text: "Recurse SubDirectories"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxNoRGB
|
||||
objectName: "checkBoxNoRGB"
|
||||
text: "Only Grayscaled Images"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxConvertCover
|
||||
objectName: "checkBoxConvertCover"
|
||||
text: "Exclude Cover"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxSize
|
||||
objectName: "spinboxSize"
|
||||
stepSize: 10
|
||||
prefix: ""
|
||||
maximumValue: 1000
|
||||
suffix: " MiB"
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Minimum Size"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxTransform
|
||||
flat: true
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
title: "Transform"
|
||||
|
||||
ColumnLayout {
|
||||
id: columnTransform
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Button {
|
||||
id: buttonLevels
|
||||
text: "Levels..."
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: {
|
||||
levelsDialog.open()
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Brightness: " + sliderBrightness.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderBrightness
|
||||
objectName: "sliderBrightness"
|
||||
value: 0
|
||||
stepSize: 1
|
||||
minimumValue: -100
|
||||
maximumValue: 100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Contrast: " + sliderContrast.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderContrast
|
||||
objectName: "sliderContrast"
|
||||
value: 0
|
||||
maximumValue: 100
|
||||
stepSize: 1
|
||||
minimumValue: -100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Flip:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFlip
|
||||
objectName: "comboBoxFlip"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "None"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Horizontal"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Vertical"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Rotate:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxRotate
|
||||
objectName: "comboBoxRotate"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "0"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "90"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "180"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "270"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: columnRight
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
Layout.fillHeight: true
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxOutput
|
||||
flat: true
|
||||
title: "Output"
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnOutput
|
||||
anchors.bottomMargin: 0
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
RowLayout {
|
||||
id: rowLayoutOutput
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 0
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
TextField {
|
||||
id: textFieldOutDir
|
||||
objectName: "textFieldOutDir"
|
||||
anchors.right: buttonBrowse.left
|
||||
anchors.rightMargin: 5
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
placeholderText: "Output Directory"
|
||||
|
||||
Settings {
|
||||
id: settingsOutDir
|
||||
property alias text: textFieldOutDir.text
|
||||
}
|
||||
}
|
||||
|
||||
Button {
|
||||
id: buttonBrowse
|
||||
text: "..."
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: {
|
||||
fileDialogOutput.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: textFieldSuffix
|
||||
objectName: "textFieldSuffix"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
placeholderText: "Add Suffix to Output File"
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxNonImage
|
||||
objectName: "checkBoxNonImage"
|
||||
text: "Remove Non-Image Files"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
GroupBox {
|
||||
id: groupBoxImage
|
||||
flat: true
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
title: "Image"
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnFormat
|
||||
anchors.fill: parent
|
||||
spacing: 5
|
||||
|
||||
Text {
|
||||
text: "Format:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFormat
|
||||
objectName: "comboBoxFormat"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "JPEG"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "PNG"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "GIF"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "BMP"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "TIFF"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Size:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 5
|
||||
|
||||
TextField {
|
||||
id: width
|
||||
objectName: "width"
|
||||
placeholderText: "width"
|
||||
maximumLength: 4
|
||||
implicitWidth: 50
|
||||
onAccepted: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
id: x
|
||||
text: "x"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: height
|
||||
objectName: "height"
|
||||
placeholderText: "height"
|
||||
maximumLength: 4
|
||||
implicitWidth: 50
|
||||
onAccepted: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxFit
|
||||
objectName: "checkBoxFit"
|
||||
text: "Best Fit"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: updateImage()
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Resize Algorithm:"
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: comboBoxFilter
|
||||
objectName: "comboBoxFilter"
|
||||
currentIndex: 2
|
||||
enabled: (c.len > 0) ? true : false
|
||||
|
||||
model: ListModel {
|
||||
ListElement {
|
||||
text: "NearestNeighbor"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Box"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Linear"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "MitchellNetravali"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "CatmullRom"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Gaussian"
|
||||
}
|
||||
|
||||
ListElement {
|
||||
text: "Lanczos"
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: updateImage()
|
||||
|
||||
Settings {
|
||||
id: settingsFilter
|
||||
property alias currentIndex: comboBoxFilter.currentIndex
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Quality: " + sliderQuality.value
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: sliderQuality
|
||||
objectName: "sliderQuality"
|
||||
stepSize: 1
|
||||
value: 75
|
||||
maximumValue: 100
|
||||
activeFocusOnPress: true
|
||||
updateValueWhileDragging: false
|
||||
enabled: (c.len > 0 && comboBoxFormat.currentText == "JPEG") ? true : false
|
||||
onValueChanged: updateImage()
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBoxGrayscale
|
||||
objectName: "checkBoxGrayscale"
|
||||
text: "Convert to Grayscale"
|
||||
enabled: (c.len > 0) ? true : false
|
||||
onClicked: updateImage()
|
||||
}
|
||||
|
||||
Settings {
|
||||
id: settingsQuality
|
||||
property alias value: sliderQuality.value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogFile
|
||||
modality: Qt.WindowModal
|
||||
title: "Add Files"
|
||||
selectFolder: false
|
||||
selectMultiple: true
|
||||
selectExisting: true
|
||||
sidebarVisible: true
|
||||
nameFilters: [ "Comic files (*.rar *.zip *.7z *.gz *.bz2 *.cbr *.cbz *.cb7 *.cbt *.pdf *.epub *.xps)" ]
|
||||
onAccepted: {
|
||||
c.addUrls(decodeURIComponent(fileUrls.join("_CBSEP_")))
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogDir
|
||||
modality: Qt.WindowModal
|
||||
title: "Add Directory"
|
||||
selectFolder: true
|
||||
sidebarVisible: true
|
||||
onAccepted: {
|
||||
c.addUrls(decodeURIComponent(fileUrl.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialogOutput
|
||||
modality: Qt.WindowModal
|
||||
title: "Output Directory"
|
||||
selectFolder: true
|
||||
sidebarVisible: true
|
||||
onAccepted: {
|
||||
textFieldOutDir.text = decodeURIComponent(fileUrl.toString().replace("file://", ""))
|
||||
}
|
||||
}
|
||||
|
||||
Dialog {
|
||||
id: levelsDialog
|
||||
objectName: "levelsDialog"
|
||||
title: "Levels"
|
||||
standardButtons: StandardButton.Close
|
||||
width: 230
|
||||
height: 150
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Text {
|
||||
text: "Input Levels:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsInMin
|
||||
objectName: "spinboxLevelsInMin"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 0
|
||||
onEditingFinished: updateImage()
|
||||
Keys.onReturnPressed: {
|
||||
event.accepted = true
|
||||
}
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsGamma
|
||||
objectName: "spinboxLevelsGamma"
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
decimals: 2
|
||||
stepSize: 0.01
|
||||
maximumValue: 10.00
|
||||
value: 1.00
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsInMax
|
||||
objectName: "spinboxLevelsInMax"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 255
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
text: "Output Levels:"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
spacing: 5
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsOutMin
|
||||
objectName: "spinboxLevelsOutMin"
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 0
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinboxLevelsOutMax
|
||||
objectName: "spinboxLevelsOutMax"
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
stepSize: 1
|
||||
maximumValue: 255
|
||||
value: 255
|
||||
onEditingFinished: updateImage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statusBar: StatusBar {
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
|
||||
Label {
|
||||
id: labelStatus
|
||||
objectName: "labelStatus"
|
||||
text: "Ready"
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: rectangle1
|
||||
Layout.fillWidth: true
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
Label {
|
||||
id: labelPercent
|
||||
objectName: "labelPercent"
|
||||
font.pointSize: 9
|
||||
anchors.right: progressBar.left
|
||||
anchors.rightMargin: 5
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
id: progressBar
|
||||
objectName: "progressBar"
|
||||
visible: false
|
||||
value: 0.0
|
||||
minimumValue : 0.0
|
||||
maximumValue : 100.0
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: -10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: -10
|
||||
anchors.right: labelProgress.left
|
||||
anchors.rightMargin: 5
|
||||
}
|
||||
|
||||
Label {
|
||||
id: labelProgress
|
||||
objectName: "labelProgress"
|
||||
font.pointSize: 9
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 0
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
549
gui/main.go
Normal file
549
gui/main.go
Normal file
@@ -0,0 +1,549 @@
|
||||
// Author: Milan Nikolic <gen2brain@gmail.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package main
|
||||
|
||||
//go:generate genqrc assets
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/gif"
|
||||
"image/jpeg"
|
||||
"image/png"
|
||||
"mime"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/dustin/go-humanize"
|
||||
"github.com/gen2brain/cbconvert"
|
||||
"github.com/gographics/imagick/imagick"
|
||||
"github.com/hotei/bmp"
|
||||
"golang.org/x/image/tiff"
|
||||
"gopkg.in/qml.v1"
|
||||
)
|
||||
|
||||
// Model
|
||||
type Comics struct {
|
||||
Root qml.Object
|
||||
Conv *cbconvert.Convertor
|
||||
List []Comic
|
||||
Len int
|
||||
}
|
||||
|
||||
// Comic Element
|
||||
type Comic struct {
|
||||
Name string
|
||||
Path string
|
||||
Type string
|
||||
Size int64
|
||||
SizeHuman string
|
||||
}
|
||||
|
||||
// Sorts by name
|
||||
type ByName []Comic
|
||||
|
||||
func (c ByName) Len() int { return len(c) }
|
||||
func (c ByName) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByName) Less(i, j int) bool { return c[i].Name < c[j].Name }
|
||||
|
||||
// Sorts by size
|
||||
type BySize []Comic
|
||||
|
||||
func (c BySize) Len() int { return len(c) }
|
||||
func (c BySize) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c BySize) Less(i, j int) bool { return c[i].Size < c[j].Size }
|
||||
|
||||
// Sorts by type
|
||||
type ByType []Comic
|
||||
|
||||
func (c ByType) Len() int { return len(c) }
|
||||
func (c ByType) Swap(i, j int) { c[i], c[j] = c[j], c[i] }
|
||||
func (c ByType) Less(i, j int) bool { return c[i].Type < c[j].Type }
|
||||
|
||||
// Adds element to list
|
||||
func (c *Comics) Add(comic Comic) {
|
||||
c.List = append(c.List, comic)
|
||||
c.Len = len(c.List)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Removes element from list
|
||||
func (c *Comics) Remove(i int) {
|
||||
l := c.List
|
||||
l = append(l[:i], l[i+1:]...)
|
||||
c.List = l
|
||||
c.Len = len(c.List)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Removes all elements from list
|
||||
func (c *Comics) RemoveAll() {
|
||||
c.Len = 0
|
||||
c.List = make([]Comic, 0)
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by name
|
||||
func (c *Comics) ByName() {
|
||||
sort.Sort(ByName(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by size
|
||||
func (c *Comics) BySize() {
|
||||
sort.Sort(BySize(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Sorts by type
|
||||
func (c *Comics) ByType() {
|
||||
sort.Sort(ByType(c.List))
|
||||
c.Len++
|
||||
qml.Changed(c, &c.Len)
|
||||
c.Len--
|
||||
qml.Changed(c, &c.Len)
|
||||
}
|
||||
|
||||
// Returns element for given index
|
||||
func (c *Comics) Get(i int) Comic {
|
||||
return c.List[i]
|
||||
}
|
||||
|
||||
// Adds elements from fileUrls to list
|
||||
func (c *Comics) AddUrls(u string) {
|
||||
var args []string
|
||||
l := strings.Split(u, "_CBSEP_")
|
||||
re := regexp.MustCompile(`^[a-zA-Z]:`)
|
||||
|
||||
for _, f := range l {
|
||||
f = strings.Replace(f, "file://", "", -1)
|
||||
f = re.ReplaceAllString(f, "")
|
||||
f = re.ReplaceAllString(f, "")
|
||||
args = append(args, f)
|
||||
}
|
||||
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
files := c.Conv.GetFiles(args)
|
||||
|
||||
for _, file := range files {
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat AddUrls: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
|
||||
m := mime.TypeByExtension(filepath.Ext(file))
|
||||
if m == "" && stat.IsDir() {
|
||||
m = "inode/directory"
|
||||
}
|
||||
|
||||
c.Add(Comic{
|
||||
filepath.Base(file),
|
||||
file,
|
||||
m,
|
||||
stat.Size(),
|
||||
humanize.IBytes(uint64(stat.Size())),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Returns cbconvert options from qml
|
||||
func (c *Comics) GetOptions() cbconvert.Options {
|
||||
var o cbconvert.Options
|
||||
o.Quiet = true
|
||||
|
||||
r := c.Root.ObjectByName("checkBoxRecursive")
|
||||
o.Recursive = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxNoRGB")
|
||||
o.RGB = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxConvertCover")
|
||||
o.ConvertCover = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxSize")
|
||||
o.Size = r.Int64("value")
|
||||
|
||||
r = c.Root.ObjectByName("sliderBrightness")
|
||||
o.Brightness = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("sliderContrast")
|
||||
o.Contrast = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxGrayscale")
|
||||
o.Grayscale = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFlip")
|
||||
o.Flip = strings.ToLower(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxRotate")
|
||||
o.Rotate, _ = strconv.Atoi(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("textFieldOutDir")
|
||||
o.Outdir = r.String("text")
|
||||
|
||||
r = c.Root.ObjectByName("textFieldSuffix")
|
||||
o.Suffix = r.String("text")
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxNonImage")
|
||||
o.NonImage = !r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFormat")
|
||||
o.Format = strings.ToLower(r.String("currentText"))
|
||||
|
||||
r = c.Root.ObjectByName("width")
|
||||
o.Width, _ = strconv.Atoi(r.String("text"))
|
||||
|
||||
r = c.Root.ObjectByName("height")
|
||||
o.Height, _ = strconv.Atoi(r.String("text"))
|
||||
|
||||
r = c.Root.ObjectByName("checkBoxFit")
|
||||
o.Fit = r.Bool("checked")
|
||||
|
||||
r = c.Root.ObjectByName("comboBoxFilter")
|
||||
o.Filter = r.Int("currentIndex")
|
||||
|
||||
r = c.Root.ObjectByName("sliderQuality")
|
||||
o.Quality = int(r.Float64("value"))
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsInMin")
|
||||
o.LevelsInMin = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsInMax")
|
||||
o.LevelsInMax = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsGamma")
|
||||
o.LevelsGamma = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsOutMin")
|
||||
o.LevelsOutMin = r.Float64("value")
|
||||
|
||||
r = c.Root.ObjectByName("spinboxLevelsOutMax")
|
||||
o.LevelsOutMax = r.Float64("value")
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// Sets "enabled" property
|
||||
func (c *Comics) SetEnabled(b bool) {
|
||||
c.Root.ObjectByName("checkBoxRecursive").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxNoRGB").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxConvertCover").Set("enabled", b)
|
||||
c.Root.ObjectByName("spinboxSize").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderBrightness").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderContrast").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxGrayscale").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFlip").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxRotate").Set("enabled", b)
|
||||
c.Root.ObjectByName("textFieldOutDir").Set("enabled", b)
|
||||
c.Root.ObjectByName("textFieldSuffix").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxNonImage").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFormat").Set("enabled", b)
|
||||
c.Root.ObjectByName("width").Set("enabled", b)
|
||||
c.Root.ObjectByName("height").Set("enabled", b)
|
||||
c.Root.ObjectByName("checkBoxFit").Set("enabled", b)
|
||||
c.Root.ObjectByName("comboBoxFilter").Set("enabled", b)
|
||||
c.Root.ObjectByName("sliderQuality").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonAddFile").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonAddDir").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonRemove").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonRemoveAll").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonThumbnail").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonCover").Set("enabled", b)
|
||||
c.Root.ObjectByName("buttonConvert").Set("enabled", b)
|
||||
}
|
||||
|
||||
// Converts comic
|
||||
func (c *Comics) Convert() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Convert: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ConvertComic(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(true, "Converting...")
|
||||
}
|
||||
|
||||
// Extracts cover
|
||||
func (c *Comics) Cover() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Cover: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ExtractCover(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(false, "Extracting...")
|
||||
}
|
||||
|
||||
// Extracts thumbnail
|
||||
func (c *Comics) Thumbnail() {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
c.Conv.Nfiles = c.Len
|
||||
c.Conv.CurrFile = 0
|
||||
|
||||
c.SetEnabled(false)
|
||||
|
||||
go func() {
|
||||
for _, e := range c.List {
|
||||
stat, err := os.Stat(e.Path)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat Thumbnail: %v\n", err.Error())
|
||||
continue
|
||||
}
|
||||
c.Conv.ExtractThumbnail(e.Path, stat)
|
||||
}
|
||||
}()
|
||||
|
||||
go c.showProgress(false, "Extracting...")
|
||||
}
|
||||
|
||||
// Shows progress
|
||||
func (c *Comics) showProgress(cn bool, text string) {
|
||||
c.Root.ObjectByName("labelStatus").Set("text", text)
|
||||
c.Root.ObjectByName("progressBar").Set("visible", true)
|
||||
|
||||
for {
|
||||
if c.Conv.CurrFile == c.Conv.Nfiles {
|
||||
if c.Conv.CurrContent == c.Conv.Ncontents {
|
||||
c.Root.ObjectByName("progressBar").Set("value", 0)
|
||||
c.Root.ObjectByName("labelProgress").Set("text", "")
|
||||
c.Root.ObjectByName("labelStatus").Set("text", "Ready")
|
||||
c.Root.ObjectByName("labelPercent").Set("text", "")
|
||||
c.Root.ObjectByName("progressBar").Set("visible", false)
|
||||
c.SetEnabled(true)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var count, current int
|
||||
if cn {
|
||||
count = c.Conv.Ncontents
|
||||
current = c.Conv.CurrContent
|
||||
} else {
|
||||
count = c.Conv.Nfiles
|
||||
current = c.Conv.CurrFile
|
||||
}
|
||||
|
||||
value := float64(current) / float64(count) * 100
|
||||
c.Root.ObjectByName("progressBar").Set("value", float64(value))
|
||||
c.Root.ObjectByName("labelPercent").Set("text",
|
||||
fmt.Sprintf("%d/%d %.0f%%", current, count, float64(value)))
|
||||
c.Root.ObjectByName("labelProgress").Set("text",
|
||||
fmt.Sprintf("File %d of %d", c.Conv.CurrFile, c.Conv.Nfiles))
|
||||
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
|
||||
// Provides image://cover/
|
||||
func (c *Comics) CoverProvider(file string, width int, height int) image.Image {
|
||||
c.Conv.Opts = c.GetOptions()
|
||||
|
||||
stat, err := os.Stat(file)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error Stat CoverProvider: %v\n", err.Error())
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
cover, err := c.Conv.GetCoverImage(file, stat)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error GetCoverImage: %v\n", err.Error())
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
cover = c.Conv.TransformImage(cover)
|
||||
|
||||
if c.Conv.Opts.LevelsInMin != 0 || c.Conv.Opts.LevelsInMax != 255 || c.Conv.Opts.LevelsGamma != 1.00 ||
|
||||
c.Conv.Opts.LevelsOutMin != 0 || c.Conv.Opts.LevelsOutMax != 255 {
|
||||
cover = c.Conv.LevelImage(cover)
|
||||
}
|
||||
|
||||
// imaging is used for preview only
|
||||
if c.Conv.Opts.Grayscale {
|
||||
cover = imaging.Grayscale(cover)
|
||||
}
|
||||
|
||||
// size preview
|
||||
s := 0
|
||||
b := new(bytes.Buffer)
|
||||
|
||||
w := 0
|
||||
h := 0
|
||||
|
||||
switch c.Conv.Opts.Format {
|
||||
case "jpeg":
|
||||
jpeg.Encode(b, cover, &jpeg.Options{c.Conv.Opts.Quality})
|
||||
s = len(b.Bytes())
|
||||
cover, _ = jpeg.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "png":
|
||||
png.Encode(b, cover)
|
||||
s = len(b.Bytes())
|
||||
cover, _ = png.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "gif":
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
mw.ReadImageBlob(c.Conv.GetImageBytes(cover))
|
||||
mw.SetImageFormat("GIF")
|
||||
blob := mw.GetImageBlob()
|
||||
|
||||
s = len(blob)
|
||||
cover, _ = gif.Decode(bytes.NewReader(blob))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(blob))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "tiff":
|
||||
tiff.Encode(b, cover, &tiff.Options{tiff.Uncompressed, false})
|
||||
|
||||
var buf bytes.Buffer
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write(b.Bytes())
|
||||
gz.Close()
|
||||
|
||||
s = buf.Len()
|
||||
cover, _ = tiff.Decode(bytes.NewReader(b.Bytes()))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(b.Bytes()))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
case "bmp":
|
||||
mw := imagick.NewMagickWand()
|
||||
defer mw.Destroy()
|
||||
|
||||
bb := c.Conv.GetImageBytes(cover)
|
||||
mw.ReadImageBlob(bb)
|
||||
|
||||
wand := imagick.NewPixelWand()
|
||||
wand.SetColor("black")
|
||||
defer wand.Destroy()
|
||||
|
||||
mw.SetImageFormat("BMP3")
|
||||
mw.SetImageBackgroundColor(wand)
|
||||
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_REMOVE)
|
||||
mw.SetImageAlphaChannel(imagick.ALPHA_CHANNEL_DEACTIVATE)
|
||||
mw.SetImageMatte(false)
|
||||
mw.SetImageCompression(imagick.COMPRESSION_NO)
|
||||
mw.QuantizeImage(16, mw.GetImageColorspace(), 8, true, true)
|
||||
|
||||
var buf bytes.Buffer
|
||||
blob := mw.GetImageBlob()
|
||||
gz := gzip.NewWriter(&buf)
|
||||
gz.Write(blob)
|
||||
gz.Close()
|
||||
|
||||
s = buf.Len()
|
||||
cover, _ = bmp.Decode(bytes.NewReader(blob))
|
||||
config, _, _ := image.DecodeConfig(bytes.NewReader(bb))
|
||||
w = config.Width
|
||||
h = config.Height
|
||||
}
|
||||
|
||||
if cover == nil {
|
||||
return image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
}
|
||||
|
||||
human := humanize.IBytes(uint64(s))
|
||||
c.Root.ObjectByName("sizePreview").Set("text", fmt.Sprintf("%s (%dx%d)", human, w, h))
|
||||
|
||||
return cover
|
||||
}
|
||||
|
||||
func run() error {
|
||||
qml.SetWindowIcon(":///assets/icon.png")
|
||||
|
||||
engine := qml.NewEngine()
|
||||
|
||||
c := &Comics{}
|
||||
engine.Context().SetVar("c", c)
|
||||
|
||||
engine.AddImportPath("qrc:/assets")
|
||||
engine.AddImageProvider("cover", c.CoverProvider)
|
||||
|
||||
q, err := engine.LoadFile("qrc:///assets/main.qml")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
window := q.CreateWindow(nil)
|
||||
c.Root = window.Root()
|
||||
c.Conv = cbconvert.NewConvertor(c.GetOptions())
|
||||
|
||||
c.Root.On("closing", func(o qml.Object) {
|
||||
os.RemoveAll(c.Conv.Workdir)
|
||||
})
|
||||
|
||||
c.Root.ObjectByName("buttonConvert").On("clicked", c.Convert)
|
||||
c.Root.ObjectByName("buttonCover").On("clicked", c.Cover)
|
||||
c.Root.ObjectByName("buttonThumbnail").On("clicked", c.Thumbnail)
|
||||
|
||||
// center window
|
||||
x := c.Root.Int("screenWidth")/2 - c.Root.Int("width")/2
|
||||
y := c.Root.Int("screenHeight")/2 - c.Root.Int("height")/2
|
||||
window.Set("x", x)
|
||||
window.Set("y", y)
|
||||
|
||||
window.Show()
|
||||
window.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
if err := qml.Run(run); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
32
gui/make.bash
Executable file
32
gui/make.bash
Executable file
@@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
CHROOT="/home/milann/chroot"
|
||||
MINGW="/usr/i686-w64-mingw32"
|
||||
|
||||
mkdir -p build
|
||||
rm -f resource.syso
|
||||
|
||||
go generate
|
||||
|
||||
LIBRARY_PATH="$CHROOT/usr/lib:$CHROOT/lib" \
|
||||
PKG_CONFIG_PATH="$CHROOT/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$CHROOT/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$CHROOT/usr/lib -L$CHROOT/lib -L$CHROOT/usr/plugins/generic -L$CHROOT/usr/plugins/platforms -L$CHROOT/usr/plugins/qmltooling -L$CHROOT/usr/qml/QtQuick.2 -L$CHROOT/usr/qml/QtQuick/Controls -L$CHROOT/usr/qml/QtQuick/Dialogs -L$CHROOT/usr/qml/QtQuick/Layouts -L$CHROOT/usr/qml/QtQuick/Window.2 -L$CHROOT/usr/qml/Qt/labs/settings -L$CHROOT/usr/qml/QtQuick/PrivateWidgets -L$CHROOT/usr/plugins/xcbglintegrations" \
|
||||
CGO_LDFLAGS="$CGO_LDFLAGS -lqxcb -lqtquick2plugin -lqtquickcontrolsplugin -ldialogplugin -lqquicklayoutsplugin -lwindowplugin -lqmlsettingsplugin -lwidgetsplugin -lqxcb-glx-integration -lqevdevkeyboardplugin -lqevdevmouseplugin" \
|
||||
CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -v -x -o build/cbconvert
|
||||
strip build/cbconvert
|
||||
|
||||
goversioninfo -icon=assets/icon.ico
|
||||
|
||||
PKG_CONFIG="/usr/bin/i686-w64-mingw32-pkg-config" \
|
||||
PKG_CONFIG_PATH="$MINGW/usr/lib/pkgconfig:$MINGW/usr/lib/pkgconfig" \
|
||||
PKG_CONFIG_LIBDIR="$MINGW/usr/lib/pkgconfig:$MINGW/usr/lib/pkgconfig" \
|
||||
CGO_LDFLAGS="-L$MINGW/usr/lib -L$MINGW/lib -L$MINGW/usr/plugins/generic -L$MINGW/usr/plugins/platforms -L$MINGW/usr/qml/Qt/labs/folderlistmodel -L$MINGW/usr/plugins/qmltooling -L$MINGW/usr/qml/QtQuick.2 -L$MINGW/usr/qml/QtQuick/Controls -L$MINGW/usr/qml/QtQuick/Dialogs -L$MINGW/usr/qml/QtQuick/Dialogs/Private -L$MINGW/usr/qml/QtQuick/Layouts -L$MINGW/usr/qml/QtQuick/Window.2 -L$MINGW/usr/qml/Qt/labs/settings -L$MINGW/usr/qml/QtQuick/PrivateWidgets" \
|
||||
CGO_LDFLAGS="$CGO_LDFLAGS -lqwindows -lqtquick2plugin -lqtquickcontrolsplugin -lqmlfolderlistmodelplugin -ldialogplugin -ldialogsprivateplugin -lqquicklayoutsplugin -lwindowplugin -lqmlsettingsplugin -lwidgetsplugin" \
|
||||
CGO_CFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CXXFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CGO_CPPFLAGS="-I$MINGW/usr/include -Wno-poison-system-directories" \
|
||||
CC="i686-w64-mingw32-gcc" CXX="i686-w64-mingw32-g++" \
|
||||
CC_FOR_TARGET="i686-w64-mingw32-gcc" CXX_FOR_TARGET="i686-w64-mingw32-g++" \
|
||||
CGO_ENABLED=1 GOOS=windows GOARCH=386 go build -v -x -o build/cbconvert.exe -ldflags "-H=windowsgui -linkmode external '-extldflags=-static -Wl,--allow-multiple-definition'"
|
||||
i686-w64-mingw32-strip build/cbconvert.exe
|
44
gui/versioninfo.json
Normal file
44
gui/versioninfo.json
Normal file
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"FixedFileInfo":
|
||||
{
|
||||
"FileVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 5,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"ProductVersion": {
|
||||
"Major": 0,
|
||||
"Minor": 5,
|
||||
"Patch": 0,
|
||||
"Build": 0
|
||||
},
|
||||
"FileFlagsMask": "3f",
|
||||
"FileFlags ": "00",
|
||||
"FileOS": "040004",
|
||||
"FileType": "01",
|
||||
"FileSubType": "00"
|
||||
},
|
||||
"StringFileInfo":
|
||||
{
|
||||
"Comments": "Comic Book converter",
|
||||
"CompanyName": "",
|
||||
"FileDescription": "CBconvert GUI",
|
||||
"FileVersion": "0.5.0",
|
||||
"InternalName": "",
|
||||
"LegalCopyright": "",
|
||||
"LegalTrademarks": "",
|
||||
"OriginalFilename": "cbconvert.exe",
|
||||
"PrivateBuild": "",
|
||||
"ProductName": "CBconvert",
|
||||
"ProductVersion": "0.5.0",
|
||||
"SpecialBuild": ""
|
||||
},
|
||||
"VarFileInfo":
|
||||
{
|
||||
"Translation": {
|
||||
"LangID": "0409",
|
||||
"CharsetID": "04B0"
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user