summaryrefslogtreecommitdiffstats
path: root/app/bluetooth
diff options
context:
space:
mode:
authorzheng_wenlong <wenlong_zheng@nexty-ele.com>2019-04-08 18:43:05 +0900
committerzheng_wenlong <wenlong_zheng@nexty-ele.com>2019-04-10 09:59:55 +0900
commit238076ffff13356c620a7834e25537d4a41e97a6 (patch)
tree73a1a1c9cebc6da145d7baaac1eaec7329ec4f2a /app/bluetooth
Add demo3 settings source code for cluster mode. [Patch Set 2] Update LICENSE file. Change-Id: I586a211bb55e5017de62e36edd0bbba6968de1b8 BUG-AGL: SPEC-2261 Signed-off-by: zheng_wenlong <wenlong_zheng@nexty-ele.com>
Diffstat (limited to 'app/bluetooth')
-rw-r--r--app/bluetooth/Bluetooth.qml435
-rw-r--r--app/bluetooth/bluetooth.qrc8
-rw-r--r--app/bluetooth/images/HMI_Pair_Button.svg43
-rw-r--r--app/bluetooth/images/HMI_Paired_Button.svg43
-rw-r--r--app/bluetooth/images/HMI_Settings_BluetoothIcon.svg55
5 files changed, 584 insertions, 0 deletions
diff --git a/app/bluetooth/Bluetooth.qml b/app/bluetooth/Bluetooth.qml
new file mode 100644
index 0000000..7a63e84
--- /dev/null
+++ b/app/bluetooth/Bluetooth.qml
@@ -0,0 +1,435 @@
+/*
+ * Copyright (C) 2016 The Qt Company Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import QtQuick 2.6
+import QtQuick.Layouts 1.1
+import QtQuick.Controls 2.0
+import QtWebSockets 1.0
+import '..'
+
+SettingPage {
+ id: root
+ icon: '/bluetooth/images/HMI_Settings_BluetoothIcon.svg'
+ title: 'Bluetooth'
+ checkable: true
+ readonly property bool isBluetooth: true
+ property int pairedDeviceCount: 0
+
+ Connections {
+ target: bluetooth
+ onRequestConfirmationEvent: {
+ bluetooth.send_confirmation()
+ }
+
+ onDeviceAddedEvent: {
+ if (data.Paired === "True") {
+ pairedDeviceList.append({
+ deviceAddress: data.Address,
+ deviceName: data.Name,
+ devicePairable: data.Paired,
+ deviceConnect: data.Connected,
+ connectAVP: data.AVPConnected,
+ connectHFP: data.HFPConnected,
+ textToShow: ""
+ })
+ pairedDeviceCount = pairedDeviceCount + 1
+ } else {
+ btDeviceList.append({
+ deviceAddress: data.Address,
+ deviceName: data.Name,
+ devicePairable: data.Paired,
+ deviceConnect: data.Connected,
+ connectAVP: data.AVPConnected,
+ connectHFP: data.HFPConnected,
+ textToShow: ""
+ })
+ }
+ }
+
+ onDeviceRemovedEvent: {
+ if (findDevice(data.Address) >= 0) {
+ btDeviceList.remove(findDevice(data.Address))
+ } else if(findPairDevice(data.Address) >= 0) {
+ pairedDeviceList.remove(findPairDevice(data.Address))
+ pairedDeviceCount = pairedDeviceCount - 1
+ }
+ }
+
+ onDeviceUpdatedEvent: {
+ updateDeviceAttribute(data)
+ }
+
+ onDeviceListEvent: {
+ for (var i = 0; i < data.list.length; i++) {
+ var value = data.list[i]
+ if (value.Paired==="True") {
+ if(findPairDevice(value.Address) == -1) {
+ pairedDeviceList.append({
+ deviceAddress: value.Address,
+ deviceName: value.Name,
+ devicePairable:value.Paired,
+ deviceConnect: value.Connected,
+ connectAVP: value.AVPConnected,
+ connectHFP: value.HFPConnected,
+ textToShow: ""
+ })
+ pairedDeviceCount = pairedDeviceCount + 1
+ }
+ }
+ else
+ if (findDevice(value.Address) == -1) {
+ btDeviceList.append({
+ deviceAddress: value.Address,
+ deviceName: value.Name,
+ devicePairable:value.Paired,
+ deviceConnect: value.Connected,
+ connectAVP: value.AVPConnected,
+ connectHFP: value.HFPConnected,
+ textToShow: ""
+ })
+ }
+ }
+ }
+
+ onPowerChanged: {
+ root.checked = bluetooth.power
+ }
+ }
+
+ Text {
+ id: log
+ anchors.fill: parent
+ anchors.margins: 10
+ horizontalAlignment: Text.AlignHCenter
+ verticalAlignment: Text.AlignVCenter
+ }
+
+ onCheckedChanged: {
+ console.log("Bluetooth set to", checked)
+
+ pairedDeviceCount = 0
+ bluetooth.power = checked;
+ bluetooth.discoverable = checked;
+
+ if (checked == true) {
+ bluetooth.start_discovery()
+ } else {
+ btDeviceList.clear()
+ pairedDeviceList.clear()
+ bluetooth.stop_discovery()
+ }
+ }
+
+ ListModel {
+ id: pairedDeviceList
+ }
+ ListModel {
+ id: btDeviceList
+ }
+
+
+ Rectangle {
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.bottom: parent.bottom
+ anchors.margins: 80
+ width: buttonScan.width + 10
+ height: buttonScan.height + 10
+ color: "#222"
+ border.color: "white"
+
+ Button {
+ id: buttonScan
+ anchors.centerIn: parent
+ width: 100
+ text: bluetooth.discoverable ? "STOP" :"SEARCH"
+
+ MouseArea {
+ //id: mouseArea
+ anchors.fill: parent
+
+ onClicked: {
+ if (bluetooth.discoverable === false && bluetooth.power === true) {
+ bluetooth.start_discovery()
+ bluetooth.discoverable = true;
+ } else {
+ bluetooth.stop_discovery()
+ bluetooth.discoverable = false;
+ }
+ }
+ }
+ }
+ }
+
+ Component {
+ id:blueToothDevice
+ Rectangle {
+ height: 120
+ width: parent.width
+ color: "transparent"
+ MouseArea {
+ anchors.fill: parent
+ Column {
+ anchors.left: parent.left
+ anchors.leftMargin: 80
+ TextMetrics {
+ id: textMetrics
+ font.family: "Arial"
+ elide: Text.ElideRight
+ elideWidth: 140
+ text: deviceName
+ }
+ Text {
+ id: btName
+ text: textMetrics.elidedText
+ color: '#66FF99'
+ font.pixelSize: 48
+ }
+ Text {
+ id: btStatus
+ property string connectionState:""
+ text: {
+ if ((devicePairable === "True")
+ && (deviceConnect === "True")
+ && (connectAVP === "True")
+ && (connectHFP === "False"))
+ text = " AV Connection, "
+ else if ((devicePairable === "True")
+ && (deviceConnect === "True")
+ && (connectHFP === "True")
+ && (connectAVP === "False"))
+ text = " Handsfree Connection, "
+ else if ((devicePairable === "True")
+ && (deviceConnect === "True")
+ && (connectHFP === "True")
+ && (connectAVP === "True"))
+ text = " Handsfree & AV Connection, "
+ else
+ text = connectionState
+
+ text = deviceAddress + text
+ }
+ font.pixelSize: 18
+ color: "#ffffff"
+ font.italic: true
+ }
+ Text {
+ id: btPairable
+ text: devicePairable
+ visible: false
+ }
+ Text {
+ id: btConnectstatus
+ text: deviceConnect
+ visible: false
+ }
+ }
+ Button {
+ id: removeButton
+ anchors.top:parent.top
+ anchors.topMargin: 15
+ //anchors.horizontalCenter: btName.horizontalCenter
+ anchors.right: parent.right
+ anchors.rightMargin: 100
+
+ text: "Remove"
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ bluetooth.remove_device(deviceAddress);
+ if (findDevice(deviceAddress) != -1) {
+ btDeviceList.remove(findDevice(deviceAddress))
+ } else if (findPairDevice(deviceAddress) != -1) {
+ pairedDeviceList.remove(findPairDevice(deviceAddress))
+ pairedDeviceCount = pairedDeviceCount - 1
+ }
+ }
+ }
+ }
+
+ Button {
+ id: connectButton
+ anchors.top:parent.top
+ anchors.topMargin: 15
+ anchors.right: removeButton.left
+ anchors.rightMargin: 10
+
+ text: (deviceConnect == "True") ? "Disconnect" : ((btPairable.text == "True") ? "Connect" : "Pair")
+ // only when HFP or AVP is connected, button will be shown as Disconnect
+ MouseArea {
+ anchors.fill: parent
+ onClicked: {
+ if (connectButton.text == "Pair"){
+ connectButton.text = "Connect"
+ bluetooth.pair(deviceAddress)
+ btPairable.text = "True"
+ }
+ else if (connectButton.text == "Connect"){
+ connectButton.text = "Disconnect"
+ bluetooth.connect(deviceAddress)
+ }
+ else if (connectButton.text == "Disconnect"){
+ bluetooth.disconnect(deviceAddress)
+ connectButton.text = "Connect"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ Text {
+ id: pairedlabel
+ width: parent.width
+ anchors.top: parent.top
+ anchors.topMargin: 50
+ anchors.left: parent.left
+ anchors.leftMargin: 80
+ height: 80
+ color:'grey'
+ font.pixelSize: 30
+ text:{
+ if (bluetooth.power == true && pairedDeviceCount != 0)
+ "LIST OF PAIRED DEVICES"
+ else
+ ""
+ }
+ }
+ ListView{
+ id: pairedListView
+ width: parent.width
+ anchors.top: pairedlabel.bottom
+ anchors.bottom: pairedlabel.bottom
+ anchors.bottomMargin: (-120*pairedDeviceCount)
+ model: pairedDeviceList
+ delegate: blueToothDevice
+ clip: true
+ }
+ Image {
+ anchors.bottom: pairedListView.bottom
+ anchors.left: parent.left
+ anchors.leftMargin: 80
+ height: 5
+ source: (bluetooth.power === true && pairedDeviceCount != 0) ? '../images/HMI_Settings_DividingLine.svg':''
+ }
+ Text {
+ id: detectedlabel
+ width: parent.width
+ anchors.top: pairedListView.bottom
+ anchors.topMargin: (pairedDeviceCount != 0) ? 80:-80
+ anchors.left: parent.left
+ anchors.leftMargin: 80
+ height: 80
+ color:'grey'
+ font.pixelSize: 30
+ text: {
+ if (bluetooth.power === true)
+ "LIST OF DETECTED DEVICES"
+ else
+ ""
+ }
+ }
+ ListView {
+ id:listView2
+ width: parent.width
+ anchors.top: detectedlabel.bottom
+ anchors.bottom: parent.bottom
+ anchors.bottomMargin: 150
+ model: btDeviceList
+ delegate: blueToothDevice
+ clip: true
+ }
+
+ function findDevice(address) {
+ for (var i = 0; i < btDeviceList.count; i++) {
+ if (address === btDeviceList.get(i).deviceAddress)
+ return i
+ }
+ return -1
+ }
+ function findPairDevice(address){
+ for (var i = 0; i < pairedDeviceList.count; i++) {
+ if (address === pairedDeviceList.get(i).deviceAddress)
+ return i
+ }
+ return -1
+ }
+
+ function updateDeviceAttribute(data){
+ var text = ""
+ for (var i = 0; i < btDeviceList.count; i++) {
+ if (data.Address === btDeviceList.get(i).deviceAddress){
+ btDeviceList.get(i).devicePairable = data.Paired
+ if (data.Paired === "True")
+ {
+ pairedDeviceList.append({
+ deviceAddress: btDeviceList.get(i).deviceAddress,
+ deviceName: btDeviceList.get(i).deviceName,
+ devicePairable:btDeviceList.get(i).devicePairable,
+ deviceConnect: btDeviceList.get(i).deviceConnect,
+ connectAVP: btDeviceList.get(i).connectAVP,
+ connectHFP: btDeviceList.get(i).connectHFP,
+ textToShow: ""
+ })
+ pairedDeviceCount = pairedDeviceCount + 1
+ btDeviceList.remove(i, 1)
+ }
+ else{
+ text=deviceConnectionAttribute(data)
+ btDeviceList.set(i, {
+ textToShow: " " + text
+ })
+
+ btDeviceList.get(i).deviceConnect = data.Connected
+ console.log(data.Connected)
+ }
+
+ }
+ }
+ for (var i = 0; i < pairedDeviceList.count; i++) {
+ if(data.Address === pairedDeviceList.get(i).deviceAddress){
+ pairedDeviceList.get(i).devicePairable = data.Paired
+
+ text=deviceConnectionAttribute(data)
+ pairedDeviceList.set(i, { textToShow: " " + text })
+
+ pairedDeviceList.get(i).deviceConnect = data.Connected
+ }
+ }
+ }
+
+ function deviceConnectionAttribute(data){
+ var text = ""
+ if ((data.Paired === "True")
+ && (data.Connected === "True")
+ && (data.AVPConnected === "True")
+ && (data.HFPConnected === "False"))
+ text = "AV Connection, "
+ else if ((data.Paired === "True")
+ && (data.Connected === "True")
+ && (data.HFPConnected === "True")
+ && (data.AVPConnected === "False"))
+ text = "Handsfree Connection, "
+ else if ((data.Paired === "True")
+ && (data.Connected === "True")
+ && (data.HFPConnected === "True")
+ && (data.AVPConnected === "True")) {
+ console.log("all connected!!")
+ text = ", Handsfree & AV Connection"}
+ else
+ text = ""
+ return text
+ }
+}
diff --git a/app/bluetooth/bluetooth.qrc b/app/bluetooth/bluetooth.qrc
new file mode 100644
index 0000000..bc8b70a
--- /dev/null
+++ b/app/bluetooth/bluetooth.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/bluetooth">
+ <file>Bluetooth.qml</file>
+ <file>images/HMI_Pair_Button.svg</file>
+ <file>images/HMI_Paired_Button.svg</file>
+ <file>images/HMI_Settings_BluetoothIcon.svg</file>
+ </qresource>
+</RCC>
diff --git a/app/bluetooth/images/HMI_Pair_Button.svg b/app/bluetooth/images/HMI_Pair_Button.svg
new file mode 100644
index 0000000..9d3f626
--- /dev/null
+++ b/app/bluetooth/images/HMI_Pair_Button.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 151 51"
+ style="enable-background:new 0 0 151 51;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;stroke:url(#SVGID_1_);stroke-miterlimit:10;}
+ .st1{opacity:0.3;fill:url(#SVGID_2_);}
+ .st2{fill:#FFFFFF;}
+ .st3{font-family:'Roboto-Regular';}
+ .st4{font-size:19.7348px;}
+ .st5{letter-spacing:3;}
+</style>
+<switch>
+ <g i:extraneous="self">
+ <g>
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="24.7258" y1="75.9063" x2="126.2742" y2="-24.9063">
+ <stop offset="0" style="stop-color:#00ADDC"/>
+ <stop offset="1" style="stop-color:#6BFBFF"/>
+ </linearGradient>
+ <rect x="0.5" y="0.5" class="st0" width="150" height="50"/>
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-25.8746" y1="126.14" x2="190.2191" y2="-88.3878">
+ <stop offset="0" style="stop-color:#00ADDC"/>
+ <stop offset="1" style="stop-color:#6BFBFF"/>
+ </linearGradient>
+ <rect x="0.5" y="0.5" class="st1" width="150" height="50"/>
+ <g>
+ <text transform="matrix(1 0 0 1 46.5699 33.9046)" class="st2 st3 st4 st5">Pair</text>
+ </g>
+ </g>
+ </g>
+</switch>
+</svg>
diff --git a/app/bluetooth/images/HMI_Paired_Button.svg b/app/bluetooth/images/HMI_Paired_Button.svg
new file mode 100644
index 0000000..6e1f57a
--- /dev/null
+++ b/app/bluetooth/images/HMI_Paired_Button.svg
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
+ <!ENTITY ns_extend "http://ns.adobe.com/Extensibility/1.0/">
+ <!ENTITY ns_ai "http://ns.adobe.com/AdobeIllustrator/10.0/">
+ <!ENTITY ns_graphs "http://ns.adobe.com/Graphs/1.0/">
+ <!ENTITY ns_vars "http://ns.adobe.com/Variables/1.0/">
+ <!ENTITY ns_imrep "http://ns.adobe.com/ImageReplacement/1.0/">
+ <!ENTITY ns_sfw "http://ns.adobe.com/SaveForWeb/1.0/">
+ <!ENTITY ns_custom "http://ns.adobe.com/GenericCustomNamespace/1.0/">
+ <!ENTITY ns_adobe_xpath "http://ns.adobe.com/XPath/1.0/">
+]>
+<svg version="1.1" id="Layer_1" xmlns:x="&ns_extend;" xmlns:i="&ns_ai;" xmlns:graph="&ns_graphs;"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 151 51"
+ style="enable-background:new 0 0 151 51;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:none;stroke:url(#SVGID_1_);stroke-miterlimit:10;}
+ .st1{opacity:0.84;fill:url(#SVGID_2_);}
+ .st2{fill:#27232B;}
+ .st3{font-family:'Roboto-Regular';}
+ .st4{font-size:19.7348px;}
+ .st5{letter-spacing:2.9;}
+</style>
+<switch>
+ <g i:extraneous="self">
+ <g>
+ <linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="24.7258" y1="75.9063" x2="126.2742" y2="-24.9063">
+ <stop offset="0" style="stop-color:#00ADDC"/>
+ <stop offset="1" style="stop-color:#6BFBFF"/>
+ </linearGradient>
+ <rect x="0.5" y="0.5" class="st0" width="150" height="50"/>
+ <linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="-25.8746" y1="126.14" x2="190.2191" y2="-88.3878">
+ <stop offset="0" style="stop-color:#00ADDC"/>
+ <stop offset="1" style="stop-color:#6BFBFF"/>
+ </linearGradient>
+ <rect x="0.5" y="0.5" class="st1" width="150" height="50"/>
+ <g>
+ <text transform="matrix(1.0252 0 0 1 33.1099 33.9038)" class="st2 st3 st4 st5">PairED</text>
+ </g>
+ </g>
+ </g>
+</switch>
+</svg>
diff --git a/app/bluetooth/images/HMI_Settings_BluetoothIcon.svg b/app/bluetooth/images/HMI_Settings_BluetoothIcon.svg
new file mode 100644
index 0000000..d41de2a
--- /dev/null
+++ b/app/bluetooth/images/HMI_Settings_BluetoothIcon.svg
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+
+<svg
+ xmlns:i="&amp;ns_ai;"
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ version="1.1"
+ id="Layer_1"
+ x="0px"
+ y="0px"
+ viewBox="0 0 100 100"
+ style="enable-background:new 0 0 100 100;"
+ xml:space="preserve"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="HMI_Settings_BluetoothIcon.svg"><metadata
+ id="metadata16"><rdf:RDF><cc:Work
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+ id="defs14" /><sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="2560"
+ inkscape:window-height="1464"
+ id="namedview12"
+ showgrid="false"
+ inkscape:zoom="2.36"
+ inkscape:cx="-130.29661"
+ inkscape:cy="50"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="Layer_1" /><style
+ type="text/css"
+ id="style3">
+ .st0{fill:#FFFFFF;}
+</style><switch
+ id="switch5"><g
+ i:extraneous="self"
+ id="g7"><g
+ id="Bluetooth_Icon"><path
+ class="st0"
+ d="M48.3,86.9V53.4L30.9,69.7l-1.4-1.5l18.8-17.6v-0.2L27.9,36.4l1.1-1.6L48.3,48v-35l23.6,18.2l-20.3,19 l20.5,14.1L48.3,86.9z M50.3,51.8v30.5l18.6-17.6L50.3,51.8z M50.3,17.1v31.6l18.5-17.3L50.3,17.1z"
+ id="path10" /></g></g></switch></svg> \ No newline at end of file