Elsaの技術日記(徒然なるままに)

主に自分で作ったアプリとかの報告・日記を記載

MENU

go言語でのTwitter画像データ収集

先日、go言語でTwitterへの自動投稿とデータを収集する方法についてまとめました。
elsammit-beginnerblg.hatenablog.com

今回はTwitterに投稿されている画像を収集する方法についてまとめておこうと思います!!
f:id:Elsammit:20210116185559j:plain

Twitter APIが使用できることを前提に記載しますので、Twitter APIの登録等、事前準備が必要な方は、
https://elsammit-beginnerblg.hatenablog.com/entry/2021/01/15/215142
へアクセスの上、準備を完了させてください。


■画像付きtweetの抽出と画像URL収集
まずはTwitter APIにて画像付きtweetの抽出と画像のURLを収集していきます。
コードはこちらのようになります。

	v := url.Values{}
	v.Set("count", "20")

	searchResult, _ := api.GetSearch("検索ワード exclude:retweets filter:images", v)
	tweets := searchResult.Statuses
	for _, tweet := range tweets {
		for _, entityMedia := range tweet.ExtendedEntities.Media {
			mediaRawUrl := entityMedia.Media_url_https
			Println(mediaRawUrl)
		}
	}

前回のTwitter抽出と異なるのは、

tweets := searchResult.Statuses
for _, entityMedia := range tweet.ExtendedEntities.Media {
	mediaRawUrl := entityMedia.Media_url_https
	Println(mediaRawUrl)
}

となります。
文字列を収集するコードは

for _, tweet := range searchResult.Statuses {
	Println(tweet.CreatedAt, tweet.Text)
}

でした。
tweetsに格納されたデータから、

entityMedia := range tweet.ExtendedEntities.Media

のようにメディアデータのみを抽出し、

mediaRawUrl := entityMedia.Media_url_https
||<[
にて、メディアデータからURLを抽出しています。


■URLから画像ダウンロードと保存
Twitterからの画像データ収集とは少しずれますが、画像をダウンロードするコードについても載せておきます。
コードはこちら。
fileUrlは画像が格納されたURL、
downloadPath は画像を格納するファイルパス、
となります。
>|go|
func DownloadFile(fileUrl, downloadPath string) string {
	response, err := http.Get(fileUrl)
	if err != nil {
		return "failed to request"
	}
	defer func() {
		cerr := response.Body.Close()
		if cerr == nil {
			return
		}
	}()

	file, err := os.Create(downloadPath)
	if err != nil {
		return "Create Error"
	}

	_, aerr := io.Copy(file, response.Body)
	if aerr != nil {
		return "failed to write download"
	}
	return "ok"
}

実施していることは、
こちらのコードにてGet requestを行い、
responseデータを収集。

response, err := http.Get(fileUrl)
if err != nil {
	return "failed to request"
}
defer func() {
	cerr := response.Body.Close()
	if cerr == nil {
		return
	}
}()

そして、Create関数にてパスで指定されたファイルを生成。

	file, err := os.Create(downloadPath)
	if err != nil {
		return "Create Error"
	}

最後にresponseデータからBody(今回は画像データ)を先ほど作成した画像ファイルにコピー。

	_, aerr := io.Copy(file, response.Body)
	if aerr != nil {
		return "failed to write download"
	}

■(参考)画像収集コードを載せておきます。
先ほどのコードを組み合わせてこちらのようなコードを作成しました。

func main() {

	api := GetTwitterApi()

	v := url.Values{}
	v.Set("count", "20")

	searchResult, _ := api.GetSearch("検索ワード exclude:retweets filter:images", v)
	tweets := searchResult.Statuses
	image := "./dir/image.jpg"
	i := 0
	for _, tweet := range tweets {
		for _, entityMedia := range tweet.ExtendedEntities.Media {
			mediaRawUrl := entityMedia.Media_url_https
			Println(mediaRawUrl)
			image = "./dir/image_" + strconv.Itoa(i) + ".jpg"
			ret := DownloadFile(mediaRawUrl, image)
			println(ret)
			time.Sleep(time.Millisecond * 500)
			i = i + 1
		}
	}
}

※GetTwitterApi()は前回の投稿を参考。

Twitterの投稿データを
./dir配下に、image_0.jpg~image_N.jpg(Nはツイッターから収集できた画像データ数)
の名前で保存していきます。
短期間にTwitterにアクセスしてしまうとTwitter社に迷惑がかかるので、

time.Sleep(time.Millisecond * 500)

にて、定期的に待ちを入れました。

■最後に
今回はTwitterから画像データの収集方法についてまとめました。
Twitterは最新トレンドなどのデータが豊富なので、データ解析とかに流用できますね。
後でやってみようかな?🤔

■参考
https://qiita.com/mpppk/items/dc1743fc8b3aa3116e70

go言語でのTwitter自動投稿とデータ収集

今回はgo言語でTwitterへ自動投稿やデータ収集に対する方法について載せておこうと思います!!

最近、ブログやSNSに自動投稿するためのAPIの使い方が知りたくて調査してきました。
今回はTwitterに対して投稿したデータの閲覧やブログへの自動投稿手段についてまとめたいと思います。

プログラミング言語は題名の通り、go言語を用います。



Twitter API登録手順

Twitterへ自動投稿するためにはTwitter APIを用いる必要があります。
Twitter APIを用いるためにはTwitter開発アカウントへの登録が必要になります。

Twitter開発アカウントの登録はこちらの記事をご参考ください。
qiita.com

■認証・動作確認

では、認証動作の確認を行っていきます。
まずはTwitter APIを利用するためのライブラリをダウンロードしておきます。

go get github.com/ChimeraCoder/anaconda

そして、先ほどのAPI登録時に得られた「Consumer API Keys」、「アクセストークン情報」を用いてこちらのようなコードを作成します。

package main

import (
	"github.com/ChimeraCoder/anaconda"
)

func main() {
	anaconda.NewTwitterApiWithCredentials(AccessToken, AccessTokenSecret, ConsumerKey, ConsumerSecret)
}

こちらを実行し、エラーなく返ってくれば認証はOKです!!

Twitterデータ収集

では実際にTwitterのタイムラインからデータを収集します。
こちらが全体のコードです。

package main

import (
	. "fmt"
	"github.com/ChimeraCoder/anaconda"
	"io"
	"net/http"
	"net/url"
	"os"
)

func GetTwitterApi() *anaconda.TwitterApi {
	anaconda.SetConsumerKey("ConsumerKey")
	anaconda.SetConsumerSecret("ConsumerSecretKey")
	return anaconda.NewTwitterApi("APIKey", "APISecretKey")
}

func main() {

	api := GetTwitterApi()
	v := url.Values{}
	v.Set("count", "20") ←抽出データ.

	searchResult, _ := api.GetSearch("検索文字列 exclude:retweets filter:images", v)
	tweets := searchResult.Statuses

	for _, tweet := range searchResult.Statuses {
		Println("=======================================")
		Println(tweet.CreatedAt, tweet.Text)

	}
}

こちらにて、Twitterの認証を行います。

api := GetTwitterApi()

GetTwitterApi関数はこちらのように認証を行っております。

func GetTwitterApi() *anaconda.TwitterApi {
	anaconda.SetConsumerKey("ConsumerKey")
	anaconda.SetConsumerSecret("ConsumerSecretKey")
	return anaconda.NewTwitterApi("APIKey", "APISecretKey")
}

そして、

	v := url.Values{}
	v.Set("count", "20") ←抽出データ.

	searchResult, _ := api.GetSearch("検索文字列 exclude:retweets filter:images", v)
	tweets := searchResult.Statuses

にて、抽出するデータ数や検索する文字列等の設定を行い、twitterのタイムラインからデータを収集します。
最後にこちらにて収集したデータをログ出力。

	for _, tweet := range searchResult.Statuses {
		Println(tweet.CreatedAt, tweet.Text)
	}

ついでに、、、

searchResult, _ := api.GetSearch("検索文字列 exclude:retweets filter:images", v)

tweets, err := api.GetHomeTimeline(v)

に置き換えると自分の最新タイムラインデータの取得が行えます。

Twitter自動投稿

先ほどのデータ収集とは反対にこちらから投稿するコードを書いてみます。
全体のコードはこちらです。

package main

import (
	"github.com/ChimeraCoder/anaconda"
	"net/http"
	"io"
	"os"
)

func GetTwitterApi() *anaconda.TwitterApi {
	anaconda.SetConsumerKey("ConsumerKey")
	anaconda.SetConsumerSecret("ConsumerSecretKey")
	return anaconda.NewTwitterApi("APIKey", "APISecretKey")
}

func main() {

	api := GetTwitterApi()

	sendText := "すいません。Testです"
	tweet_ret, err := api.PostTweet(sendText, nil)
	if err != nil {
		panic(err)
	}
	Println(tweet_ret.Text)
}

先ほどのデータ取集と異なる点は下記です。

	sendText := "すいません。Testです"
	tweet_ret, err := api.PostTweet(sendText, nil)
	if err != nil {
		panic(err)
	}
	Println(tweet_ret.Text)

sendTextへ投稿したいメッセーを記載。

tweet_ret, err := api.PostTweet(sendText, nil)

にてTweetします。

■最後に

認証のためのアクセスキーを取得する部分が結構面倒ですが、そこさえクリアすれば簡単にデータ収集や投稿が行えました!!
自分で作成するWebアプリにも利用しようかな??🤔



gtkmmでのキーボード入力(キーイベント検知)

前回までgtkmmについてまとめてきました。
elsammit-beginnerblg.hatenablog.com
elsammit-beginnerblg.hatenablog.com
elsammit-beginnerblg.hatenablog.com

今回はgtkmmでキーイベントを検知し、
キーボードからの入力を可能にする方法をまとめていきます!!



■実は、、、

gtkmmにはキーイベントを検知するためのAPIが用意されておりません。。。泣
このため、gtkmmでのGUIにてキーボード入力に対応するためには、、
別途キーイベントを検知することが出来る処理を自前で用意する必要があります。

さらにさらに、、、
Windowsの場合、キーイベントはkbhit関数(最新は_kbhit ??)があり、こちらのAPIを用いれば簡単に実装出来ますが、
Linuxの場合、Windowsのようなkbhit関数は用意されていません。。泣

■キーボードイベント

よって、Linuxにてgtkmmでキーイベントを検知したい場合には自前でkbhit関数のような処理を実装する必要があります!!
ではLinuxでのkbhit関数を実装していきたいのですが、すでに実装された方がいらっしゃったので、こちらを流用させて頂こうと思います!!
参考にしたサイトはこちらになります↓↓
https://hotnews8.net/programming/tricky-code/c-code03

こちらのkbhit関数ですが、windowsと同様に
キーイベントを検知しない場合には0が返ってきて、
何かしらのキーイベントを検知した場合、検知したキー番号を返す関数になっております。

■kbhit関数を用いたgtkmmのキーボード入力

では本題のgtkmmでのキーボード入力です。

実装したコードはこんな感じになります。

#include <iostream>
#include <gtkmm.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <termios.h>
#include <fcntl.h>
#include <thread>

class MainWin : public Gtk::Window {
    Gtk::Button *btnClick;
    Gtk::Label *selectlabel1, *selectlabel2, *selectlabel3;
    Glib::RefPtr<Gtk::Builder> builder;

public:
    MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
    virtual ~MainWin();
    void SetLabel(int keyEvent);
};


MainWin *mainWin = nullptr;
int select_flg = 0;

MainWin::MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade) :
    Gtk::Window(cobject), builder(refGlade) {
    this->builder->get_widget("selectlabel1", this->selectlabel1);
    this->builder->get_widget("selectlabel2", this->selectlabel2);
    this->builder->get_widget("selectlabel3", this->selectlabel3);
}

MainWin::~MainWin(){
}

int kbhit(void)
{
    struct termios oldt, newt;
    int ch;
    int oldf;

    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);

    ch = getchar();

    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);

    if (ch != EOF)
    {
        ungetc(ch, stdin);
        return 1;
    }

    return 0;
}

void KeyBoardInterupt(){
    printf("Call KeyBoardInterupt \n");
    printf("test is %d \n",mainWin->test);
	int Keyevent;
	while(1)
	{
		if(kbhit()){
			Keyevent = getchar();
			mainWin->SetLabel(Keyevent);
		}
        sleep(1);
	}
}

void MainWin::SetLabel(int keyEvent){
    printf("select_flg:%d \n",select_flg);
	if(keyEvent == 66){
		switch(select_flg){
			case 0:
				this->selectlabel1->set_text("");
				this->selectlabel2->set_text(">");
				select_flg++;
				break;
			case 1:
				this->selectlabel2->set_text("");
				this->selectlabel3->set_text(">");
				select_flg++;
				break;
			case 2:
				this->selectlabel3->set_text("");
				this->selectlabel1->set_text(">");
				select_flg = 0;
				break;
			default:
				break;
		}
    }
}

int main(int argc, char** argv) {
    Gtk::Main app(argc, argv);

    Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("MainWIndow.glade"); 

     builder->get_widget_derived("MainWindow",mainWin); 
     std::thread KeyEventThread = std::thread(KeyBoardInterupt);
     Gtk::Main::run(*mainWin);

     KeyEventThread.join();
}

ちょっと長くなってしまっていますが、、、
やっていることは結構簡単です。
まず、画面表示とは別にKeyBoardInteruptスレッドを生成。

std::thread KeyEventThread = std::thread(KeyBoardInterupt);

KeyBoardInteruptスレッドは、

void KeyBoardInterupt(){
	while(1)
	{
		if(kbhit()){
			Keyevent = getchar();
			mainWin->SetLabel(Keyevent);
		}
        sleep(1);
	}
}

で定義していまして、入力されたきーイベントをkbhitで1秒ごとに検知する処理になっております。
もしキーイベントが発行されれば、キーイベント番号を引数にSetLabel(Keyevent)をコールします。
SetLabel(Keyevent)は、

void MainWin::SetLabel(int keyEvent){
    printf("select_flg:%d \n",select_flg);
	if(keyEvent == 66){
		switch(select_flg){
			case 0:
				this->selectlabel1->set_text("");
				this->selectlabel2->set_text(">");
				select_flg++;
				break;
			case 1:
				this->selectlabel2->set_text("");
				this->selectlabel3->set_text(">");
				select_flg++;
				break;
			case 2:
				this->selectlabel3->set_text("");
				this->selectlabel1->set_text(">");
				select_flg = 0;
				break;
			default:
				break;
		}
    }
}

で、keyEvent == 66の場合(今回はキーボードの↓ボタン)に>マークを動かす処理にしております。

■実際に動かしてみる

では、先ほどのコードを動かしてみます。
結果はこんな感じになります。
(キーボードを押しておりますが取れていない。。。)
f:id:Elsammit:20210113210050g:plain

■最後に

今回はgtkmmでのキーイベントについてまとめてみました。
今までの内容を組み焦れば、大体のGUI制御はできますね!!
これからアプリでも作ってみようかな🤔



go言語でのコマンドライン引数扱い

今回はgo言語でのコマンドラインの引数の使い方についてまとめていきたいと思います!!

そういえば使い方が分からなかったので調べてみました。
備忘録としてまとめておきます。



■環境

go言語バージョン:go1.11.5 linux/amd64

■コマンド使用にあたり事前準備

go言語でコマンドラインの引数を用いるにあたり、
flagパッケージを用いるのが良いとのこと。
まずはこちらのコマンドでインストール。

go get flag

flagパッケージを用いた引数指定の方法と、型をオプションで指定する方法と指定しない方法の2つがあります。
それぞれ書いていきたいと思います。

■型を指定しない場合

まずはコードです。

package main

import (
	"flag"
	"fmt"
)

func main() {
	flag.Parse()
	args := flag.Args()
	fmt.Println(args)
}

では、コードの解説です。

flag.Parse()
args := flag.Args()

にて引数を取得。

fmt.Println(args)

で引数をそのまま出力。以上です。

こちらのように、コマンドを実行すると、

go run command.go 10 Hello false

こちらのような結果になります。

[10 Hello false]

配列で格納されているので、

fmt.Println(args[0])

として、先ほどのコマンドを実行すると、

10

が得られます。

■型を指定する場合

まずはコードです。

package main

import (
	"flag"
	"fmt"
)

func main() {
	var (
		i = flag.Int("int", 0, "int flag")
		s = flag.String("str", "default", "string flag")
		b = flag.Bool("bool", false, "bool flag")
	)
	flag.Parse()
	fmt.Println(*i, *s, *b)
}

先ほどの型指定しない場合と比較して、

	var (
		i = flag.Int("int", 0, "int flag")
		s = flag.String("str", "default", "string flag")
		b = flag.Bool("bool", false, "bool flag")
	)

が異なります。

こちらのコードですが、

flag.型("コマンド実行時のオプション名","デフォルト値","型")

となっております。

こちらのように、コマンドを実行すると、

go run command.go -int 10 -str Hello -bool false

こちらのような結果になります。

10 Hello true

先ほど解説しました通り、

  • intでint型の変数、-strで文字列、-boolでフラグ

になります。

もし引数を与えない、要するに

go run command.go

と実行すると

0 default false

となります。
先ほどコードでデフォルト指定した値が得られていることが分かります。

では次に、

go run command.go -Int 10 -str Hello -bool false

というように定義していないオプションを指定します。
すると、

Usage of /tmp/go-build574500599/b001/exe/command:
  -bool
        bool flag
  -int int
        int flag
  -str string
        string flag (default "default")
exit status 2

といったようにオプションエラーになります。

最後に、

go run command.go -int a -str Hello -bool false

といったように型と異なる変数を与えた場合、

invalid value "a" for flag -int: strconv.ParseInt: parsing "a": invalid syntax
Usage of /tmp/go-build025687152/b001/exe/command:
  -bool
        bool flag
  -int int
        int flag
  -str string
        string flag (default "default")
exit status 2

というように、エラーが発生します。

■最後に

今回はgo言語のコマンドラインで引数を与える方法をまとめてみました。
flag使えば簡単に出来るので、用途によって使っていきたいと思います。



gtkmmでのボタン色変更

引き続きgtkmmについて書いていきたいと思います!!

今回はgtkmmでのボタン色の変更です。
前回記事に記載したGladeではボタン色の変更までできなかったので、別の方法が必要になります。
こちらの方法についてまとめていきたいと思います!!



■ボタン色の変更

色を変更したいボタンへの色設定はこちらのようなコードになります。

    this->builder->get_widget("btnClick", this->btnClick);
	
    Glib::RefPtr<Gtk::CssProvider> css_provider = Gtk::CssProvider::create();
    css_provider->load_from_data(
    "button {background-image: image(black);}\
     button:hover {background-image: image(green);}\
     button:active {background-image: image(brown);}");

    this->btnClick->get_style_context()->add_provider(
    css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);

    this->btnClick->override_color(Gdk::RGBA("white"), Gtk::STATE_FLAG_NORMAL);

ボタン色を変更させるためにはcssの変更が必要になります。
cssの設定は下記変数を用いて実施していきます。

Glib::RefPtr<Gtk::CssProvider> css_provider = Gtk::CssProvider::create();

buttonに対するcssはこちらのように設定すればOKです。

css_provider->load_from_data(
    "button {background-image: image(black);}\
     button:hover {background-image: image(green);}\
     button:active {background-image: image(brown);}");
);

そして、用意したcssを色変更したいボタンへ設定していきます。
今回はボタン名をbtnClickとしたので、

    this->btnClick->get_style_context()->add_provider(
    css_provider, GTK_STYLE_PROVIDER_PRIORITY_USER);

となります。

今回はボタン色を黒色に設定したので、ボタンの文字色は白に設定してみました。

this->btnClick->override_color(Gdk::RGBA("white"), Gtk::STATE_FLAG_NORMAL);

■実際に動かしてみる

ボタン色がデフォルト設定の場合にはこちらのような色になっております。
f:id:Elsammit:20210108205252g:plain

では、今回の設定でアプリを動かしてみます!!
動作させるとこちらのようになります。
f:id:Elsammit:20210110192356g:plain

ボタン押下前は黒色、
マウスをボタン上に持っていくと緑、
押下中は茶色、
になっているのが分かるかと思います。
先ほどcssで定義した、

css_provider->load_from_data(
    "button {background-image: image(black);}\
     button:hover {background-image: image(green);}\
     button:active {background-image: image(brown);}");

■(おまけ)window色の変更

windowの背景色を変更する場合にはこちらのようにすればOKです。
cssを設定する必要はないです。

window名.override_background_color(Gdk::RGBA("black"));

■最後に

文字色やwindowの背景色などはoverrideで設定できるのですが、なぜかボタンだけ色変更するAPI用意されていなかったんですよね。
しかも情報があまりなかった。。。
ですので、後で困らないようにまとめてみました!!



gtkmmでの画像表示と動画表示

前回に引き続きgtkmmを用いたGUIアプリで開発についてです。

前回はgtkmmでボタンやlabelを表示した簡単なアプリを作成しました。
elsammit-beginnerblg.hatenablog.com

今回はこちらのGUIアプリに画像や動画を表示させてみたいと思います。

っといっても、すでにサンプルを公開されている方がいらっしゃいましたのでこちらを利用していこうと思います。



サンプルソースの取得と環境構築

まずはサンプルソースであるこちらをgithubからダウンロードしていきます。
https://github.com/doleron/gtk3-opencv3-ux-sampling

git clone https://github.com/doleron/gtk3-opencv3-ux-sampling

成功すれば gtk3-opencv3-ux-sampling という名前のフォルダが生成されるかと思います。
次に、

mkdir gtk3-opencv3-ux-sampling/build
cd gtk3-opencv3-ux-sampling/build
cmake ..
make

を実行してサンプルコードをビルドしていきます。

OpenCVコード修正

先ほどのmakeでエラーなく完了すればよいのですが、
こちらのようにOpenCV系のエラーが発生する場合があります。

camera_image_window.cpp:49:35: error: ‘CV_BGR2RGB’ was not declared in this scope; did you mean ‘CV_RGB’?
   49 |     cv::cvtColor(frameBGR, frame, CV_BGR2RGB);
camera_image_window.cpp:68:14: error: ‘CV_CAP_PROP_FRAME_WIDTH’ was not declared in this scope
   68 |   camera.set(CV_CAP_PROP_FRAME_WIDTH, 640);

どうやら、CV_BGR2RGBやCV_CAP_PROP_FRAME_WIDTHといった定義は古いらしく、
最新のOpenCVでは異なる定義を利用されているようです!!

このため、camera_image_window.cppのコードをこちらのように修正していきます。

#49行目.
cv::cvtColor(frameBGR, frame, cv::COLOR_BGR2RGB);
#68行目~70行目
camera.set(cv::CAP_PROP_FRAME_WIDTH, 640);
camera.set(cv::CAP_PROP_FRAME_HEIGHT, 480);
camera.set(cv::CAP_PROP_FPS, 30);

これで再度

make

を実行すれば成功するかと思います。

■実行してみる

こちらのサンプルコードには画像表示と動画表示の2つが用意されています。

それぞれこちらのように実行すればよいです。

●画像表示

./gtk3_opencv3_ux single-image 画像ファイルパス

●カメラキャプチャ表示

./gtk3_opencv3_ux cam-grabber ビデオデバイス

実行した結果はそれぞれこんな感じです。

●画像表示
f:id:Elsammit:20210109105210p:plain

●カメラキャプチャ表示
f:id:Elsammit:20210109105428g:plain

mp4などの動画表示を行いたい場合には、
camera_image_window.cpp内の

bool result = camera.open(cameraIndex);

を、

bool result = camera.open(ビデオパス);

に置き換えてからmake頂ければOKです。

■最後に

GUIアプリでの動画再生や画像表示について行いました。
ソースの解説は実施していませんでしたが、ざっくりした内容としては、
GUIアプリ表示とカメラキャプチャを別スレッドで動かし、
カメラキャプチャスレッドにてframeバッファへキャプチャ画像を書き込み、
GUIアプリ表示にてframeバッファを逐次表示している形でした。



Linux GUI gtkmm導入とgladeとの連携

今年一発目の技術ですが、Linux GUIアプリであるgtkmmの導入手順とGladeとの連携についてまとめたいと思います!!
一発目にしては少し地味😅



gtkmmの導入

下記コマンドを実行すればOKです。

 sudo apt-get install libgtkmm-3.0-dev

gtkmmでのGUI実装

では簡単なGUIアプリを作成していきます。
コードはこちら。

#include <gtkmm.h>

class MainWin : public Gtk::Window {
    Gtk::Button btnClick;
    Gtk::Label label;
    Gtk::VBox Vbox;

public:
    MainWin();

private:
    void Clickbtn();
};

MainWin::MainWin() : btnClick("Click Here") {
    label.set_text("Hello");
    
    Vbox.pack_start(label);
    Vbox.pack_start(btnClick);

    btnClick.signal_clicked().connect(sigc::mem_fun(*this, &MainWin::Clickbtn));
    
    add(Vbox);
    show_all_children();
}

void MainWin::Clickbtn() {
    label.set_text("World");
}    

int main(int argc, char *argv[]) {
    Gtk::Main kit(argc, argv);
    MainWin mainwin;

    mainwin.set_title("TestMain");

    Gtk::Main::run(mainwin);
    return 0;
}

こちらのクラスにて使用するwidgetを定義。

class MainWin : public Gtk::Window {
    Gtk::Button btnClick;
    Gtk::Label label;
    Gtk::VBox Vbox;

public:
    MainWin();

private:
    void Clickbtn();
};

widgetの配置やボタン押下時の割り込み処理などは先ほど定義したクラスのコンストラクタに定義していきます。

MainWin::MainWin() : btnClick("Click Here") {
    label.set_text("Hello");
    
    Vbox.pack_start(label);
    Vbox.pack_start(btnClick);

    btnClick.signal_clicked().connect(sigc::mem_fun(*this, &MainWin::Clickbtn));
    
    add(Vbox);
    show_all_children();
}

こちらの通り、ボタン押下時の割り込みを定義できるのですが、

btnClick.signal_clicked().connect(sigc::mem_fun(*this, &MainWin::Clickbtn));
>||

割り込みイベントの関数として、
>||
void MainWin::Clickbtn() {
    label.set_text("World");
}    

を定義しています。

gtkmmでのGUIアプリ動作確認

先ほどのコードを実行するとこちらのような動作となります。
f:id:Elsammit:20210104231506g:plain

■gladeとgtkmmの連携

では次にgladeを用いてGUIの作成を行っていきます。
gladeとは、GUI作成のためのインターフェースデザイナになります。
以前ご紹介しました、monodevelopと同じようなライブラリになります。
elsammit-beginnerblg.hatenablog.com


gladeを利用するためにまずはインストールを行います。

sudo apt install glade

インストールが完了したら、こちらのコマンドを実行してみてください。

glade

インストールが行えていれば、こちらの画面が表示されるかと思います。
f:id:Elsammit:20210108190427p:plain

その後、左上の新規作成ボタンを押下すると、
作成用画面が表示されます。
上段に、"トップレベル"、"コンテナー"、"Control"、"Display"
といったボタンがあります。
それぞれ、こちらのようにwidgetが格納されております。
・トップレベル:window系
・コンテナー:gridやboxなど、labelやボタンをセットするためのwidgetが格納
・Control:ボタンやスライドバーなどのwidgetが格納
・Display:labelなどの表示のみを行うwidgetが格納

それぞれ、windowやbox、label、buttonを組み合わせて今回はこちらのような画面を作成。
f:id:Elsammit:20210108190813p:plain

その後、gtkmmと連携させるコードはこちらになります。

#include <iostream>
#include <gtkmm.h>

class MainWin : public Gtk::Window {
    Gtk::Button *btnClick;
    Gtk::Label *label1;
    Glib::RefPtr<Gtk::Builder> builder;

public:
    MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade);
    void Clickbtn();

};

MainWin::MainWin(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& refGlade) :
    Gtk::Window(cobject), builder(refGlade) {
	
    this->builder->get_widget("label1", this->label1);
    this->builder->get_widget("btnClick", this->btnClick);
	this->btnClick->signal_clicked().connect(sigc::mem_fun(*this, &MainWin::Clickbtn));
	
}

void MainWin::Clickbtn() {
    label1->set_text("World");
}  

int main(int argc, char** argv) {
    
    MainWin *mainWin = nullptr; 

    Gtk::Main app(argc, argv);
	Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("MainWIndow.glade"); 

	builder->get_widget_derived("MainWindow", mainWin); 
	Gtk::Main::run(*mainWin);
}

先ほどのgtkmm単体の場合と大きく異なるのはこちらの2つになります。

    this->builder->get_widget("label1", this->label1);
    this->builder->get_widget("btnClick", this->btnClick);
	Glib::RefPtr<Gtk::Builder> builder = Gtk::Builder::create_from_file("MainWIndow.glade"); 
	builder->get_widget_derived("MainWindow", mainWin); 

Gtk::builderという変数を用意し、こちらから先ほど作成したglade内のwindowを読み出します。
window内のwidget群は

this->builder->get_widget("widget名", クラス定義変数);

で読み出しが可能になります。

■gladeとgtkmmで連携したアプリを実行

ビルドですが、先ほどのgtkmm単体と同様。

g++ ファイル名.cpp -o 実行ファイル `pkg-config gtkmm-3.0 --cflags --libs`

でOKです。

実行ファイルを実行すると、
f:id:Elsammit:20210108205252g:plain
といったようにGUIアプリが表示されます。

■最後に

GUIアプリとしてgtkmmを利用してみました。
また、インターフェースデザイナであるgladeも触ってみました。
感想しては、gtk#よりは使いやすいですし、情報もたくさんあるのでこちらの方が使い勝手が良いかな?と感じました。
もう少し触ってみて、またブログ作成出来たらいいな!!と考えております。