//gui.c

#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <windows.h>

/* main.c에 정의된 Todo 자료구조와 전역 데이터 */
#define MAX_TODO 100

typedef struct Todo {
    char text[200];
    int completed;
} Todo;

extern Todo todos[MAX_TODO];
extern int todoCount;

/* GUI 위젯 */
static GtkWidget* time_label;
static GtkWidget* cpu_label;
static GtkWidget* weather_label;

static GtkWidget* memo_view;
static GtkWidget* todo_entry;
static GtkWidget* todo_list;
static GtkWidget* status_label;

/* CPU 측정용 이전 값 */
static ULONGLONG previous_idle = 0;
static ULONGLONG previous_kernel = 0;
static ULONGLONG previous_user = 0;
static int cpu_initialized = 0;

/* 함수 선언 */
static void update_time(void);
static gboolean update_cpu(gpointer data);
static void update_todo_list(void);
static void show_status(const char* message);

static void on_add_todo(GtkButton* button, gpointer data);
static void on_complete_todo(GtkButton* button, gpointer data);
static void on_save_memo(GtkButton* button, gpointer data);
static void on_load_memo(GtkButton* button, gpointer data);


static gboolean update_time_timer(gpointer data)
{
    (void)data;

    update_time();

    return G_SOURCE_CONTINUE;
}

/* ==============================
   상태 메시지
   ============================== */

static void show_status(const char* message)
{
    gtk_label_set_text(GTK_LABEL(status_label), message);
}


/* ==============================
   현재 시간
   ============================== */

static void update_time(void)
{
    time_t now = time(NULL);
    struct tm* local = localtime(&now);
    char buffer[128];

    if (local == NULL) {
        gtk_label_set_text(
            GTK_LABEL(time_label),
            "시간을 가져올 수 없습니다."
        );
        return;
    }

    snprintf(
        buffer, sizeof(buffer),
        "%04d-%02d-%02d  %02d:%02d:%02d",
        local->tm_year + 1900,
        local->tm_mon + 1,
        local->tm_mday,
        local->tm_hour,
        local->tm_min,
        local->tm_sec
    );

    gtk_label_set_text(GTK_LABEL(time_label), buffer);
}


/* ==============================
   CPU 사용률
   1초마다 비차단 방식으로 갱신
   ============================== */

static gboolean update_cpu(gpointer data)
{
    FILETIME idle_ft, kernel_ft, user_ft;

    ULONGLONG idle, kernel, user;
    ULONGLONG total_delta, idle_delta;
    double usage;
    char buffer[100];

    (void)data;

    if (!GetSystemTimes(&idle_ft, &kernel_ft, &user_ft)) {
        gtk_label_set_text(
            GTK_LABEL(cpu_label),
            "CPU 정보를 가져올 수 없습니다."
        );
        return G_SOURCE_CONTINUE;
    }

    idle =
        ((ULONGLONG)idle_ft.dwHighDateTime << 32)
        | idle_ft.dwLowDateTime;

    kernel =
        ((ULONGLONG)kernel_ft.dwHighDateTime << 32)
        | kernel_ft.dwLowDateTime;

    user =
        ((ULONGLONG)user_ft.dwHighDateTime << 32)
        | user_ft.dwLowDateTime;

    if (!cpu_initialized) {
        previous_idle = idle;
        previous_kernel = kernel;
        previous_user = user;
        cpu_initialized = 1;

        gtk_label_set_text(
            GTK_LABEL(cpu_label),
            "측정 중..."
        );

        return G_SOURCE_CONTINUE;
    }

    total_delta =
        (kernel - previous_kernel)
        + (user - previous_user);

    idle_delta = idle - previous_idle;

    previous_idle = idle;
    previous_kernel = kernel;
    previous_user = user;

    if (total_delta == 0) {
        return G_SOURCE_CONTINUE;
    }

    usage = 100.0 *
        (double)(total_delta - idle_delta)
        / (double)total_delta;

    snprintf(
        buffer, sizeof(buffer),
        "%.1f%%", usage
    );

    gtk_label_set_text(GTK_LABEL(cpu_label), buffer);

    return G_SOURCE_CONTINUE;
}


/* ==============================
   Todo 목록 갱신
   ============================== */

static void update_todo_list(void)
{
    GtkWidget* child;

    /* 기존 목록 위젯 제거 */
    while ((child = gtk_widget_get_first_child(todo_list)) != NULL) {
        gtk_list_box_remove(GTK_LIST_BOX(todo_list), child);
    }

    for (int i = 0; i < todoCount; i++) {
        char text[300];

        snprintf(
            text, sizeof(text),
            "%s %s",
            todos[i].completed ? "[완료]" : "[미완료]",
            todos[i].text
        );

        GtkWidget* label = gtk_label_new(text);

        gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
        gtk_widget_set_margin_start(label, 8);
        gtk_widget_set_margin_end(label, 8);
        gtk_widget_set_margin_top(label, 6);
        gtk_widget_set_margin_bottom(label, 6);

        gtk_list_box_append(GTK_LIST_BOX(todo_list), label);
    }
}


/* ==============================
   Todo 추가
   ============================== */

static void on_add_todo(GtkButton* button, gpointer data)
{
    const char* text;

    (void)button;
    (void)data;

    text = gtk_editable_get_text(
        GTK_EDITABLE(todo_entry)
    );

    if (text[0] == '\0') {
        show_status("할 일을 입력해 주세요.");
        return;
    }

    if (todoCount >= MAX_TODO) {
        show_status("Todo 목록이 가득 찼습니다.");
        return;
    }

    snprintf(
        todos[todoCount].text,
        sizeof(todos[todoCount].text),
        "%s", text
    );

    todos[todoCount].completed = 0;
    todoCount++;

    gtk_editable_set_text(
        GTK_EDITABLE(todo_entry), ""
    );

    update_todo_list();
    show_status("Todo를 추가했습니다.");
}


/* ==============================
   선택한 Todo 완료
   ============================== */

static void on_complete_todo(
    GtkButton* button,
    gpointer data
)
{
    GtkListBoxRow* row;
    int index;

    (void)button;
    (void)data;

    row = gtk_list_box_get_selected_row(
        GTK_LIST_BOX(todo_list)
    );

    if (row == NULL) {
        show_status("완료할 Todo를 먼저 선택하세요.");
        return;
    }

    index = gtk_list_box_row_get_index(row);

    if (index < 0 || index >= todoCount) {
        show_status("잘못된 Todo입니다.");
        return;
    }

    todos[index].completed = 1;

    update_todo_list();
    show_status("Todo를 완료했습니다.");
}


/* ==============================
   메모 저장
   ============================== */

static void on_save_memo(
    GtkButton* button,
    gpointer data
)
{
    GtkTextBuffer* buffer;
    GtkTextIter start, end;
    char* text;
    FILE* fp;

    (void)button;
    (void)data;

    buffer = gtk_text_view_get_buffer(
        GTK_TEXT_VIEW(memo_view)
    );

    gtk_text_buffer_get_bounds(buffer, &start, &end);

    text = gtk_text_buffer_get_text(
        buffer, &start, &end, FALSE
    );

    fp = fopen("memo.txt", "w");

    if (fp == NULL) {
        show_status("메모 파일을 저장할 수 없습니다.");
        g_free(text);
        return;
    }

    if (fputs(text, fp) == EOF) {
        fclose(fp);
        show_status("메모 저장에 실패했습니다.");
        g_free(text);
        return;
    }

    fclose(fp);
    g_free(text);

    show_status("메모를 저장했습니다.");
}


/* ==============================
   메모 읽기
   ============================== */

static void on_load_memo(
    GtkButton* button,
    gpointer data
)
{
    FILE* fp;
    char buffer[1024];
    GtkTextBuffer* text_buffer;

    (void)button;
    (void)data;

    fp = fopen("memo.txt", "r");

    if (fp == NULL) {
        show_status("저장된 메모가 없습니다.");
        return;
    }

    text_buffer = gtk_text_view_get_buffer(
        GTK_TEXT_VIEW(memo_view)
    );

    gtk_text_buffer_set_text(text_buffer, "", 0);

    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        GtkTextIter end;

        gtk_text_buffer_get_end_iter(text_buffer, &end);

        gtk_text_buffer_insert(
            text_buffer, &end,
            buffer, -1
        );
    }

    fclose(fp);

    show_status("메모를 불러왔습니다.");
}


/* ==============================
   GTK 위젯 생성 보조 함수
   ============================== */

static GtkWidget* create_section_title(const char* text)
{
    GtkWidget* label = gtk_label_new(text);

    gtk_label_set_xalign(GTK_LABEL(label), 0.0f);
    gtk_widget_add_css_class(label, "heading");

    return label;
}


/* ==============================
   GTK 화면 구성
   ============================== */

static void activate(
    GtkApplication* app,
    gpointer user_data
)
{
    GtkWidget* window;
    GtkWidget* root;
    GtkWidget* grid;

    GtkWidget* time_box;
    GtkWidget* system_box;
    GtkWidget* todo_box;
    GtkWidget* memo_box;

    GtkWidget* todo_buttons;
    GtkWidget* memo_buttons;
    GtkWidget* scrolled;
    GtkWidget* button;

    (void)user_data;

    window = gtk_application_window_new(app);

    gtk_window_set_title(
        GTK_WINDOW(window), "PC Dashboard"
    );

    gtk_window_set_default_size(
        GTK_WINDOW(window), 850, 600
    );

    root = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);

    gtk_widget_set_margin_top(root, 18);
    gtk_widget_set_margin_bottom(root, 18);
    gtk_widget_set_margin_start(root, 18);
    gtk_widget_set_margin_end(root, 18);

    gtk_window_set_child(GTK_WINDOW(window), root);

    /* 상단 제목 */
    GtkWidget* title = gtk_label_new("PC Dashboard");

    gtk_widget_add_css_class(title, "title-1");
    gtk_label_set_xalign(GTK_LABEL(title), 0.0f);

    gtk_box_append(GTK_BOX(root), title);

    /* 시간 및 시스템 정보 */
    grid = gtk_grid_new();

    gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
    gtk_grid_set_column_spacing(GTK_GRID(grid), 24);

    gtk_box_append(GTK_BOX(root), grid);

    time_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
    system_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);

    gtk_grid_attach(GTK_GRID(grid), time_box, 0, 0, 1, 1);
    gtk_grid_attach(GTK_GRID(grid), system_box, 1, 0, 1, 1);

    gtk_box_append(
        GTK_BOX(time_box),
        create_section_title("현재 시간")
    );

    time_label = gtk_label_new("");
    gtk_label_set_xalign(GTK_LABEL(time_label), 0.0f);
    gtk_box_append(GTK_BOX(time_box), time_label);

    gtk_box_append(
        GTK_BOX(system_box),
        create_section_title("CPU 사용률")
    );

    cpu_label = gtk_label_new("측정 중...");
    gtk_label_set_xalign(GTK_LABEL(cpu_label), 0.0f);
    gtk_box_append(GTK_BOX(system_box), cpu_label);

    /* 날씨 */
    weather_label = gtk_label_new(
        "서울 · 맑음 · 22°C · 습도 45%\n"
        "테스트 데이터"
    );

    gtk_label_set_xalign(GTK_LABEL(weather_label), 0.0f);

    gtk_box_append(
        GTK_BOX(system_box), weather_label
    );

    /* 콘텐츠 영역 */
    grid = gtk_grid_new();

    gtk_grid_set_row_spacing(GTK_GRID(grid), 16);
    gtk_grid_set_column_spacing(GTK_GRID(grid), 20);

    gtk_widget_set_vexpand(grid, TRUE);
    gtk_box_append(GTK_BOX(root), grid);

    /* Todo 영역 */
    todo_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);

    gtk_widget_set_hexpand(todo_box, TRUE);
    gtk_widget_set_vexpand(todo_box, TRUE);

    gtk_grid_attach(GTK_GRID(grid), todo_box, 0, 0, 1, 1);

    gtk_box_append(
        GTK_BOX(todo_box),
        create_section_title("Todo List")
    );

    todo_entry = gtk_entry_new();

    gtk_entry_set_placeholder_text(
        GTK_ENTRY(todo_entry), "할 일을 입력하세요"
    );

    gtk_box_append(GTK_BOX(todo_box), todo_entry);

    button = gtk_button_new_with_label("Todo 추가");

    g_signal_connect(
        button, "clicked",
        G_CALLBACK(on_add_todo), NULL
    );

    gtk_box_append(GTK_BOX(todo_box), button);

    scrolled = gtk_scrolled_window_new();

    gtk_widget_set_vexpand(scrolled, TRUE);

    todo_list = gtk_list_box_new();

    gtk_list_box_set_selection_mode(
        GTK_LIST_BOX(todo_list),
        GTK_SELECTION_SINGLE
    );

    gtk_scrolled_window_set_child(
        GTK_SCROLLED_WINDOW(scrolled), todo_list
    );

    gtk_box_append(GTK_BOX(todo_box), scrolled);

    button = gtk_button_new_with_label("선택한 Todo 완료");

    g_signal_connect(
        button, "clicked",
        G_CALLBACK(on_complete_todo), NULL
    );

    gtk_box_append(GTK_BOX(todo_box), button);

    /* 메모 영역 */
    memo_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 8);

    gtk_widget_set_hexpand(memo_box, TRUE);
    gtk_widget_set_vexpand(memo_box, TRUE);

    gtk_grid_attach(GTK_GRID(grid), memo_box, 1, 0, 1, 1);

    gtk_box_append(
        GTK_BOX(memo_box),
        create_section_title("메모장")
    );

    scrolled = gtk_scrolled_window_new();

    gtk_widget_set_vexpand(scrolled, TRUE);

    memo_view = gtk_text_view_new();

    gtk_text_view_set_wrap_mode(
        GTK_TEXT_VIEW(memo_view),
        GTK_WRAP_WORD_CHAR
    );

    gtk_scrolled_window_set_child(
        GTK_SCROLLED_WINDOW(scrolled), memo_view
    );

    gtk_box_append(GTK_BOX(memo_box), scrolled);

    memo_buttons = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);

    gtk_box_append(GTK_BOX(memo_box), memo_buttons);

    button = gtk_button_new_with_label("메모 저장");

    g_signal_connect(
        button, "clicked",
        G_CALLBACK(on_save_memo), NULL
    );

    gtk_box_append(GTK_BOX(memo_buttons), button);

    button = gtk_button_new_with_label("메모 불러오기");

    g_signal_connect(
        button, "clicked",
        G_CALLBACK(on_load_memo), NULL
    );

    gtk_box_append(GTK_BOX(memo_buttons), button);

    /* 상태 표시 */
    status_label = gtk_label_new("준비되었습니다.");

    gtk_label_set_xalign(GTK_LABEL(status_label), 0.0f);

    gtk_box_append(GTK_BOX(root), status_label);

    /* 초기 화면 갱신 및 주기적 갱신 */
    update_time();
    update_todo_list();

    /*g_timeout_add_seconds(1, (GSourceFunc)update_time, NULL);
    g_timeout_add_seconds(1, update_cpu, NULL);*/
    g_timeout_add_seconds(1, update_time_timer, NULL);
    g_timeout_add_seconds(1, update_cpu, NULL);

    gtk_window_present(GTK_WINDOW(window));
}


/* ==============================
   프로그램 시작점
   ============================== */

int main(int argc, char** argv)
{
    GtkApplication* app;
    int status;

    app = gtk_application_new(
        "com.example.pcdashboard",
        G_APPLICATION_DEFAULT_FLAGS
    );

    g_signal_connect(
        app, "activate",
        G_CALLBACK(activate), NULL
    );

    status = g_application_run(
        G_APPLICATION(app), argc, argv
    );

    g_object_unref(app);

    return status;
}

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: