第三階段應用層——1.7 數碼相冊—電子書(3)—輪詢方式支持多輸入

數碼相冊——電子書輪詢方式支持多輸入

  • 硬件平臺:韋東山嵌入式Linxu開發板(S3C2440.v3)
  • 軟件平臺:運行於VMware Workstation 12 Player下UbuntuLTS16.04_x64 系統
  • 參考資料:《嵌入式Linux應用開發手冊》、《嵌入式Linux應用開發手冊第2版》、《gcc中文手冊》
  • 開發環境:Linux 3.4.2內核、arm-linux-gcc 4.3.2工具鏈


一、前言

【1.7 數碼相冊—電子書(1)—實現】【1.7 數碼相冊—電子書(2)—編寫通用的Makefile】這兩篇博文中,我們實現了在開發板中顯示電子書,但是仍然存在一些缺點,這節課針對這些缺點來進行改進。

  • 實現:
    1、電子書支持多種輸入方式如標準串口輸入實現翻頁退出操作,觸摸LCD實現翻頁操作;
    2、在main.c中採用輪詢的方式支持多種輸入方式。

二、框架圖

1、軟件框架圖

在這裏插入圖片描述
對於上述完成主要功能的5個部分:encoding編碼部分、fonts獲取字體點陣部分、display顯示部分、input輸入部分、draw協調部分

  1. encoding編碼部分去文件中獲得編碼信息,對於每個文件,保存的時候,系統對自動或手動的根據編碼規範,對文件的信息進行編碼:如保存爲ASCII、GBK、UTF-8、UTF16LE、UTF16BE
  2. fonts獲取字體點陣部分根據獲得的編碼信息得到字體數據(LCD上表示爲點陣)
  3. display顯示部分把字體數據(點陣)顯示在LCD上
  4. input輸入部分管理不同的輸入方式,定製不同的輸入方式所實現的控制,滿足多種控制場合;
  5. draw協調部分組織各個模塊部分進行合作,實現電子書的顯示、翻頁功能等。

2、Makefile框架圖

在這裏插入圖片描述
由於整個分爲如下3部分:頂層目錄的Makefile頂層目錄的Makefile.build各級子目錄的Makefile

所以對於新添加的input輸入部分需要進行如下修改:其目錄下也需要添加該目錄下的Makefile,對於頂層目錄的Makeifle也需要添加obj-y += /input,具體的在編譯的時候會介紹。

而對於頂層的Makefile.build不需要進行修改,因爲它是一個比較通用的文件根據頂層目錄的Makefile信息進入到各個子目錄下進行預處理、編譯、彙編,最後每個子目錄與頂層得到的built-in.o進行鏈接,最後得到可執行文件show_file.

下面就來介紹添加的input輸入部分

三、input輸入部分

1、管理者頭文件input_manager.h

在這個文件:

  • 定義一個結構體變量拓展文件可以根據自己的情況:分配結構體、設置結構體、註冊結構體
  • 定義一些函數,供外部文件調用

#ifndef _INPUT_MANAGER_H
#define _INPUT_MANAGER_H
#include <sys/time.h>

#define INPUT_TYPE_STDIN 			0
#define INPUT_TYPE_TOUCHSCREEN 		1

#define INPUT_VALUE_UP 				0
#define INPUT_VALUE_DOWN 			1
#define INPUT_VALUE_EXIT 			2
#define INPUT_VALUE_UNKONW 		   -1

/* 輸入事件信息結構體 */
typedef struct InputEvent {
	struct timeval time;
	int type;
	int val;
}T_InputEvent, *PT_InputEvent;

/* 輸入事件結構體 */
typedef struct InputOpr {
	char *name;
	int (*DeviceInit)(void);
	int (*DeviceExit)(void);
	int (*GetInputEvent)(PT_InputEvent ptInputEvent);
	struct InputOpr *ptNext;
}T_InputOpr, *PT_InputOpr;

int RegisterInputOpr(PT_InputOpr ptFontOpr);
int InputInit(void);
void ShowInputOpr(void);
int GetInputEvent(PT_InputEvent ptInputEvent);
int AllInputDeviceInit(void);
int StdinInit(void);
int TouchScreenInit(void);

#endif /* _INPUT_MANAGER_H */

2、管理者實現文件input_manager.c

  • 註冊函數
    每個輸入方式的結構體,可以調用這個函數把結構體註冊到鏈表中
/* 函數名:	註冊函數
 * 函數功能:構建一個鏈表:把多個拓展文件的結構體“串”起來
 * 函數實現:根據傳入的結點,首先判斷該鏈表頭是否爲空
 *				空則,頭結點指向傳入的節點,且把節點的ptNext域指向NULL
 *				不空則,尾插法插入鏈表
 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
	PT_InputOpr ptTmp;

	if (!s_ptInputOprHead) {
		s_ptInputOprHead   = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	} else {
		ptTmp = s_ptInputOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}

	return 0;
}
  • 顯示函數
    通過這個函數,可以把鏈表中所有input方式的名字打印出來,供用戶查看所支持的input方式。
/* 顯示支持拓展文件的名字 */
void ShowInputOpr(void)
{
	int i = 0;
	PT_InputOpr ptTmp = s_ptInputOprHead;

	while (ptTmp) {
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}
  • 初始化函數
    調用此函數,可以把對應的input方式結構體放入鏈表中。
/* 初始化函數 */
int InputInit(void)
{
	int error;
	
	error = StdinInit();
	error |= TouchScreenInit();

	return error;
}
  • 調用input設備事件函數
/* 調用所有的Input設備的事件發生函數,即輪詢的方式獲取LCD或標準輸入的數據 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 把鏈表中的InputOpr的GetInputEvent都調用一次,一旦有數據立即返回 */
	PT_InputOpr ptTmp;

	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->GetInputEvent(ptInputEvent) == 0)
			return 0;
		ptTmp = ptTmp->ptNext;
	}

	return -1;
}
  • 設備初始化函數
    初始化每個設備的函數(設置input設備、調整模式等)
/* 初始化所有支持的Input設備 */
int AllInputDeviceInit()
{
	int error;
	PT_InputOpr ptTmp;

	error = -1;
	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->DeviceInit() == 0)
			error = 0;
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}

  • 完整文件

#include <config.h>
#include <string.h>
#include <stdlib.h>

#include "input_manager.h"

static PT_InputOpr s_ptInputOprHead;	//鏈表頭

/* 函數名:  註冊函數
 * 函數功能:構建一個鏈表:把多個拓展文件的結構體“串”起來
 * 函數實現:根據傳入的結點,首先判斷該鏈表頭是否爲空
 *				空則,頭結點指向傳入的節點,且把節點的ptNext域指向NULL
 *				不空則,尾插法插入鏈表
 */
int RegisterInputOpr(PT_InputOpr ptInputOpr)
{
	PT_InputOpr ptTmp;

	if (!s_ptInputOprHead) {
		s_ptInputOprHead   = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	} else {
		ptTmp = s_ptInputOprHead;
		while (ptTmp->ptNext)
		{
			ptTmp = ptTmp->ptNext;
		}
		ptTmp->ptNext	  = ptInputOpr;
		ptInputOpr->ptNext = NULL;
	}

	return 0;
}

/* 顯示支持拓展文件的名字 */
void ShowInputOpr(void)
{
	int i = 0;
	PT_InputOpr ptTmp = s_ptInputOprHead;

	while (ptTmp) {
		printf("%02d %s\n", i++, ptTmp->name);
		ptTmp = ptTmp->ptNext;
	}
}

/* 初始化函數 */
int InputInit(void)
{
	int error;
	
	error = StdinInit();
	error |= TouchScreenInit();

	return error;
}

/* 調用所有的Input設備的事件發生函數,即輪詢的方式獲取LCD或標準輸入的數據 */
int GetInputEvent(PT_InputEvent ptInputEvent)
{
	/* 把鏈表中的InputOpr的GetInputEvent都調用一次,一旦有數據立即返回 */
	PT_InputOpr ptTmp;

	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->GetInputEvent(ptInputEvent) == 0)
			return 0;
		ptTmp = ptTmp->ptNext;
	}

	return -1;
}

/* 初始化所有支持的Input設備 */
int AllInputDeviceInit()
{
	int error;
	PT_InputOpr ptTmp;

	error = -1;
	ptTmp = s_ptInputOprHead;
	
	while (ptTmp) {
		if (ptTmp->DeviceInit() == 0)
			error = 0;
		ptTmp = ptTmp->ptNext;
	}

	return 0;
}

3、input設備——stdin

在這個函數中,爲stdin設備進行:分配結構體、設置結構體、註冊結構體

/**
 * @file  stdin.c
 * @brief 標準輸入input設備的處初始化,處理過程與取消
 * @version 1.0 (版本聲明)
 * @author Dk
 * @date  July 1,2020
 */
#include <termios.h>
#include <unistd.h>
#include <stdio.h>

#include "input_manager.h"

/**
 * @Description: 獲取標準串口輸入的初始化,設置模式使其可以採用非阻塞的方式獲取輸入
 * @return 成功:0
 */
static int StdinDevInit(void)
{
	struct termios ttystate;
	 
	/* 獲得終端的狀態 */
	tcgetattr(STDIN_FILENO, &ttystate);

	/* 關閉標準模式,並設置位爲  1,表示接受最小用戶數輸入爲1 */
	ttystate.c_lflag &= ~ICANON;	
	ttystate.c_cc[VMIN] = 1;		
		
	/* 設置術語狀態 */
	tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

	return 0;
}

/**
 * @Description: 獲取標準串口輸入的退出,恢復原本的模式
 * @return 成功:0
 */
static int StdinDevExit(void)
{
	struct termios ttystate;
	 
	/* 獲得終端的狀態 */
	tcgetattr(STDIN_FILENO, &ttystate);
 
	/* 打開標準模式 */
	ttystate.c_lflag |= ICANON;

	/* 設置術語狀態 */
	tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

	return 0;
}

/**
 * @Description: 根據標準輸入的結果進行相應處理,採用非阻塞的方式獲取輸入
 * @param ptInputEvent - 表示input設備的結構體.
 * @return 描述符fd在描述符集fds中:0 不在:-1
 */
static int StdinGetInputEvent(PT_InputEvent ptInputEvent)
{
	struct timeval tv;
	fd_set fds;
	char c;

	/* 輸入(stdin)執行無阻塞檢查,而不會將超時0 */
	tv.tv_sec = 0;
	tv.tv_usec = 0;

	/* 指定的文件描述符集清空 */
	FD_ZERO(&fds);

	/* 在文件描述符集合中增加一個新的fds */
	FD_SET(STDIN_FILENO, &fds);

	/* 測試指定的fds可讀 */
	select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);

	/* 測試指定的fds是否在該集合中 */
	if (FD_ISSET(STDIN_FILENO, &fds)) {
		/* 存在,處理數據 */
		ptInputEvent->type = INPUT_TYPE_STDIN;
		c = fgetc(stdin);

		if (c == 'u') {
			ptInputEvent->val = INPUT_VALUE_UP;
			gettimeofday(&ptInputEvent->time, NULL);
		} else if (c == 'n') {
			ptInputEvent->val = INPUT_VALUE_DOWN;		
			gettimeofday(&ptInputEvent->time, NULL);
		} else if (c == 'q') {
			ptInputEvent->val = INPUT_VALUE_EXIT;
			gettimeofday(&ptInputEvent->time, NULL);
		} else {
			ptInputEvent->val = INPUT_VALUE_UNKONW;
			gettimeofday(&ptInputEvent->time, NULL);
		}
		return 0;
	}else
		return -1;	
}

static T_InputOpr s_tStdinOpr = {
	.name 			= "stdin",
	.DeviceInit 	= StdinDevInit,
	.DeviceExit 	= StdinDevExit,
	.GetInputEvent  = StdinGetInputEvent,
};

/**
 * @Description: 標準輸入初始化函數,供上層調用註冊設備
 * @return 成功:0
 */
int StdinInit(void)
{
	return RegisterInputOpr(&s_tStdinOpr);
}

4、input設備——touchscreen

在這個函數中,爲touchscreen設備進行:分配結構體、設置結構體、註冊結構體

/**
 * @file  touchscreen.c
 * @brief touchscreen input設備的處初始化,處理過程與取消,參考tslib中的ts_print.c
 * @version 1.0 (版本聲明)
 * @author Dk
 * @date  July 1,2020
 */
#include <stdlib.h>
#include <tslib.h>

#include "input_manager.h"
#include "config.h"
#include "draw.h"

static struct tsdev *s_pTSDev;	//touchscreen設備
static int s_Xres;	//LCD x方向的分辨率
static int s_Xres;	//LCD y方向的分辨率

/**
 * @Description: 獲取touchscreen輸入的初始化,設置模式使其可以採用非阻塞的方式獲取輸入
 * @return 成功:0 失敗: -1
 * @note 由於需要獲取到LCD的分辨率,所以這個函數被調用之前,SelectAndInitDisplay必須被調用,纔可以獲取到數據
 */
static int TouchScreenDevInit(void)
{
	char *pTSName = NULL;	

	/* 根據環境變量獲得設備名,並以非阻塞的方式打開 */
	if((pTSName = getenv("TSLIB_TSDEVICE")) != NULL) 
		s_pTSDev = ts_open(pTSName, 1);
	else
		s_pTSDev = ts_open("/dev/event0", 1);

	if (!s_pTSDev) {
		DBG_PRINTF("ts_open error!\n");
		return -1;
	}

	if (ts_config(s_pTSDev)) {
		DBG_PRINTF("ts_config error!\n");
		return -1;
	}

	/* 獲取LCD分辨率 */
	if (GetDispResolution(&s_Xres, &s_Xres))
		return -1;

	return 0;
}

/**
 * @Description: touchscreen輸入的退出,恢復原本的模式
 * @return 成功:0
 */
static int TouchScreenDevExit(void)
{
	return 0;
}

/**
 * @Description: 判斷上一次事件與此次事件相隔的時間是否超過500ms
 * @param ppreTime - 存儲上一次事件時間的結構體指針,pcurTime - 此次存儲事件時間的結構體指針 
 * @return 兩次事件時間間隔超過500ms:0 無超過:1
 */
static int isOutOf500ms(struct timeval *ppreTime, struct timeval *pcurTime)
{
	int prems;
	int curms;

	/* tv_sec - 秒, tv_usec - 微秒 */
	prems = ppreTime->tv_sec * 1000 + ppreTime->tv_usec / 1000;
	curms = pcurTime->tv_sec * 1000 + pcurTime->tv_usec / 1000;

	return (curms > (prems + 500));
}

/**
 * @Description: 採用查詢的方式獲讀取touchscreen數據
 * @param ptInputEvent - 表示input設備的結構體.
 * @return 有數據:0 無數據:-1
 */
static int TouchScreenGetInputEvent(PT_InputEvent ptInputEvent)
{
	int ret;
	struct ts_sample samp;
	static struct timeval preTime;
	
	ret = ts_read(s_pTSDev, &samp, 1);

	/* 無數據返回 */
	if (ret < 0) {
		DBG_PRINTF("ts_read no data!\n");
		return -1;
	}

	/* 有數據處理 */
	if ((isOutOf500ms(&preTime, &samp.tv))) {
		/* 兩次事件時間間隔超過500ms */
		ptInputEvent->type = INPUT_TYPE_TOUCHSCREEN;
		preTime = samp.tv;
		ptInputEvent->time = samp.tv;

		/* 根據讀取到的LCD y座標值,進行對應操作
		 * y < y分辨率的1/3,則爲上翻
		 * y > y分辨率的2/3,則爲下翻
 		 * 其他情況,則爲未定義操作
		 */
		if (samp.y < (s_Xres / 3))
			ptInputEvent->val = INPUT_VALUE_UP;
		else if (samp.y > (2 * s_Xres / 3))
			ptInputEvent->val = INPUT_VALUE_DOWN;
		else
			ptInputEvent->val = INPUT_VALUE_UNKONW;
		return 0;
	} else
		return -1;

	return 0;
}

static T_InputOpr s_tTouchScreenOpr = {
	.name 			= "touchscreen",
	.DeviceInit 	= TouchScreenDevInit,
	.DeviceExit 	= TouchScreenDevExit,
	.GetInputEvent  = TouchScreenGetInputEvent,
};

/**
 * @Description: touchscreen input設備初始化函數,供上層調用註冊設備
 * @return 成功:0
 */
int TouchScreenInit(void)
{
	return RegisterInputOpr(&s_tTouchScreenOpr);
}

四、修改

1、main.c文件修改

1.1 顯示支持信息部分

  • 修改前:

```c
	/* 顯示當前支持信息 */
	if (List)
	{
		printf("supported display:\n");
		ShowDispOpr();

		printf("supported font:\n");
		ShowFontOpr();

		printf("supported encoding:\n");
		ShowEncodingOpr();
		
		return 0;
	}
  • 修改後:
	/* 顯示當前支持信息 */
	if (List)
	{
		printf("supported display:\n");
		ShowDispOpr();

		printf("supported font:\n");
		ShowFontOpr();

		printf("supported encoding:\n");
		ShowEncodingOpr();

		printf("supported input:\n");
		ShowInputOpr();
		
		return 0;
	}

1.2 循環部分

  • 修改前:只支持串口標準輸入控制
/* 循環根據輸入信息進行對應操作
     * n:  顯示下一頁信息
     * u:  顯示上一頁信息
     * q:  退出程序
	 */
	while (1)
	{
		printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit: ");

		do {
			Opr = getchar();			
		} while ((Opr != 'n') && (Opr != 'u') && (Opr != 'q'));

		if (Opr == 'n')
			ShowNextPage();
		else if (Opr == 'u')
			ShowPrePage();			
		else 
			return 0;		
	}
  • 修改後:通過輪詢的方式,調用GetInputEvent(&InputEvent)不斷的判斷串口和LCD是否有輸入根據輸入的標誌位來進行相關操作
	/* 循環根據輸入信息進行對應操作
     * n:  顯示下一頁信息
     * u:  顯示上一頁信息
     * q:  退出程序
	 */
	printf("Enter 'n' to show next page, 'u' to show previous page, 'q' to exit\n\r");
	printf("Touch the upper third of the touch screen to show previous page\n\r");
	printf("Touch the bottom third of the touch screen to show next page\n\r");
	
	while (1)
	{
		if (GetInputEvent(&InputEvent) == 0) {
			if (InputEvent.val == INPUT_VALUE_DOWN)
				ShowNextPage();
			else if (InputEvent.val == INPUT_VALUE_UP)
				ShowPrePage();			
			else if (InputEvent.val == INPUT_VALUE_EXIT) {
				printf("\n\r");
				return 0;
			}
		}
	}

2、draw.c文件修改

2.1 初始化部分修改

  • 修改前:
int DrawInit(void)
{
	int error;
	
	/* 初始化 */
	error = DisplayInit();
	if (error) {
		printf("DisplayInit error!\n");
		return -1;
	}

	error = FontsInit();
	if (error) {
		printf("FontsInit error!\n");
		return -1;
	}

	error = EncodingInit();
	if (error) {
		printf("EncodingInit error!\n");
		return -1;
	}

	return 0;
}
  • 修改後:添加了input部分的初始化
int DrawInit(void)
{
	int error;
	
	/* 初始化 */
	error = DisplayInit();
	if (error) {
		printf("DisplayInit error!\n");
		return -1;
	}

	error = FontsInit();
	if (error) {
		printf("FontsInit error!\n");
		return -1;
	}

	error = EncodingInit();
	if (error) {
		printf("EncodingInit error!\n");
		return -1;
	}

	error = InputInit();
	if (error) {
		printf("InputInit error!\n");
		return -1;
	}

	return 0;
}

3、Makefile修改

3.1 /input目錄下Makefile

# 子目錄Makefile

obj-y += input_manager.o
obj-y += stdin.o
obj-y += touchscreen.o

3.2 頂層目錄Makefile

  • 添加庫:
LDFLAGS := -lm -lfreetype -lts
  • 添加input目錄:
obj-y += input/
  • 完整文件:
# 頂層目錄Makefile
#
# 目的:
#	1、每個子目錄都會建立一個Makefile,包含當前目錄下.c文件.h文件
# 	2、頂層目錄下


# 交叉編譯工具鏈
CROSS_COMPILE = arm-linux-

# 定義一些變量 $(CROSS_COMPILE):取出該變量的值 $(CROSS_COMPILE)gcc->arm-linux-gcc
AS		= $(CROSS_COMPILE)as		
LD		= $(CROSS_COMPILE)ld
CC		= $(CROSS_COMPILE)gcc
CPP		= $(CC) -E
AR		= $(CROSS_COMPILE)ar
NM		= $(CROSS_COMPILE)nm

STRIP		= $(CROSS_COMPILE)strip
OBJCOPY		= $(CROSS_COMPILE)objcopy
OBJDUMP		= $(CROSS_COMPILE)objdump

# 取出變量的值
export 	AS LD CC CPP AR NM
export STRIP OBJCOPY OBJDUMP

# := 		簡單擴展型變量的值是一次掃描永遠使用
# CFLAGS	方便您利用隱含規則指定編譯C語言源程序的旗標
# -Wall 	幫助列出所有警告信息
# -02		多優化一些,除了涉及空間和速度交換的優化選項,執行幾乎所有的優化工作
# -g		可以認爲它是缺省推薦的選項	
CFLAGS := -Wall -O2 -g

# 把當前目錄下的include目錄下指定爲系統目錄
# +=		爲已經定以過的變量的值追加更多的文本
# -I		指定搜尋包含makefile文件的路徑
# $(shell pwd)  執行shell命令的pwd命令,獲取執行命令的結果
CFLAGS += -I $(shell pwd)/include

# LDFLAGS	用於調用linker(‘ld’)的編譯器的額外標誌
# -l		連接庫的搜尋目錄 -lm調用math庫 -lfreetype調用freetype庫
LDFLAGS := -lm -lfreetype -lts

# 取出變量的值
export 	CFLAGS LDFLAGS

TOPDIR := $(shell pwd)
export TOPDIR

# TARGET	目標變量
# := 		簡單擴展型變量的值是一次掃描永遠使使用
TARGET := show_file

obj-y += main.o
obj-y += display/
obj-y += draw/
obj-y += encoding/
obj-y += fonts/
obj-y += input/

# 規則		arm-linux-gcc -lm -lfreetype -o show_file built-in.o
all : 
	make -C ./ -f $(TOPDIR)/Makefile.build
	$(CC) $(LDFLAGS) -o $(TARGET) built-in.o
	
# clean 	刪除所有make正常創建的文件
# 規則		找到所有make創建出來.o文件進行刪除
#			刪除show_file
clean :
	rm -f $(shell find -name "*.o")
	rm -f $(TARGET)
	
# distclean 刪除所有正常創建的文件
#			配置文件或爲編譯正常創建的準備文件,甚至makefile文件自身不能創建的文件
# 規則		找到所有mkae創建出來的.o文件進行刪除
#			刪除show_file
distclean:
	rm -f $(shell find -name "*.o")
	rm -f $(shell find -name "*.d")
	rm -f $(TARGET)

五、編譯與運行

1、編譯

執行make,得到可執行文件show_file
在這裏插入圖片描述

2、運行

由於使用到觸摸屏,需要調用tslib庫來進行校準

  • 執行./show_file -l,顯示出當前支持的設備
    在這裏插入圖片描述
  • 執行./shoe_file -s 16 -h HZK16 -f ./MSYH.TTF hz.txt
    通過串口輸入,可以立刻根據輸入的值進行操作,不需要回車鍵後才進行操作;
    通過觸摸屏輸入點擊屏幕的上1/3部分進行上頁顯示點擊屏幕的下1/3部分進行下頁顯示在這裏插入圖片描述
    在這裏插入圖片描述
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章