retireSakiの日記

たぶん引退した?ソフトウエアエンジニアのブログ

GTKmm (3.0) と glade によるアプリ (3) - config データの取得/保存 と 最終位置の保存/表示

 今回は、前回の"GTKmm (3.0) と glade によるアプリ (2) - アプリケーションの多重起動の防止"で作成したプログラム
retiresaki.hatenablog.com
をベースに、
  (1) config(ini)データの取得と保存機能
  (2) アプリの最終ポジションの保存と起動時にその位置で表示
 の2つの機能を付けます。

 まずは、実際に動作させたときの画像から
f:id:retireSaki:20181013181043p:plain
・左側がconfigデータが無いとき
・右側がconfigデータが存在するとき
のそれぞれの画像です。

 アプリの位置だけでなく、各panedもポジションも保存しています。
アプリの位置に関しては、(現時点のGTKバージョンで) メインウィンドウではなくタイトルバーフレームサイズも含めたアプリケーションの位置を使っています。

 修正したソースは、MainWindow.cc と MainWindow.h だけです。

 今回は先にソースから

作成した MainWindow.cc

#include "MainWindow.h"

using namespace std;

extern Glib::RefPtr<Gtk::Builder> refBuilder;

void MainWindow::setInit_display()
{
    cstrInitPathFile = create_InitPath();
    load_InitDatas();

    // get widget
    refBuilder->get_widget("btn1", m_btn1);
    refBuilder->get_widget("paned1", m_paned1);
    refBuilder->get_widget("paned2", m_paned2);

    // connect signals
    m_btn1->signal_clicked().connect(
        sigc::mem_fun(*this, &MainWindow::on_btn1_clicked));

    this->signal_hide().connect(
        sigc::mem_fun(*this, &MainWindow::on_hide_window));

    // set window titles & sizes
    set_title("test paned");
    if (nConf_MainWindow_posx != -1*G_MAXINT && nConf_MainWindow_posy != -1*G_MAXINT)
        move(nConf_MainWindow_posx, nConf_MainWindow_posy);;
    set_default_size(nConf_MainWindow_width, nConf_MainWindow_height);

    // set paned position
    m_paned1->set_position(nConf_Paned1_position);
    m_paned2->set_position(nConf_Paned2_position);

}

/*************************************************
    ini functions
*************************************************/

// create ini-file pathfilename
gchar * MainWindow::create_InitPath()
{
    cstrInitPathFile = NULL;
    //g_autoptr(GError) error = NULL;
    GError *error = nullptr;

    // get application name
    const gchar *app_name;
    app_name = g_get_application_name();
    if (!app_name)
        app_name = g_get_prgname();

    // get config directory
    const gchar *user_config_dir = g_get_user_config_dir();
    gchar *pathfile = g_build_filename(user_config_dir, app_name, CONF_FILE, NULL);
    if (g_file_test(pathfile, G_FILE_TEST_EXISTS)) {
        return pathfile;
    }

    // check exists derectory
    if (!g_file_test(g_build_filename(user_config_dir, app_name, NULL), G_FILE_TEST_EXISTS)) {
        // create directory
        GFile *file = g_file_new_build_filename(user_config_dir, app_name,NULL);
        if (!g_file_make_directory(file, NULL, &error)){
            std::cout << "Error make application config directory :"
                      << error->message << std::endl;            
            g_clear_error(&error);
            return NULL;
        }
    }

    return pathfile;
}

// load ini-file
void MainWindow::load_InitDatas()
{
    // defalts
    nConf_MainWindow_posx=-1*G_MAXINT;
    nConf_MainWindow_posy=-1*G_MAXINT;
    nConf_MainWindow_width = 400;
    nConf_MainWindow_height = 300;
    nConf_Paned1_position = 150;
    nConf_Paned2_position = 50;

    if (cstrInitPathFile == NULL)
        return;

    // exists check key file
    if (!g_file_test(cstrInitPathFile, G_FILE_TEST_EXISTS))
        return;

    //g_autoptr(GError) error = NULL;
    GError *error = nullptr;
    g_autoptr(GKeyFile) key_file = g_key_file_new ();

    if (!g_key_file_load_from_file (key_file,
            cstrInitPathFile,
            G_KEY_FILE_KEEP_COMMENTS,
            &error)) {
        if (!g_error_matches (error, G_FILE_ERROR, G_FILE_ERROR_NOENT))
            std::cout << "Error loading key file :"
                      << error->message << std::endl;            
        else
            std::cout << "Error loading key file :"
                      << error->code << std::endl;            
        g_clear_error(&error);
        g_key_file_free(key_file);
        return;
    }

    int nvalue;

    // main window
    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "window_posx", &error);
    if (error == NULL)
        nConf_MainWindow_posx = nvalue;
    else
        g_clear_error(&error);

    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "window_posy", &error);
    if (error == NULL)
        nConf_MainWindow_posy = nvalue;
    else
        g_clear_error(&error);

    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "window_width", &error);
    if (error == NULL)
        nConf_MainWindow_width = nvalue;
    else
        g_clear_error(&error);

    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "window_height", &error);
    if (error == NULL)
        nConf_MainWindow_height = nvalue;
    else
        g_clear_error(&error);

    // paned1
    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "paned1_divider_position", &error);
    if (error == NULL)
        nConf_Paned1_position = nvalue;
    else
        g_clear_error(&error);

    // paned2
    error = NULL;
    nvalue = g_key_file_get_integer(key_file, "MAINWINDOW", "paned2_divider_position", &error);
    if (error == NULL)
        nConf_Paned2_position = nvalue;
    else
        g_clear_error(&error);

    g_key_file_free(key_file);
}

void MainWindow::save_InitDatas()
{
    if (cstrInitPathFile == NULL)
        return;

    // get application position & size
    Gdk::Rectangle rect;
    get_window()->get_frame_extents(rect);

    int int_val_mw_posx = rect.get_x();
    int int_val_mw_posy = rect.get_y();
    int int_val_mw_width = 0;
    int int_val_mw_height = 0;
    get_size(int_val_mw_width, int_val_mw_height);

    // get paneds position
    gint int_val_paned1 = m_paned1->get_position();
    gint int_val_paned2 = m_paned2->get_position();

    // set key values
    GError *error = nullptr;
    //g_autoptr(GError) error = NULL;
    g_autoptr(GKeyFile) key_file = g_key_file_new ();

    g_key_file_set_integer(key_file, "MAINWINDOW", "window_posx", int_val_mw_posx);
    g_key_file_set_integer(key_file, "MAINWINDOW", "window_posy", int_val_mw_posy);

    g_key_file_set_integer(key_file, "MAINWINDOW", "window_width", int_val_mw_width);
    g_key_file_set_integer(key_file, "MAINWINDOW", "window_height", int_val_mw_height);

    g_key_file_set_integer(key_file, "MAINWINDOW", "paned1_divider_position", int_val_paned1);
    g_key_file_set_integer(key_file, "MAINWINDOW", "paned2_divider_position", int_val_paned2);

    // write key file
    if (!g_key_file_save_to_file(key_file, cstrInitPathFile, &error)){
        std::cout << "Error saving key file :"
                  << error->message << std::endl;            
        g_clear_error(&error);
        return;
    }

    g_key_file_free(key_file);
}

/*************************************************
    events
*************************************************/

// btn1(close) event
void MainWindow::on_btn1_clicked()
{
    // confirm close
    Gtk::MessageDialog dialog(*this, "close this window ?", false,
                           Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
    dialog.set_title( "close this window" );
    dialog.set_default_response(Gtk::RESPONSE_OK);
    if (dialog.run()==Gtk::RESPONSE_CANCEL)
        return;

    hide();
}

1) config(ini) ファイル

1.1) config(ini) ファイルについて

 config(ini) ファイルを扱う前に、保存先のディレクトリーを確認し、存在していないときは作る必要があります。
これは、
 gchar * MainWindow::create_InitPath()
内で処理しています。この関数はメインウィンドウ生成時で、config(ini) ファイルを読み込む前に行う必要があります。

g_get_user_config_dir() を使ってconfigデータディレクトリーを取得します。
 ( ubuntu であれば、"/home/<user name>/.config" )
g_get_application_name() で得たアプリ名を使い、g_build_filename で各パスを1つのパスとして合成します。
結果、
 "/home/<user name>/.config/<application name>"
というパス文字列ができました。
これを使って
 (1) g_file_test() で存在チェック。あれば以下の処理を継続
 (2) g_file_new_build_filename() で先のパスの GFile* を作成
 (3) g_file_make_directory()ディレクトリーを作成
という手順で行っています。

1.2) メインウィンドウ生成時に config(ini) データを読み込む

 今回、config(ini)ファイル名は MainWindow.h 内で、以下のように定義しました。
#define CONF_FILE "application.conf"

 config(ini)の読み込み処理は、
 void MainWindow::load_InitDatas()
で行っています。

 基本的に通常のファイル処理と同様に
  (1) config(ini) ファイルの存在チェック
  (2) g_key_file_load_from_file config(ini) ファイルの読み込み
  (3) g_key_file_get_integer() などを使って、指定グループ、キーから該当する値を取得
  (4) config(ini) ファイルの開放
です。

 ソースコードと言語リファレンスを見れば、すぐに分かると思いますので詳細は省略します。


1.3) on_hide_window イベント時に config(ini) データを保存

 config(ini)の保存処理は、
  void MainWindow::save_InitDatas()
で行っています。

 アプリケーション終了(メインウィンドウ開放)時に保存したいので、on_hide_window イベント時に save_InitDatas() をcallします。

 保存処理は
  (1) g_key_file_new() で GKeyFile を作成
  (2) g_key_file_set_integer() などを使って、指定グループ、キーと該当する値をセット
  (3) g_key_file_save_to_file() で config(ini) データを保存
  (4) config(ini) ファイルの開放
です。

 これも、ソースコードと言語リファレンスを見れば、すぐに分かると思いますので詳細は省略します。

2) アプリケーション位置の保存と表示

2.1) アプリケーション位置の保存

 アプリケーション位置の保存は、save_InitDatas() で行っています。

Qtでもそうですが、普通に position() 関数を使うと、アプリケーションの位置ではなく、メインウィンドウの位置を取得してしまいます。
必要なのはアプリケーション(ルートウィンドウ)の位置で、メインウィンドウから見ると、タイトルバー、フレームサイズなどを差し引いた左の値となります。

そこで、
 get_frame_extents();
を使います。
引数は、Gdk::Rectangle を渡すことで、アプリケーションの左、上、幅、高さを取得できます。
この関数は、タイトルバー/ボーダー(存在する場合)を含むウィンドウのバウンディングボックスのサイズを取得します。なので、
 get_size();
でサイズを取得します。

あとは、その値を保存するだけです。


2.2) アプリケーションを最終保存位置で表示

 アプリケーション位置の取得は、load_InitDatas() で行い、その後
 move()
で最終位置にアプリケーションを移動させます。


最後に、全てのファイルを圧縮したファイル(zip)を載せます。

ソース一式

すべてのファイル(zip)をダウンロードする
www.mediafire.com


 簡単でしたね!
 何が大変かというと、使用する関数を調べることです。ある程度日本語されているといっても、情報量が多いのは英語のサイトです。
当ブログのように、最新版でいろいろな情報を発信することで、最終的に日本語の情報量も増えていきます。
皆さん、頑張ってくださいね。

Gtk+3入門

Gtk+3入門

注目記事

「Amazon.co.jpアソシエイト」または「[乙の名称を挿入]は、Amazon.co.jpを宣伝しリンクすることによってサイトが紹介料を獲得できる手段を提供することを目的に設定されたアフィリエイトプログラムである、Amazonアソシエイト・プログラムの参加者です。