mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-10-28 14:07:47 -04:00
feat(icc): show the profile details in Display Config
The profile row only showed the description, so there was no way to tell which profile a display actually has applied (whitepoint, curve type, file provenance) without leaving the settings page. - `ICCStatus` carries the descriptive metadata: class, tone-curve kind (plus gamma or table size), vcgt channels/entries, white point chromaticity with a derived CCT and standard illuminant name, file size and mtime. - `internal/icc` gains `WhitePointXY`, `WhitePointCCT` (McCamy), `WhitePointName` (D50/D65) and `TRCKind`, all covered by tests. - Display Config's Color Profile row gets an info button that opens `ICCProfileInfoModal` with those fields and the profile path, alongside the existing browse/remove actions. - `dms icc info` reports the white point as name/xy/CCT instead of raw XYZ.
This commit is contained in:
1 parent
033b96b38c
commit
4064dec333
9 files changed
+518
-8
No files matched your search
@@ -241,8 +241,21 @@ func runICCInfo(cmd *cobra.Command, args []string) {
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
fmt.Printf("White Point: %.4f %.4f %.4f\n",
|
||||
profile.WhitePoint[0], profile.WhitePoint[1], profile.WhitePoint[2])
|
||||
if x, y, ok := profile.WhitePointXY(); ok {
|
||||
cct := profile.WhitePointCCT()
|
||||
name := profile.WhitePointName()
|
||||
switch {
|
||||
case name != "" && cct > 0:
|
||||
fmt.Printf("White Point: %s (%.4f, %.4f, %dK)\n", name, x, y, cct)
|
||||
case cct > 0:
|
||||
fmt.Printf("White Point: (%.4f, %.4f, %dK)\n", x, y, cct)
|
||||
default:
|
||||
fmt.Printf("White Point: (%.4f, %.4f)\n", x, y)
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("White Point: %.4f %.4f %.4f\n",
|
||||
profile.WhitePoint[0], profile.WhitePoint[1], profile.WhitePoint[2])
|
||||
}
|
||||
}
|
||||
|
||||
func runICCApply(cmd *cobra.Command, args []string) {
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
package icc
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestWhitePointCCT(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
wp [3]float64
|
||||
wantCCT int
|
||||
wantName string
|
||||
}{
|
||||
{"D65", [3]float64{0.9505, 1.0, 1.0890}, 6504, "D65"},
|
||||
{"D50", [3]float64{0.9642, 1.0, 0.8249}, 5003, "D50"},
|
||||
{"missing white point", [3]float64{}, 0, ""},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
p := &Profile{WhitePoint: tc.wp}
|
||||
|
||||
cct := p.WhitePointCCT()
|
||||
if tc.wantCCT == 0 {
|
||||
if cct != 0 {
|
||||
t.Fatalf("WhitePointCCT() = %d, want 0", cct)
|
||||
}
|
||||
} else if math.Abs(float64(cct-tc.wantCCT)) > 150 {
|
||||
t.Fatalf("WhitePointCCT() = %d, want ~%d", cct, tc.wantCCT)
|
||||
}
|
||||
|
||||
if got := p.WhitePointName(); got != tc.wantName {
|
||||
t.Fatalf("WhitePointName() = %q, want %q", got, tc.wantName)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTRCKind(t *testing.T) {
|
||||
identity := Curve{Type: CurveIdentity}
|
||||
gamma22 := Curve{Type: CurveParametric, Gamma: 2.2}
|
||||
gamma18 := Curve{Type: CurveParametric, Gamma: 1.8}
|
||||
table := Curve{Type: CurveTable, Entries: make([]uint16, 1024)}
|
||||
tableShort := Curve{Type: CurveTable, Entries: make([]uint16, 256)}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
profile Profile
|
||||
wantKind string
|
||||
wantGamma float64
|
||||
wantEntries int
|
||||
}{
|
||||
{"identity", Profile{HasTRC: true, TRC: [3]Curve{identity, identity, identity}}, "identity", 0, 0},
|
||||
{"gamma", Profile{HasTRC: true, TRC: [3]Curve{gamma22, gamma22, gamma22}}, "gamma", 2.2, 0},
|
||||
{"table", Profile{HasTRC: true, TRC: [3]Curve{table, table, table}}, "table", 0, 1024},
|
||||
{"mixed types", Profile{HasTRC: true, TRC: [3]Curve{gamma22, table, gamma22}}, "mixed", 0, 0},
|
||||
{"per-channel gamma", Profile{HasTRC: true, TRC: [3]Curve{gamma22, gamma18, gamma22}}, "mixed", 0, 0},
|
||||
{"per-channel table size", Profile{HasTRC: true, TRC: [3]Curve{table, tableShort, table}}, "mixed", 0, 0},
|
||||
{"no TRC", Profile{}, "", 0, 0},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
kind, gamma, entries := tc.profile.TRCKind()
|
||||
if kind != tc.wantKind || gamma != tc.wantGamma || entries != tc.wantEntries {
|
||||
t.Fatalf("TRCKind() = (%q, %v, %d), want (%q, %v, %d)",
|
||||
kind, gamma, entries, tc.wantKind, tc.wantGamma, tc.wantEntries)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -618,3 +618,93 @@ func uint16Clamp(v float64) uint16 {
|
||||
}
|
||||
return uint16(v + 0.5) // round to nearest
|
||||
}
|
||||
|
||||
// WhitePointXY returns the chromaticity coordinates of the profile white point.
|
||||
func (p *Profile) WhitePointXY() (float64, float64, bool) {
|
||||
sum := p.WhitePoint[0] + p.WhitePoint[1] + p.WhitePoint[2]
|
||||
if sum <= 0 {
|
||||
return 0, 0, false
|
||||
}
|
||||
return p.WhitePoint[0] / sum, p.WhitePoint[1] / sum, true
|
||||
}
|
||||
|
||||
// WhitePointCCT estimates the correlated color temperature of the profile white
|
||||
// point in kelvin using McCamy's approximation. It returns 0 when the white
|
||||
// point is missing or the estimate is out of range.
|
||||
func (p *Profile) WhitePointCCT() int {
|
||||
x, y, ok := p.WhitePointXY()
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
denominator := 0.1858 - y
|
||||
if math.Abs(denominator) < 1e-9 {
|
||||
return 0
|
||||
}
|
||||
n := (x - 0.3320) / denominator
|
||||
cct := 449.0*n*n*n + 3525.0*n*n + 6823.3*n + 5520.33
|
||||
if cct < 1000 || cct > 40000 {
|
||||
return 0
|
||||
}
|
||||
return int(math.Round(cct))
|
||||
}
|
||||
|
||||
// WhitePointName names the white point when it is close to a standard
|
||||
// illuminant ("D50", "D65"), or returns an empty string.
|
||||
func (p *Profile) WhitePointName() string {
|
||||
cct := p.WhitePointCCT()
|
||||
switch {
|
||||
case cct == 0:
|
||||
return ""
|
||||
case math.Abs(float64(cct)-6504) <= 150:
|
||||
return "D65"
|
||||
case math.Abs(float64(cct)-5003) <= 150:
|
||||
return "D50"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
// TRCKind describes how the tone reproduction curves are stored: "identity",
|
||||
// "gamma", "table" or "mixed". gamma and entries are only set when all three
|
||||
// channels agree.
|
||||
func (p *Profile) TRCKind() (kind string, gamma float64, entries int) {
|
||||
if !p.HasTRC {
|
||||
return "", 0, 0
|
||||
}
|
||||
|
||||
identity, parametric, tables := 0, 0, 0
|
||||
sameGamma := true
|
||||
sameEntries := true
|
||||
for i, curve := range p.TRC {
|
||||
switch curve.Type {
|
||||
case CurveIdentity:
|
||||
identity++
|
||||
case CurveParametric:
|
||||
parametric++
|
||||
case CurveTable:
|
||||
tables++
|
||||
}
|
||||
if i == 0 {
|
||||
gamma = curve.Gamma
|
||||
entries = len(curve.Entries)
|
||||
continue
|
||||
}
|
||||
if curve.Gamma != gamma {
|
||||
sameGamma = false
|
||||
}
|
||||
if len(curve.Entries) != entries {
|
||||
sameEntries = false
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case identity == len(p.TRC):
|
||||
return "identity", 0, 0
|
||||
case parametric == len(p.TRC) && sameGamma:
|
||||
return "gamma", gamma, 0
|
||||
case tables == len(p.TRC) && sameEntries:
|
||||
return "table", 0, entries
|
||||
default:
|
||||
return "mixed", 0, 0
|
||||
}
|
||||
}
|
||||
@@ -1553,11 +1553,27 @@ func (m *Manager) GetICCStatus() map[string]*ICCStatus {
|
||||
Path: out.iccPath,
|
||||
Active: out.iccProfile != nil,
|
||||
}
|
||||
if out.iccProfile != nil {
|
||||
status.Description = out.iccProfile.Description
|
||||
status.Version = out.iccProfile.Version
|
||||
status.ColorSpace = out.iccProfile.ColorSpace
|
||||
status.HasVCGT = out.iccProfile.HasVCGT
|
||||
if profile := out.iccProfile; profile != nil {
|
||||
status.Description = profile.Description
|
||||
status.Version = profile.Version
|
||||
status.ColorSpace = profile.ColorSpace
|
||||
status.Class = profile.Class
|
||||
status.HasVCGT = profile.HasVCGT
|
||||
status.TRCKind, status.TRCGamma, status.TRCEntries = profile.TRCKind()
|
||||
if profile.VCGT != nil {
|
||||
status.VCGTChannels = profile.VCGT.Channels
|
||||
status.VCGTEntries = profile.VCGT.Entries
|
||||
}
|
||||
if x, y, ok := profile.WhitePointXY(); ok {
|
||||
status.WhitePointX = x
|
||||
status.WhitePointY = y
|
||||
status.WhitePointCCT = profile.WhitePointCCT()
|
||||
status.WhitePointName = profile.WhitePointName()
|
||||
}
|
||||
}
|
||||
if info, err := os.Stat(out.iccPath); err == nil {
|
||||
status.Size = info.Size()
|
||||
status.Modified = info.ModTime().Unix()
|
||||
}
|
||||
result[name] = status
|
||||
return true
|
||||
|
||||
@@ -2,6 +2,7 @@ package wayland
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
@@ -9,6 +10,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/icc"
|
||||
mocks_wlclient "github.com/AvengeMedia/DankMaterialShell/core/internal/mocks/wlclient"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/proto/wlr_gamma_control"
|
||||
)
|
||||
@@ -628,3 +630,47 @@ func TestEffectiveTempTarget(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The status payload is what the settings UI shows for a profile, so the
|
||||
// descriptive metadata has to be carried through.
|
||||
func TestManager_GetICCStatusDescribesProfile(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
profilePath := filepath.Join(dir, "display.icm")
|
||||
if err := os.WriteFile(profilePath, []byte("stub"), 0o644); err != nil {
|
||||
t.Fatalf("write stub profile: %v", err)
|
||||
}
|
||||
|
||||
gamma := icc.Curve{Type: icc.CurveParametric, Gamma: 2.2}
|
||||
m := &Manager{}
|
||||
m.outputs.Store(1, &outputState{
|
||||
id: 1,
|
||||
iccPath: profilePath,
|
||||
iccProfile: &icc.Profile{
|
||||
Description: "Test Display",
|
||||
Version: "2.1.0",
|
||||
Class: "mntr",
|
||||
ColorSpace: "RGB",
|
||||
HasTRC: true,
|
||||
TRC: [3]icc.Curve{gamma, gamma, gamma},
|
||||
HasVCGT: true,
|
||||
VCGT: &icc.VCGT{Channels: 3, Entries: 1024},
|
||||
WhitePoint: [3]float64{0.9505, 1.0, 1.0890},
|
||||
},
|
||||
})
|
||||
m.outputNames.Store(1, "DP-2")
|
||||
|
||||
status := m.GetICCStatus()["DP-2"]
|
||||
if status == nil {
|
||||
t.Fatal("no status for DP-2")
|
||||
}
|
||||
|
||||
assert.True(t, status.Active)
|
||||
assert.Equal(t, "mntr", status.Class)
|
||||
assert.Equal(t, "gamma", status.TRCKind)
|
||||
assert.Equal(t, 2.2, status.TRCGamma)
|
||||
assert.Equal(t, 3, status.VCGTChannels)
|
||||
assert.Equal(t, 1024, status.VCGTEntries)
|
||||
assert.Equal(t, "D65", status.WhitePointName)
|
||||
assert.Equal(t, int64(4), status.Size)
|
||||
assert.NotZero(t, status.Modified)
|
||||
}
|
||||
@@ -69,6 +69,21 @@ type ICCStatus struct {
|
||||
ColorSpace string `json:"colorSpace"` // e.g., "RGB"
|
||||
HasVCGT bool `json:"hasVCGT"` // has video card gamma table
|
||||
Active bool `json:"active"` // currently applied
|
||||
|
||||
// Descriptive profile metadata, surfaced in the UI so a profile can be
|
||||
// identified without leaving the settings page.
|
||||
Class string `json:"class,omitempty"` // "mntr", "scnr", "prtr"
|
||||
TRCKind string `json:"trcKind,omitempty"` // "identity", "gamma", "table", "mixed"
|
||||
TRCGamma float64 `json:"trcGamma,omitempty"` // when TRCKind is "gamma"
|
||||
TRCEntries int `json:"trcEntries,omitempty"` // when TRCKind is "table"
|
||||
VCGTChannels int `json:"vcgtChannels,omitempty"` // channels in the video card gamma table
|
||||
VCGTEntries int `json:"vcgtEntries,omitempty"` // entries per channel
|
||||
WhitePointX float64 `json:"whitePointX,omitempty"` // chromaticity x
|
||||
WhitePointY float64 `json:"whitePointY,omitempty"` // chromaticity y
|
||||
WhitePointCCT int `json:"whitePointCCT,omitempty"` // correlated color temperature (K)
|
||||
WhitePointName string `json:"whitePointName,omitempty"` // "D50", "D65", ...
|
||||
Size int64 `json:"size,omitempty"` // profile file size in bytes
|
||||
Modified int64 `json:"modified,omitempty"` // profile file mtime, unix seconds
|
||||
}
|
||||
|
||||
type cmd struct {
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
import QtQuick
|
||||
import qs.Common
|
||||
import qs.Modals.Common
|
||||
import qs.Services
|
||||
import qs.Widgets
|
||||
|
||||
DankModal {
|
||||
id: root
|
||||
|
||||
layerNamespace: "dms:icc-profile-info"
|
||||
keepPopoutsOpen: true
|
||||
|
||||
property string outputName: ""
|
||||
readonly property var profile: outputName !== "" ? (ICCService.status[outputName] ?? null) : null
|
||||
|
||||
function showProfile(output) {
|
||||
outputName = output;
|
||||
open();
|
||||
}
|
||||
|
||||
function toneCurveText(p) {
|
||||
if (!p)
|
||||
return "";
|
||||
|
||||
const kind = p.trcKind;
|
||||
if (kind === "gamma")
|
||||
return I18n.tr("Gamma", "Gamma") + " " + (p.trcGamma ?? 0).toFixed(2);
|
||||
if (kind === "table")
|
||||
return I18n.tr("Table", "Tone curve stored as a lookup table") + " · " + p.trcEntries;
|
||||
if (kind === "identity")
|
||||
return I18n.tr("Identity", "Tone curve that leaves the signal unchanged");
|
||||
if (kind === "mixed")
|
||||
return I18n.tr("Mixed", "Tone curves that differ between channels");
|
||||
return "";
|
||||
}
|
||||
|
||||
function whitePointText(p) {
|
||||
if (!p)
|
||||
return "";
|
||||
if (p.whitePointName && p.whitePointCCT)
|
||||
return p.whitePointName + " · " + p.whitePointCCT + "K";
|
||||
if (p.whitePointCCT)
|
||||
return p.whitePointCCT + "K";
|
||||
if (p.whitePointX !== undefined)
|
||||
return p.whitePointX.toFixed(4) + ", " + p.whitePointY.toFixed(4);
|
||||
return "";
|
||||
}
|
||||
|
||||
function vcgtText(p) {
|
||||
if (!p)
|
||||
return "";
|
||||
return p.hasVCGT ? (p.vcgtChannels + " × " + p.vcgtEntries) : I18n.tr("No", "No");
|
||||
}
|
||||
|
||||
function sizeText(p) {
|
||||
if (!p || !p.size)
|
||||
return "";
|
||||
return p.size < 1024 ? p.size + " B" : (p.size / 1024).toFixed(1) + " KB";
|
||||
}
|
||||
|
||||
function modifiedText(p) {
|
||||
if (!p || !p.modified)
|
||||
return "";
|
||||
return Qt.formatDateTime(new Date(p.modified * 1000), "yyyy-MM-dd HH:mm");
|
||||
}
|
||||
|
||||
readonly property var details: {
|
||||
const p = profile;
|
||||
if (!p)
|
||||
return [];
|
||||
|
||||
return [{
|
||||
"label": I18n.tr("Version", "Version"),
|
||||
"value": p.version || ""
|
||||
}, {
|
||||
"label": I18n.tr("Class", "ICC profile class, e.g. monitor, scanner, printer"),
|
||||
"value": p.class || ""
|
||||
}, {
|
||||
"label": I18n.tr("Color space", "ICC profile color space"),
|
||||
"value": p.colorSpace || ""
|
||||
}, {
|
||||
"label": I18n.tr("Tone curve", "How the ICC profile stores its tone reproduction curves"),
|
||||
"value": toneCurveText(p)
|
||||
}, {
|
||||
"label": I18n.tr("Video card gamma table", "Video card gamma table (vcgt) carried by the profile"),
|
||||
"value": vcgtText(p)
|
||||
}, {
|
||||
"label": I18n.tr("White point", "White point the ICC profile was produced for"),
|
||||
"value": whitePointText(p)
|
||||
}, {
|
||||
"label": I18n.tr("Active", "Active"),
|
||||
"value": p.active ? I18n.tr("Yes", "Yes") : I18n.tr("No", "No")
|
||||
}, {
|
||||
"label": I18n.tr("Size", "Size"),
|
||||
"value": sizeText(p)
|
||||
}, {
|
||||
"label": I18n.tr("Modified", "Modified"),
|
||||
"value": modifiedText(p)
|
||||
}];
|
||||
}
|
||||
|
||||
modalWidth: 580
|
||||
modalHeight: 520
|
||||
onBackgroundClicked: close()
|
||||
onVisibleChanged: {
|
||||
if (!visible)
|
||||
outputName = "";
|
||||
}
|
||||
|
||||
content: Component {
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
|
||||
Column {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingL
|
||||
spacing: Theme.spacingM
|
||||
|
||||
Row {
|
||||
width: parent.width
|
||||
|
||||
Column {
|
||||
width: parent.width - 40
|
||||
spacing: Theme.spacingXS
|
||||
|
||||
StyledText {
|
||||
text: root.profile?.description || I18n.tr("Color Profile", "Display Config output card label for the per-monitor ICC profile row")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.surfaceText
|
||||
font.weight: Font.Medium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.outputName
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
}
|
||||
|
||||
DankActionButton {
|
||||
iconName: "close"
|
||||
iconSize: Theme.iconSize - 4
|
||||
iconColor: Theme.surfaceText
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
|
||||
StyledRect {
|
||||
width: parent.width
|
||||
height: parent.height - 120
|
||||
radius: Theme.cornerRadius
|
||||
color: Theme.surfaceHover
|
||||
border.color: Theme.outlineStrong
|
||||
border.width: 1
|
||||
clip: true
|
||||
|
||||
DankFlickable {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacingM
|
||||
contentHeight: detailColumn.implicitHeight
|
||||
|
||||
Column {
|
||||
id: detailColumn
|
||||
|
||||
width: parent.width
|
||||
spacing: Theme.spacingS
|
||||
|
||||
Repeater {
|
||||
model: root.details
|
||||
|
||||
delegate: Column {
|
||||
required property var modelData
|
||||
|
||||
width: detailColumn.width
|
||||
spacing: 2
|
||||
|
||||
StyledText {
|
||||
text: modelData.label
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: modelData.value !== "" ? modelData.value : "—"
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceText
|
||||
width: parent.width
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StyledText {
|
||||
visible: root.details.length === 0
|
||||
text: I18n.tr("No information available")
|
||||
font.pixelSize: Theme.fontSizeMedium
|
||||
color: Theme.surfaceTextMedium
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: I18n.tr("Path", "Path")
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceVariantText
|
||||
width: parent.width
|
||||
topPadding: Theme.spacingS
|
||||
}
|
||||
|
||||
StyledText {
|
||||
text: root.profile?.path || "—"
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
color: Theme.surfaceTextMedium
|
||||
width: parent.width
|
||||
elide: Text.ElideMiddle
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 40
|
||||
|
||||
DankButton {
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: I18n.tr("Close", "Close")
|
||||
onClicked: root.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ StyledRect {
|
||||
LayoutMirroring.childrenInherit: true
|
||||
|
||||
signal requestICCBrowse(string outputName)
|
||||
signal requestICCInfo(string outputName)
|
||||
|
||||
required property string outputName
|
||||
required property var outputData
|
||||
@@ -429,7 +430,7 @@ StyledRect {
|
||||
}
|
||||
|
||||
Column {
|
||||
width: parent.width - 18 - Theme.spacingS - iccBrowseButton.width - Theme.spacingS - (iccRemoveButton.visible ? iccRemoveButton.width + Theme.spacingS : 0)
|
||||
width: parent.width - 18 - Theme.spacingS - iccBrowseButton.width - Theme.spacingS - (iccInfoButton.visible ? iccInfoButton.width + Theme.spacingS : 0) - (iccRemoveButton.visible ? iccRemoveButton.width + Theme.spacingS : 0)
|
||||
spacing: 1
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
@@ -479,6 +480,19 @@ StyledRect {
|
||||
onClicked: root.requestICCBrowse(root.outputName)
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: iccInfoButton
|
||||
text: ""
|
||||
iconName: "info"
|
||||
buttonHeight: 30
|
||||
horizontalPadding: Theme.spacingXS
|
||||
backgroundColor: "transparent"
|
||||
textColor: Theme.surfaceText
|
||||
visible: iccProfileRow.iccInfo !== undefined
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onClicked: root.requestICCInfo(root.outputName)
|
||||
}
|
||||
|
||||
DankButton {
|
||||
id: iccRemoveButton
|
||||
text: ""
|
||||
|
||||
@@ -621,6 +621,7 @@ Item {
|
||||
pendingICCOutput = name;
|
||||
iccFileBrowser.open();
|
||||
}
|
||||
onRequestICCInfo: name => iccInfoModal.showProfile(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -655,6 +656,10 @@ Item {
|
||||
}
|
||||
}
|
||||
|
||||
ICCProfileInfoModal {
|
||||
id: iccInfoModal
|
||||
}
|
||||
|
||||
FileBrowserModal {
|
||||
id: iccFileBrowser
|
||||
parentModal: root.parentModal || null
|
||||
|
||||
Reference in new issue
Block a user