Browse Source

Merge pull request #356 from djeada/copilot/ensure-lgpl-compliance

Add Qt LGPL v3 compliance documentation and notices
Adam Djellouli 1 tháng trước cách đây
mục cha
commit
a8d97240a8
4 tập tin đã thay đổi với 362 bổ sung187 xóa
  1. 1 0
      .github/workflows/windows.yml
  2. 8 0
      README.md
  3. 61 0
      THIRD_PARTY_LICENSES.md
  4. 292 187
      ui/qml/SettingsPanel.qml

+ 1 - 0
.github/workflows/windows.yml

@@ -122,6 +122,7 @@ jobs:
       - name: Build
         run: cmake --build build
 
+      # Deploy Qt DLLs (dynamic linking ensures LGPL v3 compliance)
       - name: Deploy Qt
         shell: pwsh
         run: |

+ 8 - 0
README.md

@@ -434,6 +434,14 @@ Quick start for contributors:
 
 MIT License - see LICENSE file for details.
 
+### Third-Party Software Licenses
+
+This game uses the **Qt framework** (https://www.qt.io), which is licensed under the **GNU Lesser General Public License v3 (LGPL v3)**.
+
+- Qt is dynamically linked in this application, allowing you to replace the Qt libraries with your own versions.
+- You may obtain a copy of the LGPL v3 license at https://www.gnu.org/licenses/lgpl-3.0.html
+- Qt source code is available at https://www.qt.io/download-open-source
+
 ## Acknowledgments
 
 Built with modern C++20, Qt 6, and OpenGL 3.3 Core. Special thanks to the open-source community for excellent documentation and tools.

+ 61 - 0
THIRD_PARTY_LICENSES.md

@@ -0,0 +1,61 @@
+# Third-Party Software Licenses
+
+This document lists the licenses of third-party software used in Standard of Iron.
+
+## Qt Framework
+
+**License:** GNU Lesser General Public License v3 (LGPL v3)  
+**Website:** https://www.qt.io  
+**License Text:** https://www.gnu.org/licenses/lgpl-3.0.html  
+**Source Code:** https://www.qt.io/download-open-source
+
+### LGPL v3 Compliance
+
+Standard of Iron complies with the LGPL v3 requirements for using Qt:
+
+1. **Dynamic Linking**: Qt is dynamically linked to the application, not statically linked.
+   - On Windows: Qt DLLs are deployed alongside the executable using `windeployqt`
+   - On Linux: Qt shared libraries (.so) are linked dynamically
+   - On macOS: Qt frameworks are linked dynamically
+
+2. **No Modifications**: We do not modify Qt source code. If modifications were made, they would be released under LGPL v3.
+
+3. **License Notice**: LGPL v3 attribution is provided in:
+   - This document (THIRD_PARTY_LICENSES.md)
+   - README.md (License section)
+   - In-game Settings panel (About section)
+
+4. **User Re-linking**: Users can replace the Qt libraries with their own versions because Qt is dynamically linked. This is automatic with dynamic linking - users simply replace the Qt DLLs/shared libraries in the application directory.
+
+### Qt Components Used
+
+- QtCore
+- QtGui
+- QtWidgets
+- QtOpenGL
+- QtQuick
+- QtQml
+- QtQuickControls2
+- QtSql
+- QtMultimedia
+
+### Verification
+
+To verify dynamic linking:
+
+**Windows:**
+```powershell
+dumpbin /DEPENDENTS standard_of_iron.exe | findstr Qt
+```
+
+**Linux:**
+```bash
+ldd standard_of_iron | grep Qt
+```
+
+**macOS:**
+```bash
+otool -L standard_of_iron | grep Qt
+```
+
+All commands should show Qt libraries as external dependencies, confirming dynamic linking.

+ 292 - 187
ui/qml/SettingsPanel.qml

@@ -30,7 +30,7 @@ Item {
         id: container
 
         width: Math.min(parent.width * 0.6, 700)
-        height: Math.min(parent.height * 0.7, 500)
+        height: Math.min(parent.height * 0.8, 600)
         anchors.centerIn: parent
         radius: Theme.radiusPanel
         color: Theme.panelBase
@@ -68,262 +68,367 @@ Item {
                 color: Theme.border
             }
 
-            ColumnLayout {
+            ScrollView {
                 Layout.fillWidth: true
-                spacing: Theme.spacingMedium
-
-                Label {
-                    text: qsTr("Audio Settings")
-                    color: Theme.textMain
-                    font.pointSize: Theme.fontSizeLarge
-                    font.bold: true
-                }
-
-                Rectangle {
-                    Layout.fillWidth: true
-                    Layout.preferredHeight: 2
-                    color: Theme.border
-                    opacity: 0.5
-                }
+                Layout.fillHeight: true
+                clip: true
+                ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
+                ScrollBar.vertical.policy: ScrollBar.AsNeeded
 
-                GridLayout {
-                    Layout.fillWidth: true
-                    columns: 2
-                    rowSpacing: Theme.spacingMedium
-                    columnSpacing: Theme.spacingMedium
-
-                    Label {
-                        text: qsTr("Master Volume:")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeMedium
-                    }
+                ColumnLayout {
+                    width: container.width - Theme.spacingXLarge * 2
+                    spacing: Theme.spacingLarge
 
-                    RowLayout {
+                    ColumnLayout {
                         Layout.fillWidth: true
-                        spacing: Theme.spacingSmall
+                        spacing: Theme.spacingMedium
 
-                        Slider {
-                            id: masterVolumeSlider
+                        Label {
+                            text: qsTr("Audio Settings")
+                            color: Theme.textMain
+                            font.pointSize: Theme.fontSizeLarge
+                            font.bold: true
+                        }
 
+                        Rectangle {
                             Layout.fillWidth: true
-                            from: 0
-                            to: 100
-                            value: 100
-                            stepSize: 1
-                            onValueChanged: {
-                                if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
-                                    gameEngine.audioSystem.setMasterVolume(value / 100);
+                            Layout.preferredHeight: 2
+                            color: Theme.border
+                            opacity: 0.5
+                        }
 
+                        GridLayout {
+                            Layout.fillWidth: true
+                            columns: 2
+                            rowSpacing: Theme.spacingMedium
+                            columnSpacing: Theme.spacingMedium
+
+                            Label {
+                                text: qsTr("Master Volume:")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeMedium
                             }
-                        }
 
-                        Label {
-                            text: Math.round(masterVolumeSlider.value) + "%"
-                            color: Theme.textSub
-                            font.pointSize: Theme.fontSizeMedium
-                            Layout.minimumWidth: 45
-                        }
+                            RowLayout {
+                                Layout.fillWidth: true
+                                spacing: Theme.spacingSmall
 
-                    }
+                                Slider {
+                                    id: masterVolumeSlider
 
-                    Label {
-                        text: qsTr("Music Volume:")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeMedium
-                    }
+                                    Layout.fillWidth: true
+                                    from: 0
+                                    to: 100
+                                    value: 100
+                                    stepSize: 1
+                                    onValueChanged: {
+                                        if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
+                                            gameEngine.audioSystem.setMasterVolume(value / 100);
 
-                    RowLayout {
-                        Layout.fillWidth: true
-                        spacing: Theme.spacingSmall
+                                    }
+                                }
 
-                        Slider {
-                            id: musicVolumeSlider
+                                Label {
+                                    text: Math.round(masterVolumeSlider.value) + "%"
+                                    color: Theme.textSub
+                                    font.pointSize: Theme.fontSizeMedium
+                                    Layout.minimumWidth: 45
+                                }
 
-                            Layout.fillWidth: true
-                            from: 0
-                            to: 100
-                            value: 100
-                            stepSize: 1
-                            onValueChanged: {
-                                if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
-                                    gameEngine.audioSystem.setMusicVolume(value / 100);
+                            }
 
+                            Label {
+                                text: qsTr("Music Volume:")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeMedium
                             }
-                        }
 
-                        Label {
-                            text: Math.round(musicVolumeSlider.value) + "%"
-                            color: Theme.textSub
-                            font.pointSize: Theme.fontSizeMedium
-                            Layout.minimumWidth: 45
-                        }
+                            RowLayout {
+                                Layout.fillWidth: true
+                                spacing: Theme.spacingSmall
 
-                    }
+                                Slider {
+                                    id: musicVolumeSlider
 
-                    Label {
-                        text: qsTr("SFX Volume:")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeMedium
-                    }
+                                    Layout.fillWidth: true
+                                    from: 0
+                                    to: 100
+                                    value: 100
+                                    stepSize: 1
+                                    onValueChanged: {
+                                        if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
+                                            gameEngine.audioSystem.setMusicVolume(value / 100);
 
-                    RowLayout {
-                        Layout.fillWidth: true
-                        spacing: Theme.spacingSmall
+                                    }
+                                }
 
-                        Slider {
-                            id: sfxVolumeSlider
+                                Label {
+                                    text: Math.round(musicVolumeSlider.value) + "%"
+                                    color: Theme.textSub
+                                    font.pointSize: Theme.fontSizeMedium
+                                    Layout.minimumWidth: 45
+                                }
 
-                            Layout.fillWidth: true
-                            from: 0
-                            to: 100
-                            value: 100
-                            stepSize: 1
-                            onValueChanged: {
-                                if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
-                                    gameEngine.audioSystem.setSoundVolume(value / 100);
+                            }
+
+                            Label {
+                                text: qsTr("SFX Volume:")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeMedium
+                            }
+
+                            RowLayout {
+                                Layout.fillWidth: true
+                                spacing: Theme.spacingSmall
+
+                                Slider {
+                                    id: sfxVolumeSlider
+
+                                    Layout.fillWidth: true
+                                    from: 0
+                                    to: 100
+                                    value: 100
+                                    stepSize: 1
+                                    onValueChanged: {
+                                        if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
+                                            gameEngine.audioSystem.setSoundVolume(value / 100);
+
+                                    }
+                                }
+
+                                Label {
+                                    text: Math.round(sfxVolumeSlider.value) + "%"
+                                    color: Theme.textSub
+                                    font.pointSize: Theme.fontSizeMedium
+                                    Layout.minimumWidth: 45
+                                }
+
+                            }
+
+                            Label {
+                                text: qsTr("Voice Volume:")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeMedium
+                            }
+
+                            RowLayout {
+                                Layout.fillWidth: true
+                                spacing: Theme.spacingSmall
+
+                                Slider {
+                                    id: voiceVolumeSlider
+
+                                    Layout.fillWidth: true
+                                    from: 0
+                                    to: 100
+                                    value: 100
+                                    stepSize: 1
+                                    onValueChanged: {
+                                        if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
+                                            gameEngine.audioSystem.setVoiceVolume(value / 100);
+
+                                    }
+                                }
+
+                                Label {
+                                    text: Math.round(voiceVolumeSlider.value) + "%"
+                                    color: Theme.textSub
+                                    font.pointSize: Theme.fontSizeMedium
+                                    Layout.minimumWidth: 45
+                                }
 
                             }
-                        }
 
-                        Label {
-                            text: Math.round(sfxVolumeSlider.value) + "%"
-                            color: Theme.textSub
-                            font.pointSize: Theme.fontSizeMedium
-                            Layout.minimumWidth: 45
                         }
 
                     }
 
-                    Label {
-                        text: qsTr("Voice Volume:")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeMedium
+                    Rectangle {
+                        Layout.fillWidth: true
+                        Layout.preferredHeight: 1
+                        color: Theme.border
                     }
 
-                    RowLayout {
+                    ColumnLayout {
                         Layout.fillWidth: true
-                        spacing: Theme.spacingSmall
+                        spacing: Theme.spacingMedium
 
-                        Slider {
-                            id: voiceVolumeSlider
+                        Label {
+                            text: qsTr("Language")
+                            color: Theme.textMain
+                            font.pointSize: Theme.fontSizeLarge
+                            font.bold: true
+                        }
+
+                        Rectangle {
+                            Layout.fillWidth: true
+                            Layout.preferredHeight: 2
+                            color: Theme.border
+                            opacity: 0.5
+                        }
 
+                        GridLayout {
                             Layout.fillWidth: true
-                            from: 0
-                            to: 100
-                            value: 100
-                            stepSize: 1
-                            onValueChanged: {
-                                if (typeof gameEngine !== 'undefined' && gameEngine.audioSystem)
-                                    gameEngine.audioSystem.setVoiceVolume(value / 100);
+                            columns: 2
+                            rowSpacing: Theme.spacingMedium
+                            columnSpacing: Theme.spacingMedium
 
+                            Label {
+                                text: qsTr("Select Language:")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeMedium
                             }
-                        }
 
-                        Label {
-                            text: Math.round(voiceVolumeSlider.value) + "%"
-                            color: Theme.textSub
-                            font.pointSize: Theme.fontSizeMedium
-                            Layout.minimumWidth: 45
-                        }
+                            ComboBox {
+                                id: languageComboBox
 
-                    }
+                                Layout.fillWidth: true
+                                model: typeof languageManager !== 'undefined' ? languageManager.availableLanguages : []
+                                currentIndex: {
+                                    if (typeof languageManager === 'undefined')
+                                        return 0;
 
-                }
+                                    var idx = languageManager.availableLanguages.indexOf(languageManager.currentLanguage);
+                                    return idx >= 0 ? idx : 0;
+                                }
+                                displayText: {
+                                    if (typeof languageManager === 'undefined' || !currentText)
+                                        return "";
 
-            }
+                                    return languageManager.languageDisplayName(currentText);
+                                }
+                                onCurrentTextChanged: {
+                                    if (typeof languageManager !== 'undefined' && currentText)
+                                        languageManager.setLanguage(currentText);
 
-            Rectangle {
-                Layout.fillWidth: true
-                Layout.preferredHeight: 1
-                color: Theme.border
-            }
+                                }
 
-            ColumnLayout {
-                Layout.fillWidth: true
-                spacing: Theme.spacingMedium
+                                delegate: ItemDelegate {
+                                    width: languageComboBox.width
+                                    highlighted: languageComboBox.highlightedIndex === index
 
-                Label {
-                    text: qsTr("Language")
-                    color: Theme.textMain
-                    font.pointSize: Theme.fontSizeLarge
-                    font.bold: true
-                }
+                                    contentItem: Text {
+                                        text: typeof languageManager !== 'undefined' ? languageManager.languageDisplayName(modelData) : modelData
+                                        color: Theme.textMain
+                                        font.pointSize: Theme.fontSizeMedium
+                                        elide: Text.ElideRight
+                                        verticalAlignment: Text.AlignVCenter
+                                    }
 
-                Rectangle {
-                    Layout.fillWidth: true
-                    Layout.preferredHeight: 2
-                    color: Theme.border
-                    opacity: 0.5
-                }
+                                }
+
+                            }
+
+                            Label {
+                                text: qsTr("Language changes apply immediately")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeSmall
+                                opacity: 0.7
+                                Layout.columnSpan: 2
+                            }
+
+                        }
 
-                GridLayout {
-                    Layout.fillWidth: true
-                    columns: 2
-                    rowSpacing: Theme.spacingMedium
-                    columnSpacing: Theme.spacingMedium
-
-                    Label {
-                        text: qsTr("Select Language:")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeMedium
                     }
 
-                    ComboBox {
-                        id: languageComboBox
+                    Rectangle {
+                        Layout.fillWidth: true
+                        Layout.preferredHeight: 1
+                        color: Theme.border
+                    }
 
+                    ColumnLayout {
                         Layout.fillWidth: true
-                        model: typeof languageManager !== 'undefined' ? languageManager.availableLanguages : []
-                        currentIndex: {
-                            if (typeof languageManager === 'undefined')
-                                return 0;
+                        spacing: Theme.spacingMedium
 
-                            var idx = languageManager.availableLanguages.indexOf(languageManager.currentLanguage);
-                            return idx >= 0 ? idx : 0;
+                        Label {
+                            text: qsTr("About")
+                            color: Theme.textMain
+                            font.pointSize: Theme.fontSizeLarge
+                            font.bold: true
                         }
-                        displayText: {
-                            if (typeof languageManager === 'undefined' || !currentText)
-                                return "";
 
-                            return languageManager.languageDisplayName(currentText);
+                        Rectangle {
+                            Layout.fillWidth: true
+                            Layout.preferredHeight: 2
+                            color: Theme.border
+                            opacity: 0.5
                         }
-                        onCurrentTextChanged: {
-                            if (typeof languageManager !== 'undefined' && currentText)
-                                languageManager.setLanguage(currentText);
 
-                        }
+                        ColumnLayout {
+                            Layout.fillWidth: true
+                            spacing: Theme.spacingSmall
+
+                            Label {
+                                text: qsTr("Standard of Iron - RTS Game")
+                                color: Theme.textMain
+                                font.pointSize: Theme.fontSizeMedium
+                                font.bold: true
+                            }
+
+                            Label {
+                                text: qsTr("Version 1.0.0")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeSmall
+                            }
 
-                        delegate: ItemDelegate {
-                            width: languageComboBox.width
-                            highlighted: languageComboBox.highlightedIndex === index
+                            Rectangle {
+                                Layout.fillWidth: true
+                                Layout.preferredHeight: 1
+                                color: Theme.border
+                                opacity: 0.3
+                                Layout.topMargin: Theme.spacingSmall
+                                Layout.bottomMargin: Theme.spacingSmall
+                            }
 
-                            contentItem: Text {
-                                text: typeof languageManager !== 'undefined' ? languageManager.languageDisplayName(modelData) : modelData
+                            Label {
+                                text: qsTr("Third-Party Software")
                                 color: Theme.textMain
                                 font.pointSize: Theme.fontSizeMedium
-                                elide: Text.ElideRight
-                                verticalAlignment: Text.AlignVCenter
+                                font.bold: true
                             }
 
-                        }
+                            Label {
+                                text: qsTr("This game uses the Qt framework, licensed under the GNU Lesser General Public License v3 (LGPL v3).")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeSmall
+                                wrapMode: Text.WordWrap
+                                Layout.fillWidth: true
+                            }
 
-                    }
+                            Label {
+                                text: qsTr("Qt is dynamically linked, allowing you to replace Qt libraries with your own versions.")
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeSmall
+                                wrapMode: Text.WordWrap
+                                Layout.fillWidth: true
+                            }
+
+                            Label {
+                                text: "<a href='https://www.gnu.org/licenses/lgpl-3.0.html'>LGPL v3 License</a> | <a href='https://www.qt.io'>Qt Website</a>"
+                                color: Theme.textSub
+                                font.pointSize: Theme.fontSizeSmall
+                                textFormat: Text.RichText
+                                onLinkActivated: function(link) {
+                                    Qt.openUrlExternally(link);
+                                }
+
+                                MouseArea {
+                                    anchors.fill: parent
+                                    acceptedButtons: Qt.NoButton
+                                    cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
+                                }
+
+                            }
+
+                        }
 
-                    Label {
-                        text: qsTr("Language changes apply immediately")
-                        color: Theme.textSub
-                        font.pointSize: Theme.fontSizeSmall
-                        opacity: 0.7
-                        Layout.columnSpan: 2
                     }
 
                 }
 
             }
 
-            Item {
-                Layout.fillHeight: true
-            }
-
         }
 
     }