Tampilkan Menu & Dialog Simpan dengan ImGui – Contoh Aplikasi Notepad TXT

Di pelajaran kali ini, kita akan belajar cara menampilkan menu. Kita akan membuat program mirip notepad TXT dengan menu bar di bagian atas yang memiliki submenu Simpan. Saat klik menu Simpan, semua teks yang diketik di kotak teks bawah akan disimpan ke berkas. Kita juga akan membahas masalah karakter rusak dan cara konfigurasi font. Jika tidak disetel, huruf selain bahasa Inggris akan muncul berupa tanda tanya.

Saat proses simpan berjalan, dialog simpan berkas sistem akan muncul. Pengguna bisa memilih folder penyimpanan, memasukkan nama berkas lalu menyelesaikan proses simpan.

Berikut tangkapan layar program saat berjalan:

Fungsinya mirip notepad bawaan Windows, ada fitur edit dasar dan simpan, ukuran file hasil kompilasi sangat ringan.

Jika dikompilasi mode Release, ukuran executable hanya 645kb.

Mari kita lihat kode lengkap program:

#pragma comment(linker, "/SUBSYSTEM:windows /ENTRY:mainCRTStartup")
#pragma execution_character_set("utf-8")

#include <GLFW/glfw3.h>
#include "imgui.h"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_opengl3.h"
#include <cstdio>
#include <string>
#include <fstream>
#include <windows.h>
#include <io.h>
#include <commdlg.h>  // Header wajib untuk dialog berkas

// Batas maksimal buffer teks
const int TEXT_BUFFER_MAX = 4096;
std::string editor_text;

// Fungsi simpan teks ke file dengan UTF8 BOM
// Tulis isi teks ke jalur file yang ditentukan
bool SaveTextToFile(const char* filepath, const std::string& text)
{
    std::ofstream out_file(filepath, std::ios::out | std::ios::binary);
    if (!out_file.is_open())
        return false;
    const unsigned char utf8_bom[] = { 0xEF, 0xBB, 0xBF };
    out_file.write((char*)utf8_bom, sizeof(utf8_bom));
    out_file << text.c_str(); // Hanya cetak teks valid sampai karakter null
    out_file.close();
    return true;
}

// Tampilkan dialog simpan file sistem, kembalikan jalur file yang dipilih pengguna
bool ShowSaveFileDialog(char* out_path, DWORD out_size)
{
    OPENFILENAMEA ofn;   // Pakai versi A ANSI
    ZeroMemory(&ofn, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = nullptr;
    ofn.lpstrFilter = "Berkas Teks\0*.txt\0Semua Berkas\0*.*\0"; // Filter txt atau semua format
    ofn.lpstrFile = out_path;
    ofn.nMaxFile = out_size;
    ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_OVERWRITEPROMPT;
    ofn.lpstrDefExt = "txt";

    return GetSaveFileNameA(&ofn);   // Panggil fungsi dialog ANSI
}

// Atur font untuk memperbaiki karakter rusak tanda tanya
// Isi jalur font yang sesuai lingkunganmu atau pakai font default sistem
void SetupFont(ImGuiIO& io)
{
    // Jalur font umum untuk Windows (Hanzi)
    const char* font_path = "C:/Windows/Fonts/msyh.ttc"; // Microsoft YaHei
    float font_size = 18.0f;

    // Muat rentang karakter Hanzi sederhana yang sering dipakai
    ImVector<ImWchar> ranges;
    ImFontGlyphRangesBuilder builder;
    builder.AddRanges(io.Fonts->GetGlyphRangesChineseSimplifiedCommon());
    builder.BuildRanges(&ranges);

    // Muat font kustom
    io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, ranges.Data);

    // Jika butuh dukungan multibahasa bisa tambahkan kode berikut:
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesJapanese());
    // io.Fonts->AddFontFromFileTTF(font_path, font_size, nullptr, io.Fonts->GetGlyphRangesKorean());
}


int main()
{
    SetConsoleOutputCP(65001);
    // Penting: Perluas buffer dan isi semua dengan nol untuk bersihkan sampah memori, perbaiki tanda tanya ????
    editor_text.resize(TEXT_BUFFER_MAX, '\0');

    if (!glfwInit())
    {
        printf("Inisialisasi GLFW gagal\n");
        return -1;
    }

    const char* glsl_version = "#version 330";
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    GLFWwindow* window = glfwCreateWindow(800, 600, "Editor Catatan Teks", nullptr, nullptr);
    if (!window)
    {
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSwapInterval(1);

    IMGUI_CHECKVERSION();
    ImGui::CreateContext();
    ImGuiIO& io = ImGui::GetIO();
    ImGui::StyleColorsLight();

    SetupFont(io); // Muat font agar karakter tidak rusak

    ImGui_ImplGlfw_InitForOpenGL(window, true);
    ImGui_ImplOpenGL3_Init(glsl_version);

    bool show_save_success = false;
    bool show_save_fail = false;

	char path[MAX_PATH] = "";// Buffer simpan jalur file

    while (!glfwWindowShouldClose(window))
    {
        glfwPollEvents();

        // Shortcut Ctrl+S simpan
        if (io.KeyCtrl && ImGui::IsKeyPressed(ImGuiKey_S))
        {
            if (ShowSaveFileDialog(path, MAX_PATH))
            {
                if (SaveTextToFile(path, editor_text))
                    show_save_success = true;
                else
                    show_save_fail = true;
            }
        }

        ImGui_ImplOpenGL3_NewFrame();
        ImGui_ImplGlfw_NewFrame();
        ImGui::NewFrame();

        ImGui::SetNextWindowPos(ImVec2(0, 0));
        ImGui::SetNextWindowSize(io.DisplaySize);

        ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration
            | ImGuiWindowFlags_NoMove
            | ImGuiWindowFlags_NoResize
            | ImGuiWindowFlags_NoSavedSettings
            | ImGuiWindowFlags_MenuBar;   // Flag wajib agar menu bar tampil

        ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
        ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);

        ImGui::Begin("Editor Catatan Teks", nullptr, window_flags);

        // Gambar menu bar bagian atas
        if (ImGui::BeginMenuBar())
        {
            if (ImGui::BeginMenu("Berkas"))
            {
				if (ImGui::MenuItem("Simpan", "Ctrl+S")) // Item simpan: teks tampilan + petunjuk shortcut
                {
					// Logikanya sama tombol yang kita pelajari sebelumnya, kode berjalan saat menu diklik
					if (ShowSaveFileDialog(path, MAX_PATH)) // Munculkan dialog simpan
                    {
						if (SaveTextToFile(path, editor_text)) // Jalankan simpan file
                            show_save_success = true; // Tandai sukses untuk tampilkan popup nanti
                        else
                            show_save_fail = true;
                    }
                }

                ImGui::Separator();

                if (ImGui::MenuItem("Keluar", "Esc")) // Menu tutup program
                {
					glfwSetWindowShouldClose(window, true); // Pasang flag tutup untuk akhiri program
                }
                ImGui::EndMenu();
            }
            ImGui::EndMenuBar();
        }

        ImGui::Spacing();

        ImVec2 multiline_size = ImGui::GetContentRegionAvail();
        // Solusi utama tanda tanya: gunakan batas buffer tetap, memori sudah diisi nol tanpa sampah
        ImGui::InputTextMultiline("##text_editor", &editor_text[0], TEXT_BUFFER_MAX, multiline_size);

	// Pemicu popup berdasarkan hasil simpan
        if (show_save_success)
        {
            // Baris ini tidak gambar jendela ataupun jalankan BeginPopupModal
	    ImGui::OpenPopup("Simpan Berhasil");// Beri tanda internal ImGui untuk buka modal, string sebagai ID unik popup
            show_save_success = false; 
        }
        if (show_save_fail)
        {
			ImGui::OpenPopup("Gagal Simpan");// Tanda untuk popup gagal
            show_save_fail = false;
        }

	// Gambar modal sukses simpan
        if (ImGui::BeginPopupModal("Simpan Berhasil", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
        {
            ImGui::Text("Teks berhasil disimpan ke file!");
            if (ImGui::Button("OK", ImVec2(120, 0)))
                ImGui::CloseCurrentPopup();
            ImGui::EndPopup();
        }

	// Gambar modal gagal simpan
        if (ImGui::BeginPopupModal("Gagal Simpan", nullptr, ImGuiWindowFlags_AlwaysAutoResize))
        {
            ImGui::Text("Jalur tidak valid atau tidak punya izin tulis!");
            if (ImGui::Button("OK", ImVec2(120, 0)))
                ImGui::CloseCurrentPopup();
            ImGui::EndPopup();
        }

        ImGui::End();
        ImGui::PopStyleVar(2);

        ImGui::Render();
        int width, height;
        glfwGetFramebufferSize(window, &width, &height);
        glViewport(0, 0, width, height);
        glClearColor(0.12f, 0.12f, 0.12f, 1.f);
        glClear(GL_COLOR_BUFFER_BIT);
        ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
                                

Tinggalkan Balasan

Alamat email Anda tidak akan dipublikasikan. Ruas yang wajib ditandai *