#include "lisans_uretici_penceresi.hpp" #include #include #include // ShellExecuteW (üretilen lisansın klasörünü açmak için) #include #include #include #include #include #include #include #include #include #include #include #include "uygulama_baglam.hpp" #include "fiko/cekirdek/cekirdek.hpp" #include "fiko/cekirdek/kayit.hpp" #include "fiko/cekirdek/kripto.hpp" #include "fiko/cekirdek/lisans_dosya.hpp" #include "fiko/cekirdek/makine_kimlik.hpp" #include "fiko/core/temel.hpp" #include "fiko/ui/bilesen.hpp" #include "fiko/ui/bilesen_ek.hpp" #include "fiko/ui/pencere_cerceve.hpp" #include "fiko/ui/tema.hpp" #include "imgui.h" namespace fiko { namespace { constexpr i64 GUN = 86400; // ---- Küçük yardımcılar ---------------------------------------------------- std::string kirp(const std::string& s) { std::size_t a = 0, b = s.size(); while (a < b && (unsigned char)s[a] <= ' ') ++a; while (b > a && (unsigned char)s[b - 1] <= ' ') --b; return s.substr(a, b - a); } // ASCII küçültme (arama filtresi için yeterli; Türkçe harfler olduğu gibi kalır). std::string kucuk(std::string s) { for (char& c : s) c = (char)std::tolower((unsigned char)c); return s; } bool icerir(const std::string& metin, const std::string& kucukArama) { if (kucukArama.empty()) return true; return kucuk(metin).find(kucukArama) != std::string::npos; } i64 suanUnix() { return (i64)std::time(nullptr); } // Unix saniye → "20.07.2026 14:30". 0 = süresiz. std::string tarihMetni(i64 damga) { if (damga <= 0) return "süresiz"; std::tm t{}; const std::time_t z = (std::time_t)damga; if (localtime_s(&t, &z) != 0) return "geçersiz"; char b[64]; std::snprintf(b, sizeof b, "%02d.%02d.%04d %02d:%02d", t.tm_mday, t.tm_mon + 1, t.tm_year + 1900, t.tm_hour, t.tm_min); return b; } // Bitişe kalan gün (negatifse geçmiş). i64 kalanGun(i64 damga) { return damga <= 0 ? -1 : (damga - suanUnix()) / GUN; } // Metinden EN UZUN kesintisiz hex dizisini ayıklar. Anahtar dosyası "ozel=" // gibi etiketli yazılmış olabilir; körlemesine "hex olmayanı at" derse etiket // harfleri (a-f) hex'e karışır — bu yüzden dizi bazlı ayıklama. std::string enUzunHex(const std::string& metin) { std::string enIyi, guncel; for (char c : metin) { const bool hex = (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); if (hex) { guncel.push_back(c); } else { if (guncel.size() > enIyi.size()) enIyi = guncel; guncel.clear(); } } if (guncel.size() > enIyi.size()) enIyi = guncel; return enIyi; } bool dosyaOku(const std::string& yol, std::string& out) { std::ifstream f(yol, std::ios::binary); if (!f) return false; out.assign((std::istreambuf_iterator(f)), std::istreambuf_iterator()); return true; } bool dosyaYaz(const std::string& yol, const std::string& veri) { std::ofstream f(yol, std::ios::binary | std::ios::trunc); if (!f) return false; f.write(veri.data(), (std::streamsize)veri.size()); return (bool)f; } // Win32 dosya seçici (kaydet/aç). Boş dönüş = iptal. std::string dosyaSecUretici(bool kaydet, const wchar_t* filtre, const wchar_t* varsayilanUzanti) { wchar_t yol[MAX_PATH] = {}; OPENFILENAMEW ofn{}; ofn.lStructSize = sizeof(ofn); ofn.hwndOwner = GetActiveWindow(); ofn.lpstrFilter = filtre; ofn.lpstrFile = yol; ofn.nMaxFile = MAX_PATH; ofn.lpstrDefExt = varsayilanUzanti; ofn.Flags = kaydet ? (OFN_OVERWRITEPROMPT | OFN_PATHMUSTEXIST) : (OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST); const BOOL tamam = kaydet ? GetSaveFileNameW(&ofn) : GetOpenFileNameW(&ofn); if (!tamam) return {}; const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr); if (bayt <= 1) return {}; std::string s((std::size_t)bayt - 1, 0); WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr); return s; } void metinKopyala(char* hedef, std::size_t n, const std::string& kaynak) { const std::size_t k = std::min(kaynak.size(), n - 1); std::memcpy(hedef, kaynak.data(), k); hedef[k] = 0; } // UTF-8 → geniş dize (Win32 API çağrıları için). std::wstring genisle(const std::string& s) { if (s.empty()) return {}; const int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0); if (n <= 0) return {}; std::wstring w((std::size_t)n, L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), w.data(), n); return w; } // Dosyanın bulunduğu klasörü Explorer'da açar (dosya yoksa sessiz geçer). void klasoruAc(const std::string& dosya) { if (dosya.empty()) return; const std::size_t s = dosya.find_last_of("\\/"); if (s == std::string::npos) return; ShellExecuteW(nullptr, L"open", genisle(dosya.substr(0, s)).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } // "FKC--<4 hane>". Sıra RASTGELE DEĞİL — dakika sayacının son 4 hanesi; aynı // dakikada iki kez basılırsa aynı numara çıkar (bilinçli: kopya seri fark edilsin). std::string seriUret() { const i64 z = suanUnix(); std::tm t{}; const std::time_t zz = (std::time_t)z; const int yil = (localtime_s(&t, &zz) == 0) ? (t.tm_year + 1900) : 2026; char b[64]; std::snprintf(b, sizeof b, "FKC-%04d-%04d", yil, (int)((z / 60) % 10000)); return b; } // ---- Durum modeli --------------------------------------------------------- // ---- Serbest periyot yardımcıları ---------------------------------------- // Motor artık sabit dönem enum'u değil SANİYE tutuyor (LisansKota::periyot). // UI bunu "sayı + birim" ikilisi olarak gösterir; kaynak doğruluk saniyedir. // BIRIM_KOD dizisi ayrıştırıcı (kotaPeriyotCoz) ile BİREBİR aynı olmalı. constexpr u32 BIRIM_SN[] = {0u, 1u, 60u, 3600u, 86400u, 604800u, 2592000u, 31536000u}; const char* const BIRIM_ETIKET[] = {"ömür boyu", "sn", "dk", "saat", "gün", "hafta", "ay", "yıl"}; const char* const BIRIM_KOD[] = {"", "sn", "dk", "saat", "gun", "hafta", "ay", "yil"}; constexpr int BIRIM_SAYI = 8; // Saniye → (sayı, birim). Tam bölen EN BÜYÜK birim seçilir: 18000 → 5 saat, // 604800 → 1 hafta. Böylece gövdeye yazılan metin gidip gelse de aynı kalır. void periyotAyristir(u32 periyot, int& sayi, int& birim) { if (periyot == 0) { sayi = 1; birim = 0; return; } for (int i = BIRIM_SAYI - 1; i >= 1; --i) { if (periyot % BIRIM_SN[i] == 0) { sayi = (int)(periyot / BIRIM_SN[i]); birim = i; return; } } sayi = (int)periyot; birim = 1; } u32 periyotKur(int sayi, int birim) { if (birim <= 0 || birim >= BIRIM_SAYI) return 0; if (sayi < 1) sayi = 1; const u64 top = (u64)sayi * (u64)BIRIM_SN[birim]; return top > 0xFFFFFFFFull ? 0xFFFFFFFFu : (u32)top; } // Gövdeye yazılacak AYRIŞTIRILABİLİR periyot eki: 18000 → "5saat", 604800 → "hafta". // Sayı 1 ise atlanır — kotaPeriyotCoz sayı yoksa 1 varsayar (CLI yardımı da "/ay" yazar). std::string periyotKodu(u32 periyot) { if (periyot == 0) return ""; int sayi = 1, birim = 0; periyotAyristir(periyot, sayi, birim); return (sayi == 1 ? std::string() : std::to_string(sayi)) + BIRIM_KOD[birim]; } struct KotaSatir { char kimlik[96] = ""; int adet = 100; u32 periyot = 0; // saniye; 0 = ömür boyu (LisansKota::periyot ile birebir) }; struct PaketDurum { char kimlik[64] = ""; char ad[128] = ""; bool suresiz = true; i64 bitis = 0; std::vector komutlar; std::vector kotalar; }; struct UreticiDurum { // Sol sütun char arama[64] = ""; std::set secili; // seçili komut kimlikleri (joker dizgeler dahil) // Orta sütun std::vector paketler; int aktifPaket = -1; std::vector genelKomutlar; // paketsiz doğrudan komut hakkı std::vector genelKotalar; std::vector gruplar; // menü grubu whitelist bool genelAcik = false; // Sağ sütun char musteri[128] = ""; char seri[64] = ""; char urun[64] = "fikocad"; char seviye[64] = "pro"; bool kisitli = true; bool suresiz = true; i64 bitis = 0; char makineler[4096] = ""; int tolerans = 3; bool yapiKilidi = false; char yapiHash[96] = ""; char anahtarYol[MAX_PATH] = ""; char ciktiYol[MAX_PATH] = ""; // Varsayılan ŞİFRELİ (CLI ile aynı davranış). Açık = eski düz metin biçimi. bool duzMetin = false; // Kota ekleme kutucuğu (paket ve genel için ortak giriş alanı) char yeniKota[96] = ""; int yeniKotaAdet = 100; u32 yeniKotaPeriyot = 2592000u; // varsayılan 1 ay // Önizleme / doğrulama std::string onizleme; std::string dogrulamaOzet; int dogrulamaDurum = 0; // 0 yok · 1 iyi · 2 kötü bool ilkKurulum = false; // Sihirbaz kipi (varsayılan): aynı durumu adım adım doldurur. Uzman kipi (3 sütun) // AYNI veriyi gösterir — kip değiştirmek hiçbir alanı kaybettirmez. bool sihirbaz = true; int adim = 0; int sonSablon = -1; // -1 seçilmedi · -2 özel/boş · >=0 SABLONLAR indeksi bool sihirbazGelismis = false; bool sihirbazKota = false; }; UreticiDurum& durum() { static UreticiDurum d; if (!d.ilkKurulum) { d.ilkKurulum = true; // Varsayılan özel anahtar yolu: %USERPROFILE%\.fikoyt\lisans_ozel.key char* prof = nullptr; std::size_t n = 0; if (_dupenv_s(&prof, &n, "USERPROFILE") == 0 && prof) { metinKopyala(d.anahtarYol, sizeof d.anahtarYol, std::string(prof) + "\\.fikoyt\\lisans_ozel.key"); std::free(prof); } } return d; } // ---- Hazır şablonlar (VERİ-ODAKLI; tek tıkla makul lisans) ---------------- // Yeni satış paketi eklemek için AŞAĞIDAKİ diziye satır yazmak yeterli — kod yok. // Biçim: komut/kota listeleri "|" ile ayrılır, kota "kimlik:adet@periyotSaniye". // (periyot 0/eksik = ömür boyu.) struct SablonPaket { const char* kimlik = nullptr; const char* ad = nullptr; int gun = 0; // paket bitişi (0 = süresiz) const char* komutlar = nullptr; const char* kotalar = nullptr; }; struct Sablon { const char* etiket; const char* ipucu; const char* seviye; int gun; // lisans geneli bitiş (0 = süresiz) bool kisitli; SablonPaket paketler[2]; }; const Sablon SABLONLAR[] = { {"⏱ Deneme (30 gün)", "30 günlük deneme: modelleme + ölçüm serbest, CAM/döküm aylık kotalı.\n" "Seri no otomatik üretilir.", "deneme", 30, true, {{"deneme", "Deneme (30 Gün)", 30, "dosya.*|duzenle.*|gorunum.*|secim.*|olcum.*|geometri.*|sketch.*|feature.*", "cam.*:5@2592000|dokum.*:3@2592000"}, {}}}, {"📅 Yıllık tam", "1 yıllık tam sürüm: tüm komut aileleri açık, kota yok.", "pro", 365, true, {{"tam", "Tam Sürüm (Yıllık)", 365, "dosya.*|duzenle.*|gorunum.*|secim.*|arac.*|referans.*|geometri.*|sketch.*|feature.*|" "dm.*|boolean.*|yuzey.*|tel.*|olcum.*|pafta.*|cam.*|analiz.*|dokum.*|islem.*|makro.*", ""}, {}}}, {"👁 Sadece görüntüleyici", "Süresiz görüntüleyici: aç / gez / ölç. Modelleme, CAM ve döküm kapalı.", "goruntuleyici", 0, true, {{"goruntuleyici", "Görüntüleyici", 0, "dosya.ac|dosya.step_ice|dosya.webcad_ac|gorunum.*|secim.*|olcum.*", ""}, {}}}, }; constexpr int SABLON_SAYI = (int)(sizeof(SABLONLAR) / sizeof(SABLONLAR[0])); // "a|b|c" → parçalar (boş parçalar atılır). void ayirBoru(const char* metin, std::vector& cikti) { if (!metin) return; std::string p; for (const char* c = metin; *c; ++c) { if (*c == '|') { if (!kirp(p).empty()) cikti.push_back(kirp(p)); p.clear(); } else { p.push_back(*c); } } if (!kirp(p).empty()) cikti.push_back(kirp(p)); } // "cam.*:5@2592000" → KotaSatir. Kimlik joker içerebildiği için ':' SONDAN aranır. bool kotaCoz(const std::string& metin, KotaSatir& k) { const std::size_t ik = metin.rfind(':'); if (ik == std::string::npos || ik == 0) return false; const std::size_t ap = metin.find('@', ik); metinKopyala(k.kimlik, sizeof k.kimlik, kirp(metin.substr(0, ik))); const std::size_t son = (ap == std::string::npos) ? metin.size() : ap; k.adet = std::max(1, std::atoi(metin.substr(ik + 1, son - ik - 1).c_str())); k.periyot = (ap == std::string::npos) ? 0u : (u32)std::strtoul(metin.c_str() + ap + 1, nullptr, 10); return k.kimlik[0] != 0; } // Tüm içerik alanlarını sıfırlar (anahtar/çıktı yolu KORUNUR — makine ayarıdır). void durumBosalt(UreticiDurum& d) { d.paketler.clear(); d.aktifPaket = -1; d.genelKomutlar.clear(); d.genelKotalar.clear(); d.gruplar.clear(); d.secili.clear(); d.musteri[0] = 0; d.seri[0] = 0; metinKopyala(d.urun, sizeof d.urun, std::string("fikocad")); metinKopyala(d.seviye, sizeof d.seviye, std::string("pro")); d.kisitli = true; d.suresiz = true; d.bitis = 0; d.makineler[0] = 0; d.yapiKilidi = false; d.yapiHash[0] = 0; d.dogrulamaDurum = 0; d.dogrulamaOzet.clear(); } void sablonUygula(UreticiDurum& d, const Sablon& s) { durumBosalt(d); metinKopyala(d.seviye, sizeof d.seviye, std::string(s.seviye)); d.kisitli = s.kisitli; d.suresiz = (s.gun <= 0); d.bitis = d.suresiz ? 0 : suanUnix() + (i64)s.gun * GUN; metinKopyala(d.seri, sizeof d.seri, seriUret()); for (const SablonPaket& sp : s.paketler) { if (!sp.kimlik || !*sp.kimlik) continue; PaketDurum p; metinKopyala(p.kimlik, sizeof p.kimlik, std::string(sp.kimlik)); metinKopyala(p.ad, sizeof p.ad, std::string(sp.ad ? sp.ad : "")); p.suresiz = (sp.gun <= 0); p.bitis = p.suresiz ? 0 : suanUnix() + (i64)sp.gun * GUN; ayirBoru(sp.komutlar, p.komutlar); std::vector ham; ayirBoru(sp.kotalar, ham); for (const std::string& x : ham) { KotaSatir k; if (kotaCoz(x, k)) p.kotalar.push_back(k); } d.paketler.push_back(std::move(p)); } d.aktifPaket = d.paketler.empty() ? -1 : 0; } // Sihirbaz: şablon uygularken MÜŞTERİ KİMLİĞİNİ koru. durumBosalt müşteri/seri/ürünü // de siler; sihirbazda bunlar 1. adımda girilir, 2. adımda şablon seçilince silinmesi // kullanıcıyı geri gönderirdi. void kimlikKoruyarak(UreticiDurum& d, const Sablon* s) { const std::string mus = d.musteri, ser = d.seri, urn = d.urun; if (s) sablonUygula(d, *s); else durumBosalt(d); metinKopyala(d.musteri, sizeof d.musteri, mus); metinKopyala(d.seri, sizeof d.seri, ser.empty() ? seriUret() : ser); if (!kirp(urn).empty()) metinKopyala(d.urun, sizeof d.urun, urn); } // Sihirbaz süre adımı: lisans geneli bitişi kurar ve SÜRESİZ OLMAYAN paketleri ona // eşitler (şablon paketi 30 gün gelir, kullanıcı 1 yıl seçerse paket 30'da kalmasın). void sureUygula(UreticiDurum& d, int gun) { d.suresiz = (gun <= 0); d.bitis = d.suresiz ? 0 : suanUnix() + (i64)gun * GUN; for (PaketDurum& p : d.paketler) { p.suresiz = d.suresiz; p.bitis = d.bitis; } } // Üretim için ZORUNLU alan denetimi. nullptr = hazır, aksi halde eksik alan metni. const char* eksikAlan(const UreticiDurum& d) { if (kirp(d.musteri).empty()) return "Müşteri adı boş"; if (kirp(d.seri).empty()) return "Seri no boş — yanındaki 'Üret' düğmesi doldurur"; if (kirp(d.urun).empty()) return "Ürün adı boş (fikocad)"; if (kirp(d.anahtarYol).empty()) return "Özel anahtar (.key) dosya yolu boş"; if (d.kisitli && d.paketler.empty() && d.genelKomutlar.empty() && d.gruplar.empty()) return "Kısıtlı kipte en az bir paket / genel komut / grup gerekir"; return nullptr; } // ---- Komut kataloğu (gruplu, aranabilir; yalnız değişince kurulur) --------- struct KatalogOge { std::string kimlik, ad; }; struct KatalogGrup { std::string ad; std::string joker; // "geometri.*" — grubun tüm komutları aynı öneki paylaşıyorsa std::vector ogeler; }; struct Katalog { std::vector gruplar; std::size_t kaynakSayi = (std::size_t)-1; std::string arama = "\x01"; // imkânsız başlangıç → ilk karede kurulur int toplam = 0; }; // Katalog kare başına yeniden kurulmaz: kaynak liste boyu veya arama değişince. const Katalog& katalog(const MenuKaydi& menuler, const char* arama) { static Katalog k; const std::vector& kayit = menuler.tumKayit(); const std::string ar = kucuk(arama); if (k.kaynakSayi == kayit.size() && k.arama == ar) return k; k.kaynakSayi = kayit.size(); k.arama = ar; k.gruplar.clear(); k.toplam = 0; for (const MenuOge& o : kayit) { const std::string kimlik = komutKimligi(o); if (!(icerir(kimlik, ar) || icerir(o.ad, ar) || icerir(o.grup, ar))) continue; KatalogGrup* g = nullptr; for (KatalogGrup& x : k.gruplar) if (x.ad == o.grup) { g = &x; break; } if (!g) { k.gruplar.push_back(KatalogGrup{o.grup, "", {}}); g = &k.gruplar.back(); } g->ogeler.push_back(KatalogOge{kimlik, o.ad}); ++k.toplam; } // Grup jokeri: gruptaki TÜM komutlar aynı "önek." ile başlıyorsa "önek.*" önerilir. for (KatalogGrup& g : k.gruplar) { std::string onek; bool uyumlu = !g.ogeler.empty(); for (const KatalogOge& o : g.ogeler) { const std::size_t nokta = o.kimlik.find('.'); if (nokta == std::string::npos) { uyumlu = false; break; } const std::string p = o.kimlik.substr(0, nokta); if (onek.empty()) onek = p; else if (onek != p) { uyumlu = false; break; } } if (uyumlu && !onek.empty()) g.joker = onek + ".*"; } return k; } // ---- Ortak küçük bileşenler ---------------------------------------------- void rozetSayi(const char* etiket, int sayi, const ImVec4& renk) { char b[96]; std::snprintf(b, sizeof b, "%s %d", etiket, sayi); ui::rozet(b, renk); } void ipucu(const char* metin) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) ImGui::SetTooltip("%s", metin); } void soluk(const char* fmt, ...) { va_list ap; va_start(ap, fmt); ImGui::PushStyleColor(ImGuiCol_Text, ui::tema().metinSoluk); ImGui::TextV(fmt, ap); ImGui::PopStyleColor(); va_end(ap); } // Bitiş düzenleyici: süresiz onayı + hızlı ekleme düğmeleri + ham unix damgası. // Lisans geneli ve her paket AYNI arayüzü kullanır (tek kalıp, tek hata yüzeyi). bool bitisDuzenle(const char* kimlik, bool& suresiz, i64& bitis) { bool degisti = false; ImGui::PushID(kimlik); if (ImGui::Checkbox("Süresiz", &suresiz)) { if (suresiz) bitis = 0; else if (bitis <= 0) bitis = suanUnix() + 365 * GUN; degisti = true; } ipucu("İşaretli = bitiş yazılmaz (bitis=0). Kaldırınca aşağıdaki tarih geçerlidir."); if (!suresiz) { // Hızlı ekleme: geçmiş bir tarihe eklemek anlamsız → taban en az "şimdi". auto ekle = [&](const char* etiket, i64 gun) { if (ImGui::SmallButton(etiket)) { bitis = std::max(bitis, suanUnix()) + gun * GUN; degisti = true; } }; ekle("+7g", 7); ImGui::SameLine(); ekle("+1ay", 30); ImGui::SameLine(); ekle("+3ay", 90); ImGui::SameLine(); ekle("+1yıl", 365); ImGui::SameLine(); if (ImGui::SmallButton("Bugün")) { bitis = suanUnix(); degisti = true; } ipucu("Bitişi şu ana çeker (test için: lisans anında dolmuş sayılır)."); int damga = (int)bitis; // 2038'e kadar güvenli; UI girişi int ImGui::SetNextItemWidth(140.0f); if (ImGui::InputInt("unix", &damga, 0, 0)) { bitis = damga < 0 ? 0 : (i64)damga; degisti = true; } ipucu("Ham unix saniye damgası (bitis= alanına aynen yazılır)."); ImGui::SameLine(); const i64 kg = kalanGun(bitis); const ImVec4 renk = kg < 0 ? ui::tema().hata : (kg < 30 ? ui::tema().uyari : ui::tema().basari); ImGui::TextColored(renk, "%s (%lld gün)", tarihMetni(bitis).c_str(), (long long)kg); } ImGui::PopID(); return degisti; } // Serbest periyot düzenleyici: [sayı][birim]. "ömür boyu" seçiliyken sayı kutusu // pasiftir (0 saniye = sıfırlanmayan toplam sayaç; sayının anlamı yok). // Aynı kalıp hem ekleme kutucuğunda hem tablo satırında kullanılır. void periyotDuzenle(u32& periyot, float sayiEn, float birimEn) { int sayi = 1, birim = 0; periyotAyristir(periyot, sayi, birim); const bool omur = (birim == 0); ImGui::BeginDisabled(omur); ImGui::SetNextItemWidth(sayiEn); if (ImGui::DragInt("##psayi", &sayi, 0.2f, 1, 9999, "%d")) periyot = periyotKur(sayi, birim); ImGui::EndDisabled(); if (!omur) ipucu("Pencere uzunluğu (kaç birim). Ör. 5 + saat = 5 saatte bir sıfırlanır."); ImGui::SameLine(0.0f, 3.0f); ImGui::SetNextItemWidth(birimEn); if (ImGui::Combo("##pbirim", &birim, BIRIM_ETIKET, BIRIM_SAYI)) periyot = periyotKur(sayi, birim); ipucu("Sayacın sıfırlanma penceresi. 'ömür boyu' = hiç sıfırlanmaz (toplam hak).\n" "Pencere ANKRAJLIDIR: ilk kullanımda başlar, süre dolunca sıfırlanır."); } // Kota tablosu (paket veya genel). Ekleme satırı üst kutucuktan beslenir. void kotaTablosu(const char* kimlik, std::vector& kotalar, UreticiDurum& d) { ImGui::PushID(kimlik); soluk("Aynı komuta farklı periyotlu birden çok kota ekleyebilirsin: 5/5saat + 30/hafta"); ImGui::SetNextItemWidth(-284.0f); ImGui::InputTextWithHint("##kotaKimlik", "kota kimliği (cam.gkod / cam.*)", d.yeniKota, sizeof d.yeniKota); ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton("⤵")) { if (!d.secili.empty()) metinKopyala(d.yeniKota, sizeof d.yeniKota, *d.secili.begin()); } ipucu("Seçili ilk komutun kimliğini kutuya doldurur."); ImGui::SameLine(0.0f, 4.0f); ImGui::SetNextItemWidth(56.0f); ImGui::DragInt("##kotaAdet", &d.yeniKotaAdet, 1.0f, 1, 1000000, "%d"); ipucu("Periyot başına kullanım hakkı (adet)."); ImGui::SameLine(0.0f, 4.0f); periyotDuzenle(d.yeniKotaPeriyot, 46.0f, 82.0f); ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton("+ Kota")) { const std::string kk = kirp(d.yeniKota); if (kk.empty()) { ui::toastGoster("Kota kimliği boş olamaz", ui::ToastTip::Uyari); } else { KotaSatir k; metinKopyala(k.kimlik, sizeof k.kimlik, kk); k.adet = d.yeniKotaAdet; k.periyot = d.yeniKotaPeriyot; kotalar.push_back(k); d.yeniKota[0] = 0; } } // Hazır periyot kısayolları (tek tıkla ekleme kutucuğunu doldurur). soluk("Hazır periyot:"); struct Kisayol { const char* etiket; u32 saniye; }; static const Kisayol kisayollar[] = { {"5saat", 5u * 3600u}, {"gun", 86400u}, {"hafta", 604800u}, {"ay", 2592000u}}; for (const Kisayol& ks : kisayollar) { ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton(ks.etiket)) d.yeniKotaPeriyot = ks.saniye; } ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton("ömür")) d.yeniKotaPeriyot = 0u; if (kotalar.empty()) { soluk("Kota yok — komut hakkı sınırsız kullanılır."); ImGui::PopID(); return; } // Silinecek satır DÖNGÜ İÇİNDE yalnız işaretlenir; erase tablo kapandıktan SONRA // yapılır — döngü içinde silmek indeksleri kaydırır ve ImGui ID'leri karışır. int silinen = -1; if (ImGui::BeginTable("##kotaTablo", 5, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY | ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingStretchProp, ImVec2(0.0f, std::min(160.0f, 26.0f + 22.0f * (float)kotalar.size())))) { ImGui::TableSetupColumn("Kimlik", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("Adet", ImGuiTableColumnFlags_WidthFixed, 54.0f); ImGui::TableSetupColumn("Her", ImGuiTableColumnFlags_WidthFixed, 44.0f); ImGui::TableSetupColumn("Periyot", ImGuiTableColumnFlags_WidthFixed, 82.0f); ImGui::TableSetupColumn("##sil", ImGuiTableColumnFlags_WidthFixed, 26.0f); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); for (int i = 0; i < (int)kotalar.size(); ++i) { KotaSatir& k = kotalar[(std::size_t)i]; // ÇİFT KAYIT: motor eşleşen HER kuralı ayrı artırır — aynı kimlik+periyot iki // kez yazılırsa tek kullanım iki kova birden yer (hak yarıya iner). bool cift = false; for (int j = 0; j < i; ++j) if (k.periyot == kotalar[(std::size_t)j].periyot && std::strcmp(k.kimlik, kotalar[(std::size_t)j].kimlik) == 0) { cift = true; break; } ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); ImGui::SetNextItemWidth(-1); ImGui::InputText("##k", k.kimlik, sizeof k.kimlik); if (cift) { ImGui::SameLine(0.0f, 0.0f); ImGui::TextColored(ui::tema().hata, " ⚠"); ipucu("Aynı kimlik + aynı periyot zaten var: ÇİFT SAYILIR (her kullanım iki " "kovadan da düşer). Farklı periyot verin veya satırı silin."); } ImGui::TableSetColumnIndex(1); ImGui::SetNextItemWidth(-1); ImGui::DragInt("##a", &k.adet, 1.0f, 1, 1000000, "%d"); ImGui::TableSetColumnIndex(2); int sayi = 1, birim = 0; periyotAyristir(k.periyot, sayi, birim); ImGui::BeginDisabled(birim == 0); ImGui::SetNextItemWidth(-1); if (ImGui::DragInt("##ps", &sayi, 0.2f, 1, 9999, "%d")) k.periyot = periyotKur(sayi, birim); ImGui::EndDisabled(); ImGui::TableSetColumnIndex(3); ImGui::SetNextItemWidth(-1); if (ImGui::Combo("##pb", &birim, BIRIM_ETIKET, BIRIM_SAYI)) k.periyot = periyotKur(sayi, birim); ImGui::TableSetColumnIndex(4); if (ImGui::SmallButton("×")) silinen = i; ipucu("Bu kota satırını siler."); ImGui::PopID(); } ImGui::EndTable(); } if (silinen >= 0 && silinen < (int)kotalar.size()) kotalar.erase(kotalar.begin() + silinen); ImGui::PopID(); } // Silinebilir komut listesi (paket veya genel haklar). void komutListesi(const char* kimlik, std::vector& komutlar, float yukseklik) { ImGui::PushID(kimlik); if (komutlar.empty()) { soluk("Komut yok — soldan seçip \"Seçilenleri ekle\" deyin."); ImGui::PopID(); return; } int silinen = -1; // erase tablo kapandıktan SONRA (indeks kayması + ID karışması) if (ImGui::BeginTable("##komutTablo", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0.0f, yukseklik))) { ImGui::TableSetupColumn("Komut kimliği", ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("##sil", ImGuiTableColumnFlags_WidthFixed, 26.0f); ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableHeadersRow(); for (int i = 0; i < (int)komutlar.size(); ++i) { ImGui::PushID(i); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); const bool joker = komutlar[(std::size_t)i].find('*') != std::string::npos; ImGui::TextColored(joker ? ui::tema().vurgu2 : ui::tema().metin, "%s", komutlar[(std::size_t)i].c_str()); ImGui::TableSetColumnIndex(1); if (ImGui::SmallButton("×")) silinen = i; ipucu("Bu komut hakkını listeden çıkarır."); ImGui::PopID(); } ImGui::EndTable(); } if (silinen >= 0 && silinen < (int)komutlar.size()) komutlar.erase(komutlar.begin() + silinen); ImGui::PopID(); } // Listeye tekrarsız ekleme (aynı komut iki kez yazılmasın). void tekrarsizEkle(std::vector& liste, const std::string& deger) { for (const std::string& x : liste) if (x == deger) return; liste.push_back(deger); } // ---- Lisans gövdesi ------------------------------------------------------- // SIRA ÖNEMLİ: `paket=` satırı BLOK AÇAR — ondan sonra gelen komut/kota satırları // o pakete yazılır. Bu yüzden genel (paketsiz) haklar paketlerden ÖNCE gelmeli. std::string govdeKur(const UreticiDurum& d) { std::string g; g.reserve(2048); auto satir = [&](const char* anahtar, const std::string& deger) { g += anahtar; g += '='; g += deger; g += '\n'; }; auto sayi = [&](const char* anahtar, long long deger) { char b[32]; std::snprintf(b, sizeof b, "%lld", deger); satir(anahtar, b); }; // "kimlik:adet" (ömür boyu) veya "kimlik:adet/" — birim kısaltmaları // kotaPeriyotCoz ile birebir (sn|dk|saat|gun|hafta|ay|yil). auto kotaMetni = [](const KotaSatir& k) { const std::string kod = periyotKodu(k.periyot); char b[192]; std::snprintf(b, sizeof b, "%s:%d%s%s", k.kimlik, k.adet, kod.empty() ? "" : "/", kod.c_str()); return std::string(b); }; g += "# FikoCAD Lisans v1\n"; g += "# üretim: " + tarihMetni(suanUnix()) + "\n"; satir("musteri", kirp(d.musteri)); satir("seri", kirp(d.seri)); satir("urun", kirp(d.urun)); if (!kirp(d.seviye).empty()) satir("seviye", kirp(d.seviye)); satir("kisitli", d.kisitli ? "1" : "0"); if (!d.suresiz && d.bitis > 0) sayi("bitis", (long long)d.bitis); for (const std::string& gr : d.gruplar) satir("grup", gr); for (const std::string& k : d.genelKomutlar) satir("komut", k); for (const KotaSatir& k : d.genelKotalar) satir("kota", kotaMetni(k)); // Makine kilidi: metin kutusundaki her satır bir hash adayı (boş/yorum atılır). // Müşteriden gelen "makine=" biçimi de kabul edilir → sağ taraf alınır. { std::string metin = d.makineler; std::size_t i = 0; int adet = 0; while (i <= metin.size()) { std::size_t son = metin.find('\n', i); const bool sonSatir = (son == std::string::npos); if (sonSatir) son = metin.size(); std::string s = kirp(metin.substr(i, son - i)); if (const std::size_t esit = s.find('='); esit != std::string::npos) s = kirp(s.substr(esit + 1)); if (!s.empty() && s[0] != '#') { satir("makine", s); ++adet; } i = son + 1; if (sonSatir) break; } if (adet > 0) sayi("makineTolerans", d.tolerans); } if (d.yapiKilidi && kirp(d.yapiHash).size() > 0) satir("yapi", kirp(d.yapiHash)); for (const PaketDurum& p : d.paketler) { const std::string pk = kirp(p.kimlik); if (pk.empty()) continue; // kimliksiz paket blok açamaz (ayrıştırıcı boş kimlik yazar) satir("paket", pk); if (const std::string pad = kirp(p.ad); !pad.empty()) satir("paket.ad", pad); if (!p.suresiz && p.bitis > 0) sayi("paket.bitis", (long long)p.bitis); for (const std::string& k : p.komutlar) satir("komut", k); for (const KotaSatir& k : p.kotalar) satir("kota", kotaMetni(k)); } return g; } // Özel anahtar dosyasını oku → ozelHex (96 bayt = 192 hane). Hata metni döner. bool ozelAnahtarOku(const std::string& yol, std::string& ozelHex, std::string& hata) { std::string ham; if (!dosyaOku(yol, ham)) { hata = "Özel anahtar dosyası okunamadı: " + yol; return false; } ozelHex = enUzunHex(ham); if (ozelHex.size() < 192) { hata = "Anahtar dosyasında 192 haneli özel anahtar bulunamadı (bulunan: " + std::to_string(ozelHex.size()) + " hane)"; return false; } ozelHex.resize(192); return true; } // ---- Sol sütun: komut kataloğu ------------------------------------------- void solSutun(UygulamaBaglam& b, UreticiDurum& d) { const ui::Tema& t = ui::tema(); ImGui::TextColored(t.vurgu, "📚 KOMUT KATALOĞU"); ui::neonAyrac(); ui::aramaKutusu("##katalogAra", d.arama, (int)sizeof d.arama, "Kimlik / ad / grup ara…", -1.0f); const Katalog& kat = katalog(b.cekirdek.menuler(), d.arama); rozetSayi("Seçili:", (int)d.secili.size(), t.vurgu); ImGui::SameLine(); rozetSayi("Katalog:", kat.toplam, t.metinSoluk); ImGui::SameLine(); if (ImGui::SmallButton("Tümünü seç")) { for (const KatalogGrup& g : kat.gruplar) for (const KatalogOge& o : g.ogeler) d.secili.insert(o.kimlik); } ipucu("Filtreye UYAN tüm komutları seçime ekler."); ImGui::SameLine(); if (ImGui::SmallButton("Temizle")) d.secili.clear(); ipucu("Seçimi tamamen boşaltır (paketlerdeki komutlara dokunmaz)."); ImGui::Spacing(); if (ImGui::BeginChild("##katalogListe", ImVec2(0, 0), ImGuiChildFlags_Borders)) { if (kat.gruplar.empty()) { soluk("Eşleşen komut yok. Aramayı temizleyin."); } // ID'ler İNDEKS tabanlı: grup adı boş ya da iki grupta aynı olursa dizge ID'ler // çakışır (aynı kimlikli iki widget = tıklama yanlış satıra gider). for (int gi = 0; gi < (int)kat.gruplar.size(); ++gi) { const KatalogGrup& g = kat.gruplar[(std::size_t)gi]; ImGui::PushID(gi); char basl[192]; std::snprintf(basl, sizeof basl, "%s (%d)###grup", g.ad.c_str(), (int)g.ogeler.size()); const bool acik = ImGui::CollapsingHeader(basl); // Grup kısayolları başlığın kendi satırında değil hemen altında: başlık genişliği // ImGui'de tam satırdır, üstüne düğme koymak tıklama alanını böler. if (acik) { if (ImGui::SmallButton("Grubu seç")) { for (const KatalogOge& o : g.ogeler) d.secili.insert(o.kimlik); } ipucu("Gruptaki tüm komutları seçime ekler."); if (!g.joker.empty()) { ImGui::SameLine(); char jb[64]; std::snprintf(jb, sizeof jb, "+ %s", g.joker.c_str()); if (ImGui::SmallButton(jb)) d.secili.insert(g.joker); ipucu("Grup jokeri: tek satırla grubun TAMAMI (gelecekte eklenecek komutlar dahil)."); } ImGui::SameLine(); char gb[128]; std::snprintf(gb, sizeof gb, "+ grup=%s", g.ad.c_str()); if (ImGui::SmallButton(gb)) tekrarsizEkle(d.gruplar, g.ad); ipucu("Menü grubu whitelist'ine ekler (grup= satırı; komut gate'inden bağımsız)."); for (int oi = 0; oi < (int)g.ogeler.size(); ++oi) { const KatalogOge& o = g.ogeler[(std::size_t)oi]; ImGui::PushID(oi); bool tik = d.secili.count(o.kimlik) > 0; if (ImGui::Checkbox("##tik", &tik)) { if (tik) d.secili.insert(o.kimlik); else d.secili.erase(o.kimlik); } ImGui::SameLine(); ImGui::TextUnformatted(o.ad.c_str()); ImGui::SameLine(); ImGui::TextColored(t.metinSoluk, "%s", o.kimlik.c_str()); if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) ImGui::SetTooltip("Lisans kimliği: %s\nGrup: %s", o.kimlik.c_str(), g.ad.c_str()); ImGui::PopID(); } ImGui::Spacing(); } ImGui::PopID(); } } ImGui::EndChild(); } // ---- Orta sütun: paketler + genel haklar ---------------------------------- void ortaSutun(UreticiDurum& d) { const ui::Tema& t = ui::tema(); ImGui::TextColored(t.vurgu, "📦 PAKETLER"); ui::neonAyrac(); rozetSayi("Paket:", (int)d.paketler.size(), t.vurgu2); ImGui::SameLine(); int toplamKota = (int)d.genelKotalar.size(); int toplamKomut = (int)d.genelKomutlar.size(); for (const PaketDurum& p : d.paketler) { toplamKota += (int)p.kotalar.size(); toplamKomut += (int)p.komutlar.size(); } rozetSayi("Komut:", toplamKomut, t.basari); ImGui::SameLine(); rozetSayi("Kota:", toplamKota, t.uyari); ImGui::SameLine(); if (ImGui::SmallButton("+ Paket")) { PaketDurum p; char kb[64]; std::snprintf(kb, sizeof kb, "paket%d", (int)d.paketler.size() + 1); metinKopyala(p.kimlik, sizeof p.kimlik, kb); metinKopyala(p.ad, sizeof p.ad, std::string("Yeni Paket")); d.paketler.push_back(std::move(p)); d.aktifPaket = (int)d.paketler.size() - 1; } ipucu("Yeni satış paketi: kendi bitişi ve kotalarıyla bağımsız komut kümesi."); // Paket listesi (seçim + silme) int silinenPaket = -1; // erase child KAPANDIKTAN sonra (indeks kayması güvenliği) if (ImGui::BeginChild("##paketListe", ImVec2(0, 92.0f), ImGuiChildFlags_Borders)) { if (d.paketler.empty()) { soluk("Paket yok. Süresiz tek blok satış için paketsiz"); soluk("\"Genel haklar\" bölümünü kullanabilirsiniz."); } for (int i = 0; i < (int)d.paketler.size(); ++i) { PaketDurum& p = d.paketler[(std::size_t)i]; ImGui::PushID(i); // × düğmesi konumu = satır sonu. GetContentRegionMax() OBSOLETE — cursor + avail. const float sagKenar = ImGui::GetCursorPosX() + ImGui::GetContentRegionAvail().x; char etiket[256]; const std::string bit = p.suresiz ? std::string("süresiz") : tarihMetni(p.bitis); std::snprintf(etiket, sizeof etiket, "%s · %s · %d komut · %s", p.kimlik, p.ad, (int)p.komutlar.size(), bit.c_str()); // 🐞 PAKET SİLİNMİYOR'UN KÖKÜ: Selectable tüm satırı kaplar ve HoveredId'yi ilk // o kapar; AllowOverlap yoksa üstüne gelen × düğmesi ItemHoverable'dan false // döner → tık hep Selectable'a gider, × HİÇ çalışmaz. Overlap'e izin şart. ImGui::SetNextItemAllowOverlap(); if (ImGui::Selectable(etiket, d.aktifPaket == i, ImGuiSelectableFlags_AllowOverlap)) d.aktifPaket = i; ImGui::SameLine(sagKenar - 18.0f); if (ImGui::SmallButton("×")) silinenPaket = i; ipucu("Bu paketi siler (komutları ve kotalarıyla birlikte)."); ImGui::PopID(); } } ImGui::EndChild(); if (silinenPaket >= 0 && silinenPaket < (int)d.paketler.size()) { d.paketler.erase(d.paketler.begin() + silinenPaket); // Seçili indeks KAYAR: silinen seçiliyse seçim düşer, üstündekiler bir geri gelir. if (d.aktifPaket == silinenPaket) d.aktifPaket = -1; else if (d.aktifPaket > silinenPaket) --d.aktifPaket; if (d.aktifPaket >= (int)d.paketler.size()) d.aktifPaket = (int)d.paketler.size() - 1; if (d.paketler.empty()) d.aktifPaket = -1; } // Seçili paketin düzenlemesi if (d.aktifPaket >= 0 && d.aktifPaket < (int)d.paketler.size()) { PaketDurum& p = d.paketler[(std::size_t)d.aktifPaket]; ImGui::PushID(d.aktifPaket); ImGui::SeparatorText("Seçili paket"); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##pkimlik", "paket kimliği (cam / modelleme)", p.kimlik, sizeof p.kimlik); ipucu("paket= satırına yazılır. Kısa, değişmez, ASCII tutun."); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##pad", "görünen ad (CAM 3 Aylık)", p.ad, sizeof p.ad); ipucu("paket.ad= satırı — müşteriye gösterilen okunur ad."); bitisDuzenle("pbitis", p.suresiz, p.bitis); ImGui::Spacing(); if (ui::buton("+ Seçili komutları ekle", ImVec2(-1, 0), ui::ButonTip::Birincil)) { if (d.secili.empty()) { ui::toastGoster("Önce soldan komut seçin", ui::ToastTip::Uyari); } else { for (const std::string& k : d.secili) tekrarsizEkle(p.komutlar, k); char m[96]; std::snprintf(m, sizeof m, "%d komut pakete eklendi", (int)d.secili.size()); ui::toastGoster(m, ui::ToastTip::Basari); } } komutListesi("pkomut", p.komutlar, 150.0f); ImGui::SeparatorText("Paket kotaları"); kotaTablosu("pkota", p.kotalar, d); ImGui::PopID(); } else if (!d.paketler.empty()) { soluk("Düzenlemek için yukarıdan bir paket seçin."); } // Genel (paketsiz) haklar ImGui::Spacing(); if (ui::katlanirBasla("Genel (paketsiz) haklar", &d.genelAcik)) { soluk("Bu haklar lisans geneli bitişe tabidir."); if (ui::buton("+ Seçili komutları genel hak yap", ImVec2(-1, 0), ui::ButonTip::Ikincil)) { if (d.secili.empty()) ui::toastGoster("Önce soldan komut seçin", ui::ToastTip::Uyari); else for (const std::string& k : d.secili) tekrarsizEkle(d.genelKomutlar, k); } komutListesi("gkomut", d.genelKomutlar, 120.0f); ImGui::SeparatorText("Genel kotalar"); kotaTablosu("gkota", d.genelKotalar, d); ImGui::SeparatorText("Grup whitelist (grup=)"); if (d.gruplar.empty()) { soluk("Grup yok — komut/paket hakları geçerli."); } else { int silinen = -1; for (int i = 0; i < (int)d.gruplar.size(); ++i) { ImGui::PushID(i); ui::rozet(d.gruplar[(std::size_t)i].c_str(), ui::tema().vurgu2); ImGui::SameLine(); if (ImGui::SmallButton("×")) silinen = i; ipucu("Bu grup whitelist satırını siler."); ImGui::PopID(); if (i % 2 == 0 && i + 1 < (int)d.gruplar.size()) ImGui::SameLine(); } if (silinen >= 0 && silinen < (int)d.gruplar.size()) d.gruplar.erase(d.gruplar.begin() + silinen); } ui::katlanirBitir(); } } // ---- Sağ sütun: müşteri + kilit + üretim ---------------------------------- void uret(UreticiDurum& d) { if (kirp(d.seri).empty() || kirp(d.urun).empty()) { ui::toastGoster("Seri ve ürün alanları zorunlu", ui::ToastTip::Uyari); return; } std::string cikti = kirp(d.ciktiYol); if (cikti.empty()) { cikti = dosyaSecUretici(true, L"FikoCAD Lisans (*.fkl)\0*.fkl\0Tümü (*.*)\0*.*\0", L"fkl"); if (cikti.empty()) return; metinKopyala(d.ciktiYol, sizeof d.ciktiYol, cikti); } std::string ozelHex, hata; if (!ozelAnahtarOku(kirp(d.anahtarYol), ozelHex, hata)) { ui::toastGoster(hata.c_str(), ui::ToastTip::Hata); return; } const std::string govde = govdeKur(d); std::string imza; if (!kripto::imzala(lisansKanonikGovde(govde), ozelHex, imza)) { ui::toastGoster("İmzalama başarısız (özel anahtar geçersiz olabilir)", ui::ToastTip::Hata); return; } const std::string tam = govde + "imza=" + imza + "\n"; // VARSAYILAN ŞİFRELİ (CLI `fiko_lisans imzala` ile aynı davranış): müşteri .fkl'i // Notepad'de açıp komut/kota/makine listesini okuyamasın. İmza zaten içerideki // düz metnin üzerinde — zarf yalnız GİZLİLİK katmanı. std::string yazilacak = tam; if (!d.duzMetin) { u8 zarfAnahtar[32]; lisansZarfAnahtari(zarfAnahtar); yazilacak = lisansZarfla(tam, zarfAnahtar); if (yazilacak.empty()) { ui::toastGoster("Lisans şifrelenemedi (düz metin seçeneğiyle deneyin)", ui::ToastTip::Hata); return; } } if (!dosyaYaz(cikti, yazilacak)) { ui::toastGoster(("Dosya yazılamadı: " + cikti).c_str(), ui::ToastTip::Hata); return; } const char* bicim = d.duzMetin ? "düz metin" : "şifreli"; ui::toastGoster(("Lisans üretildi (" + std::string(bicim) + "): " + cikti).c_str(), ui::ToastTip::Basari, 5.0f); // Üretim sonrası ANINDA geri-okuma: DOSYADAN okuruz (bellekteki dizgeden değil) — // zarflama/yazma hatası da burada yakalanır, müşteriye gitmeden. std::string diskten; if (!dosyaOku(cikti, diskten)) { d.dogrulamaOzet = "Yazıldı ama geri okunamadı: " + cikti; d.dogrulamaDurum = 2; return; } u8 zarfAnahtar[32]; lisansZarfAnahtari(zarfAnahtar); const std::string duzIcerik = lisansZarfCoz(diskten, zarfAnahtar); LisansBilgi bilgi; std::string hata2; const LisansDurum dur = lisansCoz(duzIcerik, ozelHex.substr(0, 128), bilgi, hata2); int kotaSayi = (int)bilgi.kotalar.size(), komutSayi = (int)bilgi.komutlar.size(); for (const LisansPaket& p : bilgi.paketler) { kotaSayi += (int)p.kotalar.size(); komutSayi += (int)p.komutlar.size(); } char oz[640]; std::snprintf(oz, sizeof oz, "Durum: %s%s%s\nBiçim: %s (%d bayt)\n%d paket · %d komut · %d kota · %d makine\n" "Bitiş: %s", lisansDurumMetni(dur), hata2.empty() ? "" : " — ", hata2.c_str(), bicim, (int)diskten.size(), (int)bilgi.paketler.size(), komutSayi, kotaSayi, (int)bilgi.makineHashleri.size(), tarihMetni(bilgi.bitis).c_str()); d.dogrulamaOzet = oz; d.dogrulamaDurum = (dur == LisansDurum::Gecerli) ? 1 : 2; } // Diskteki .fkl dosyasını doğrular. Şifreli/düz fark etmez: lisansZarfCoz zarflı // değilse içeriği AYNEN geri verir (geriye uyum). void dosyadanDogrula(UreticiDurum& d) { std::string yol = kirp(d.ciktiYol); if (yol.empty()) { yol = dosyaSecUretici(false, L"FikoCAD Lisans (*.fkl)\0*.fkl\0Tümü (*.*)\0*.*\0", L"fkl"); if (yol.empty()) return; } std::string ozelHex, hata; if (!ozelAnahtarOku(kirp(d.anahtarYol), ozelHex, hata)) { d.dogrulamaOzet = hata; d.dogrulamaDurum = 2; return; } std::string ham; if (!dosyaOku(yol, ham)) { d.dogrulamaOzet = "Dosya okunamadı: " + yol; d.dogrulamaDurum = 2; return; } u8 zarfAnahtar[32]; lisansZarfAnahtari(zarfAnahtar); const bool zarfli = ham.rfind("FKLE1", 0) == 0; const std::string duzIcerik = lisansZarfCoz(ham, zarfAnahtar); LisansBilgi bilgi; std::string hata2; const LisansDurum dur = lisansCoz(duzIcerik, ozelHex.substr(0, 128), bilgi, hata2); int kotaSayi = (int)bilgi.kotalar.size(), komutSayi = (int)bilgi.komutlar.size(); for (const LisansPaket& p : bilgi.paketler) { kotaSayi += (int)p.kotalar.size(); komutSayi += (int)p.komutlar.size(); } char oz[768]; std::snprintf(oz, sizeof oz, "Durum: %s%s%s\nDosya: %s (%s)\nMüşteri: %s · Seri: %s · Ürün: %s\n" "%d paket · %d komut · %d kota · %d makine\nBitiş: %s", lisansDurumMetni(dur), hata2.empty() ? "" : " — ", hata2.c_str(), yol.c_str(), zarfli ? "şifreli" : "düz metin", bilgi.musteri.c_str(), bilgi.seri.c_str(), bilgi.urun.c_str(), (int)bilgi.paketler.size(), komutSayi, kotaSayi, (int)bilgi.makineHashleri.size(), tarihMetni(bilgi.bitis).c_str()); d.dogrulamaOzet = oz; d.dogrulamaDurum = (dur == LisansDurum::Gecerli) ? 1 : 2; } void dogrula(UreticiDurum& d) { std::string ozelHex, hata; if (!ozelAnahtarOku(kirp(d.anahtarYol), ozelHex, hata)) { d.dogrulamaOzet = hata; d.dogrulamaDurum = 2; return; } const std::string govde = govdeKur(d); std::string imza; if (!kripto::imzala(lisansKanonikGovde(govde), ozelHex, imza)) { d.dogrulamaOzet = "İmzalama başarısız (özel anahtar geçersiz)"; d.dogrulamaDurum = 2; return; } const std::string tam = govde + "imza=" + imza + "\n"; LisansBilgi bilgi; std::string hata2; // Açık anahtar özel anahtarın ilk 128 hanesidir (ozelHex = X||Y||d). const LisansDurum dur = lisansCoz(tam, ozelHex.substr(0, 128), bilgi, hata2); int kotaSayi = (int)bilgi.kotalar.size(), komutSayi = (int)bilgi.komutlar.size(); for (const LisansPaket& p : bilgi.paketler) { kotaSayi += (int)p.kotalar.size(); komutSayi += (int)p.komutlar.size(); } char oz[640]; std::snprintf(oz, sizeof oz, "Durum: %s%s%s\nMüşteri: %s · Seri: %s · Ürün: %s\n" "%d paket · %d komut · %d kota · %d makine (tolerans %d)\nBitiş: %s", lisansDurumMetni(dur), hata2.empty() ? "" : " — ", hata2.c_str(), bilgi.musteri.c_str(), bilgi.seri.c_str(), bilgi.urun.c_str(), (int)bilgi.paketler.size(), komutSayi, kotaSayi, (int)bilgi.makineHashleri.size(), bilgi.makineTolerans, tarihMetni(bilgi.bitis).c_str()); d.dogrulamaOzet = oz; d.dogrulamaDurum = (dur == LisansDurum::Gecerli) ? 1 : 2; } // İmzalama + çıktı + üret/doğrula + sonuç özeti. Uzman sağ sütunu ve sihirbazın son // adımı AYNI bloğu kullanır (tek kod, tek hata yüzeyi). void imzaCiktiUretimBloku(UreticiDurum& d) { const ui::Tema& t = ui::tema(); ImGui::SeparatorText("İmzalama"); ImGui::SetNextItemWidth(-64.0f); ImGui::InputTextWithHint("##anahtar", "özel anahtar dosyası (.key)", d.anahtarYol, sizeof d.anahtarYol); ipucu("ozelHex = X||Y||d (192 hane). ASLA müşteriye gitmez."); ImGui::SameLine(); if (ImGui::SmallButton("…##anahtar")) { const std::string s = dosyaSecUretici(false, L"Anahtar (*.key;*.txt)\0*.key;*.txt\0Tümü (*.*)\0*.*\0", nullptr); if (!s.empty()) metinKopyala(d.anahtarYol, sizeof d.anahtarYol, s); } if (ImGui::SmallButton("🔑 Yeni anahtar çifti üret")) { std::string ozel, acik; if (!kripto::p256AnahtarUret(ozel, acik)) { ui::toastGoster("Anahtar üretilemedi", ui::ToastTip::Hata); } else { const std::string yol = dosyaSecUretici( true, L"Anahtar (*.key)\0*.key\0Tümü (*.*)\0*.*\0", L"key"); if (!yol.empty()) { // Özel anahtar dosyaya; AÇIK anahtar panoya → uygulamaya gömülecek olan odur. if (dosyaYaz(yol, "# FikoCAD lisans ÖZEL anahtarı — PAYLAŞMAYIN\n" + ozel + "\n")) { metinKopyala(d.anahtarYol, sizeof d.anahtarYol, yol); ImGui::SetClipboardText(acik.c_str()); ui::toastGoster("Anahtar üretildi · açık anahtar panoya kopyalandı", ui::ToastTip::Basari, 6.0f); } else { ui::toastGoster("Anahtar dosyası yazılamadı", ui::ToastTip::Hata); } } } } ipucu("Yeni anahtar ÜRETMEK eski tüm lisansları geçersiz kılar — yalnız ilk kurulumda."); ImGui::SeparatorText("Çıktı"); ImGui::SetNextItemWidth(-64.0f); ImGui::InputTextWithHint("##cikti", "lisans.fkl çıktı yolu", d.ciktiYol, sizeof d.ciktiYol); ImGui::SameLine(); if (ImGui::SmallButton("…##cikti")) { const std::string s = dosyaSecUretici(true, L"FikoCAD Lisans (*.fkl)\0*.fkl\0Tümü (*.*)\0*.*\0", L"fkl"); if (!s.empty()) metinKopyala(d.ciktiYol, sizeof d.ciktiYol, s); } ImGui::Checkbox("Düz metin yaz (hata ayıklama)", &d.duzMetin); ipucu("Varsayılan KAPALI = dosya ŞİFRELİ yazılır; müşteri .fkl'i açsa da komut/kota/" "makine listesini okuyamaz (imza yine geçerlidir).\nAçık = eski düz metin biçimi — " "yalnız hata ayıklama için, müşteriye GÖNDERME."); if (d.duzMetin) { ImGui::SameLine(); ImGui::TextColored(t.uyari, "⚠ okunabilir"); } ImGui::Spacing(); // Zorunlu alan eksikse üretim düğmesi PASİF + hangi alanın eksik olduğu kırmızı yazılır // (eskiden düğme aktifti, tıklayınca kaybolan bir toast atıyordu). const char* eksik = eksikAlan(d); ImGui::BeginDisabled(eksik != nullptr); if (ui::buton("🔐 Lisans Üret", ImVec2(-1, 32.0f), ui::ButonTip::Birincil)) uret(d); ipucu("Gövdeyi kurar → imzalar → (şifreliyse) zarflar → dosyaya yazar → DİSKTEN geri " "okuyup doğrular."); ImGui::EndDisabled(); if (eksik) ImGui::TextColored(t.hata, "⛔ %s", eksik); if (ui::buton("🔎 Doğrula (yazmadan)", ImVec2(-1, 0), ui::ButonTip::Ikincil)) dogrula(d); ipucu("Dosyaya yazmadan aynı zinciri çalıştırır — biçim hatasını anında gösterir."); if (ui::buton("📂 Dosyadan doğrula (.fkl)", ImVec2(-1, 0), ui::ButonTip::Ikincil)) dosyadanDogrula(d); ipucu("Diskteki lisansı okur: şifreliyse zarfı açar, sonra imzayı doğrular.\n" "Çıktı yolu boşsa dosya seçici açılır."); if (d.dogrulamaDurum != 0) { const ImVec4 renk = d.dogrulamaDurum == 1 ? t.basari : t.hata; ImGui::PushStyleColor(ImGuiCol_Text, renk); ImGui::TextWrapped("%s", d.dogrulamaOzet.c_str()); ImGui::PopStyleColor(); if (!kirp(d.ciktiYol).empty()) { if (ImGui::SmallButton("📂 Klasörü aç##sonuc")) klasoruAc(kirp(d.ciktiYol)); ipucu("Çıktı dosyasının bulunduğu klasörü Explorer'da açar (müşteriye göndermek için)."); } } } // Makine (node-lock) + yapı kilidi. Uzman sağ sütunu ve sihirbaz 4. adımı ortak kullanır. void kilitBloku(UreticiDurum& d) { ImGui::SeparatorText("Makine kilidi (node-lock)"); soluk("Müşteriden gelen makine hash satırlarını yapıştırın."); ImGui::InputTextMultiline("##makine", d.makineler, sizeof d.makineler, ImVec2(-1, 76.0f)); ipucu("Her satır bir hash. \"makine=\" biçimi de kabul edilir.\nBoş bırakılırsa " "node-lock UYGULANMAZ (lisans her makinede çalışır)."); if (ImGui::SmallButton("🖥 Bu makineyi kullan")) { const std::vector h = makineParmakIzi().hashListesi(); std::string m; for (const std::string& x : h) m += x + "\n"; metinKopyala(d.makineler, sizeof d.makineler, m); if (h.empty()) ui::toastGoster("Makine parmak izi okunamadı", ui::ToastTip::Hata); } ipucu("Bu bilgisayarın parmak izini yazar (kendi test lisansınız için)."); ImGui::SameLine(); if (ImGui::SmallButton("Temizle##mk")) d.makineler[0] = 0; ImGui::SetNextItemWidth(120.0f); ImGui::DragInt("Tolerans", &d.tolerans, 0.1f, 1, 8); ipucu("En az kaç alan tutarsa makine kabul edilsin.\nDüşük = esnek (donanım değişimi " "müşteriyi kilitlemez), yüksek = sıkı."); ImGui::SeparatorText("Yapı kilidi"); ImGui::Checkbox("Bu exe'ye kilitle (yapi=)", &d.yapiKilidi); ipucu("Lisans YALNIZ bu sürüm exe ile çalışır. Güncelleme sonrası müşteriye yeni " "lisans gerekir — dikkatli kullanın."); if (d.yapiKilidi) { ImGui::SetNextItemWidth(-90.0f); ImGui::InputTextWithHint("##yapi", "exe sha256", d.yapiHash, sizeof d.yapiHash); ImGui::SameLine(); if (ImGui::SmallButton("Doldur##yapi")) metinKopyala(d.yapiHash, sizeof d.yapiHash, uygulamaYapiHash()); ipucu("Çalışan fikocad.exe'nin sha256'sını yazar."); } } void sagSutun(UreticiDurum& d) { const ui::Tema& t = ui::tema(); ImGui::TextColored(t.vurgu, "🧾 MÜŞTERİ · KİLİT · ÜRETİM"); ui::neonAyrac(); ImGui::SeparatorText("Müşteri"); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##musteri", "müşteri adı (Akçalı Döküm)", d.musteri, sizeof d.musteri); ImGui::SetNextItemWidth(-62.0f); ImGui::InputTextWithHint("##seri", "seri no (FKC-2026-0001) — ZORUNLU", d.seri, sizeof d.seri); ipucu("Lisansın kimliği. Her müşteri/satış için benzersiz olmalı."); ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton("🎲 Üret")) metinKopyala(d.seri, sizeof d.seri, seriUret()); ipucu("FKC--<4 hane> üretir. Hane sırası saatten türer (rastgele DEĞİL) —\n" "aynı dakikada iki kez basarsan aynı numarayı verir, kopya seri fark edilir."); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##urun", "ürün (fikocad) — ZORUNLU", d.urun, sizeof d.urun); ipucu("Uygulama kendi ürün adıyla karşılaştırır; farklıysa lisansı reddeder."); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##seviye", "seviye (pro / standart)", d.seviye, sizeof d.seviye); ImGui::Checkbox("Kısıtlı kip (kisitli=1)", &d.kisitli); ipucu("Açık = yalnız satın alınan komutlar çalışır.\nKapalı = tüm uygulama serbest " "(demo/iç kullanım lisansı)."); ImGui::SeparatorText("Lisans geneli bitiş"); bitisDuzenle("lbitis", d.suresiz, d.bitis); kilitBloku(d); imzaCiktiUretimBloku(d); ImGui::SeparatorText("Önizleme (canlı)"); soluk(d.duzMetin ? "İmza satırı üretim anında eklenir (dosya DÜZ yazılacak)." : "İmza satırı üretim anında eklenir; dosya ŞİFRELİ yazılır."); const float onizYuk = std::max(110.0f, ImGui::GetContentRegionAvail().y); ImGui::InputTextMultiline("##onizleme", d.onizleme.data(), d.onizleme.size() + 1, ImVec2(-1, onizYuk), ImGuiInputTextFlags_ReadOnly); } // ---- Sihirbaz kipi -------------------------------------------------------- // Uzman 3-sütun kipiyle AYNI UreticiDurum üzerinde çalışır: kip değiştirmek veri // kaybettirmez, sihirbazda kurulan lisans uzman kipinde ince ayarlanabilir. constexpr int ADIM_SAYI = 5; const char* const ADIM_BASLIK[ADIM_SAYI] = {"1 · Müşteri", "2 · Ne satılıyor", "3 · Süre & kota", "4 · Kilit", "5 · Üret"}; // Adımdan ilerlemeyi engelleyen eksik. nullptr = geçilebilir. const char* adimEksik(const UreticiDurum& d, int adim) { switch (adim) { case 0: if (kirp(d.musteri).empty()) return "Müşteri adı gerekli"; if (kirp(d.seri).empty()) return "Seri no gerekli (🎲 ile üretebilirsiniz)"; if (kirp(d.urun).empty()) return "Ürün adı gerekli (fikocad)"; return nullptr; case 1: if (!d.kisitli) return nullptr; // kısıtsız lisansta komut listesi anlamsız if (d.paketler.empty() && d.genelKomutlar.empty() && d.gruplar.empty()) return "Bir paket seçin ya da özel kurulumda komut ekleyin"; return nullptr; case 4: return eksikAlan(d); default: return nullptr; } } // Şablon kartı: başlık + açıklama + seçili çerçevesi. Tıklayınca durumu kurar. void sablonKarti(UreticiDurum& d, int indeks) { const ui::Tema& t = ui::tema(); const bool ozel = (indeks < 0); const bool aktif = (d.sonSablon == (ozel ? -2 : indeks)); ImGui::PushID(ozel ? 900 : indeks); if (aktif) ImGui::PushStyleColor(ImGuiCol_Header, t.vurgu2); const char* etiket = ozel ? "🧩 Özel kurulum (boş başla)" : SABLONLAR[indeks].etiket; if (ImGui::Selectable(etiket, aktif, 0, ImVec2(0.0f, 26.0f))) { kimlikKoruyarak(d, ozel ? nullptr : &SABLONLAR[indeks]); d.sonSablon = ozel ? -2 : indeks; if (ozel) d.sihirbazGelismis = true; // özel seçildiyse katalog hemen açılsın } if (aktif) ImGui::PopStyleColor(); ImGui::Indent(18.0f); soluk("%s", ozel ? "Hazır paket kullanmadan komutları elle seçmek için.\n" "Aşağıdaki katalog bölümünden paket kurun." : SABLONLAR[indeks].ipucu); ImGui::Unindent(18.0f); ImGui::Spacing(); ImGui::PopID(); } // Kurulan lisansın tek bakışta özeti (2., 3. ve 5. adımda gösterilir). void sihirbazOzet(const UreticiDurum& d) { const ui::Tema& t = ui::tema(); int komut = (int)d.genelKomutlar.size(), kota = (int)d.genelKotalar.size(); for (const PaketDurum& p : d.paketler) { komut += (int)p.komutlar.size(); kota += (int)p.kotalar.size(); } rozetSayi("Paket:", (int)d.paketler.size(), t.vurgu2); ImGui::SameLine(); rozetSayi("Komut:", komut, t.basari); ImGui::SameLine(); rozetSayi("Kota:", kota, t.uyari); ImGui::SameLine(); ui::rozet(d.suresiz ? "süresiz" : tarihMetni(d.bitis).c_str(), d.suresiz ? t.metinSoluk : t.vurgu); } void sihirbazAdim0(UreticiDurum& d) { soluk("Lisans kime kesiliyor? Seri no her satış için benzersiz olmalı."); ImGui::Spacing(); ImGui::TextUnformatted("Müşteri adı"); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##musteri", "Akçalı Döküm", d.musteri, sizeof d.musteri); ImGui::Spacing(); ImGui::TextUnformatted("Seri no"); ImGui::SetNextItemWidth(-70.0f); ImGui::InputTextWithHint("##seri", "FKC-2026-0001", d.seri, sizeof d.seri); ImGui::SameLine(0.0f, 4.0f); if (ImGui::SmallButton("🎲 Üret")) metinKopyala(d.seri, sizeof d.seri, seriUret()); ipucu("FKC--<4 hane> üretir. Hane sırası saatten türer (rastgele DEĞİL)."); ImGui::Spacing(); ImGui::TextUnformatted("Ürün · Seviye"); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##urun", "ürün (fikocad)", d.urun, sizeof d.urun); ipucu("Uygulama kendi ürün adıyla karşılaştırır; farklıysa lisansı reddeder."); ImGui::SetNextItemWidth(-1); ImGui::InputTextWithHint("##seviye", "seviye (pro / standart)", d.seviye, sizeof d.seviye); ImGui::Spacing(); ImGui::Checkbox("Kısıtlı kip — yalnız satın alınan komutlar çalışır", &d.kisitli); ipucu("Kapatırsan tüm uygulama serbest olur (demo / iç kullanım lisansı)."); } void sihirbazAdim1(UygulamaBaglam& b, UreticiDurum& d) { soluk("Hazır bir satış paketi seç — sonraki adımlarda süresini ve kilidini ayarlarsın."); ImGui::Spacing(); for (int i = 0; i < SABLON_SAYI; ++i) sablonKarti(d, i); sablonKarti(d, -1); ImGui::Spacing(); sihirbazOzet(d); ImGui::Spacing(); if (ui::katlanirBasla("Komut kataloğu / paket düzenleme (gelişmiş)", &d.sihirbazGelismis)) { const float yuk = std::max(240.0f, ImGui::GetContentRegionAvail().y - 8.0f); if (ImGui::BeginTable("##sihirbazKatalog", 2, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV, ImVec2(0.0f, yuk))) { ImGui::TableSetupColumn("katalog", ImGuiTableColumnFlags_WidthStretch, 1.0f); ImGui::TableSetupColumn("paketler", ImGuiTableColumnFlags_WidthStretch, 1.0f); ImGui::TableNextRow(); const ImVec2 cocuk{0.0f, yuk - 8.0f}; ImGui::TableSetColumnIndex(0); if (ImGui::BeginChild("##swSol", cocuk)) solSutun(b, d); ImGui::EndChild(); ImGui::TableSetColumnIndex(1); if (ImGui::BeginChild("##swOrta", cocuk)) ortaSutun(d); ImGui::EndChild(); ImGui::EndTable(); } ui::katlanirBitir(); } } void sihirbazAdim2(UreticiDurum& d) { const ui::Tema& t = ui::tema(); soluk("Lisans ne kadar geçerli olsun? Seçim paket bitişlerine de uygulanır."); ImGui::Spacing(); struct Sure { const char* etiket; int gun; }; static const Sure sureler[] = {{"30 gün", 30}, {"3 ay", 90}, {"6 ay", 180}, {"1 yıl", 365}, {"2 yıl", 730}, {"Süresiz", 0}}; for (const Sure& s : sureler) { if (ui::buton(s.etiket, ImVec2(96.0f, 26.0f), ui::ButonTip::Ikincil)) sureUygula(d, s.gun); ImGui::SameLine(0.0f, 6.0f); } ImGui::NewLine(); ImGui::Spacing(); if (d.suresiz) { ImGui::TextColored(t.metinSoluk, "Bitiş yazılmayacak — lisans süresiz."); } else { const i64 kg = kalanGun(d.bitis); const ImVec4 renk = kg < 0 ? t.hata : (kg < 30 ? t.uyari : t.basari); ImGui::TextColored(renk, "Bitiş: %s (%lld gün)", tarihMetni(d.bitis).c_str(), (long long)kg); } ImGui::Spacing(); ImGui::SeparatorText("İnce ayar"); bitisDuzenle("swbitis", d.suresiz, d.bitis); ImGui::Spacing(); if (ui::katlanirBasla("Serbest periyot kotaları (5 saatte 5 kullanım gibi)", &d.sihirbazKota)) { if (d.aktifPaket >= 0 && d.aktifPaket < (int)d.paketler.size()) { PaketDurum& p = d.paketler[(std::size_t)d.aktifPaket]; ImGui::Text("Paket: %s", p.ad[0] ? p.ad : p.kimlik); kotaTablosu("swpkota", p.kotalar, d); } else { soluk("Paket yok — kotalar genel haklara yazılacak."); kotaTablosu("swgkota", d.genelKotalar, d); } ui::katlanirBitir(); } ImGui::Spacing(); sihirbazOzet(d); } void sihirbazAdim3(UreticiDurum& d) { soluk("Lisans belirli makinelere kilitlensin mi? Boş bırakırsan her bilgisayarda çalışır."); ImGui::Spacing(); kilitBloku(d); } void sihirbazAdim4(UreticiDurum& d) { const ui::Tema& t = ui::tema(); ImGui::TextColored(t.vurgu, "Özet"); ImGui::Text("Müşteri: %s · Seri: %s · Ürün: %s", d.musteri, d.seri, d.urun); sihirbazOzet(d); { const std::size_t n = std::strlen(d.makineler); int makine = 0; for (std::size_t i = 0; i < n; ++i) if (d.makineler[i] == '\n') ++makine; if (n && d.makineler[n - 1] != '\n') ++makine; ImGui::SameLine(); const std::string mm = makine ? (std::to_string(makine) + " makine") : std::string("kilit yok"); ui::rozet(mm.c_str(), makine ? t.basari : t.metinSoluk); } ImGui::Spacing(); imzaCiktiUretimBloku(d); ImGui::Spacing(); if (ui::katlanirBasla("Gövde önizleme (imzasız)", &d.sihirbazKota)) { ImGui::InputTextMultiline("##swOnizleme", d.onizleme.data(), d.onizleme.size() + 1, ImVec2(-1, 160.0f), ImGuiInputTextFlags_ReadOnly); ui::katlanirBitir(); } } void sihirbazCiz(UygulamaBaglam& b, UreticiDurum& d) { const ui::Tema& t = ui::tema(); // Adım şeridi: tamamlanan adıma geri dönmek serbest, ileri atlamak değil. for (int i = 0; i < ADIM_SAYI; ++i) { if (i) ImGui::SameLine(0.0f, 4.0f); ImGui::PushID(i); const bool aktif = (d.adim == i); const bool gecildi = (i < d.adim); ImGui::PushStyleColor(ImGuiCol_Text, aktif ? t.vurgu : (gecildi ? t.basari : t.metinSoluk)); if (ImGui::SmallButton(ADIM_BASLIK[i]) && i <= d.adim) d.adim = i; ImGui::PopStyleColor(); ImGui::PopID(); } ui::ilerleme(((float)d.adim + 1.0f) / (float)ADIM_SAYI, nullptr, -1.0f); ImGui::Spacing(); const float altYuk = 44.0f; const float govdeYuk = std::max(180.0f, ImGui::GetContentRegionAvail().y - altYuk); if (ImGui::BeginChild("##sihirbazGovde", ImVec2(0.0f, govdeYuk), ImGuiChildFlags_Borders)) { switch (d.adim) { case 0: sihirbazAdim0(d); break; case 1: sihirbazAdim1(b, d); break; case 2: sihirbazAdim2(d); break; case 3: sihirbazAdim3(d); break; default: sihirbazAdim4(d); break; } } ImGui::EndChild(); // Alt gezinme çubuğu const char* eksik = adimEksik(d, d.adim); ImGui::BeginDisabled(d.adim == 0); if (ui::buton("◀ Geri", ImVec2(110.0f, 0.0f), ui::ButonTip::Hayalet)) --d.adim; ImGui::EndDisabled(); ImGui::SameLine(); if (d.adim < ADIM_SAYI - 1) { ImGui::BeginDisabled(eksik != nullptr); if (ui::buton("İleri ▶", ImVec2(130.0f, 0.0f), ui::ButonTip::Birincil)) { ++d.adim; if (kirp(d.seri).empty()) metinKopyala(d.seri, sizeof d.seri, seriUret()); } ImGui::EndDisabled(); } else { soluk("Son adım — üretim düğmesi yukarıda."); } if (eksik) { ImGui::SameLine(); ImGui::TextColored(t.hata, "⛔ %s", eksik); } } } // namespace void lisansUreticiPenceresiCiz(UygulamaBaglam& b, bool& acik) { if (!acik) return; UreticiDurum& d = durum(); // Önizleme her karede yeniden kurulur: gövde birkaç KB, pencere yalnız üretici // makinede ve elle açılır — sıcak yol DEĞİL. (std::string kapasitesi yeniden // kullanıldığı için pratikte tahsis olmaz.) d.onizleme = govdeKur(d); // Görünüm = CAM Simülasyon penceresiyle AYNI FikoUi "WebCam" skin'i (teal cam zemin + // cyan kenar + arka plan grid + pill widget). Grid'i skin çizer (web_skin.cpp) — // burada ayrı çizim yok, tek kaynak korunur. ui::Pencere p{"🔐 Lisans Üretici###lisans_uretici", &acik, {.boyut = {1240, 760}, .minBoyut = {900, 480}, .skin = ui::PencereSkin::WebCam}}; if (!p) return; const ui::Tema& t = ui::tema(); ImGui::TextColored(t.uyari, "⚠ ÜRETİCİ ARACI — özel anahtar bu makinede kalır, müşteriye " "yalnız .fkl dosyası gider."); { // Sağ üst: "nasıl çalışır" 4 adım const float bosluk = ImGui::GetContentRegionAvail().x; if (bosluk > 40.0f) { ImGui::SameLine(ImGui::GetCursorPosX() + bosluk - 26.0f); ImGui::TextDisabled("(?)"); ipucu("NASIL ÇALIŞIR — 4 adım\n" "1) Müşteriden makine parmak izini al (uygulamasındaki Lisans penceresi verir).\n" "2) Hazır şablonla başla, ortadan paket/komut/kota kur.\n" "3) Sağdan müşteri + seri no + çıktı yolunu doldur → 'Lisans Üret'.\n" "4) Üretilen .fkl dosyasını müşteriye gönder (özel anahtar ASLA gitmez)."); } } ui::neonAyrac(); // Kip anahtarı: sihirbaz (adım adım) ↔ uzman (3 sütun). İkisi AYNI durumu düzenler. if (ui::buton(d.sihirbaz ? "🛠 Uzman kipine geç" : "🧙 Sihirbaz kipine geç", ImVec2(190.0f, 0.0f), ui::ButonTip::Hayalet)) d.sihirbaz = !d.sihirbaz; ipucu("Sihirbaz: 5 adımda soru-cevap. Uzman: tüm alanlar tek ekranda.\n" "Kip değiştirmek hiçbir alanı kaybettirmez."); ImGui::SameLine(0.0f, 10.0f); if (ImGui::SmallButton("🧹 Boşalt##kip")) { durumBosalt(d); d.sonSablon = -1; d.adim = 0; d.onizleme = govdeKur(d); } ipucu("Tüm müşteri/paket/komut/kota alanlarını sıfırlar.\n" "Anahtar ve çıktı yolu KORUNUR (bunlar makine ayarıdır)."); if (d.sihirbaz) { ImGui::Spacing(); sihirbazCiz(b, d); return; } // Hazır şablon şeridi: tek tıkla makul bir lisans kurar (SABLONLAR dizisi veri-odaklı). ImGui::TextColored(t.vurgu, "⚡ Hazır şablon:"); for (int i = 0; i < SABLON_SAYI; ++i) { ImGui::SameLine(0.0f, 4.0f); ImGui::PushID(i); // etiketler benzersiz olsa da satır kimliğini indekse bağla if (ImGui::SmallButton(SABLONLAR[i].etiket)) { sablonUygula(d, SABLONLAR[i]); d.onizleme = govdeKur(d); // aynı karede önizleme tazelensin ui::toastGoster("Şablon uygulandı — müşteri adını doldurmayı unutmayın", ui::ToastTip::Basari, 4.0f); } ipucu(SABLONLAR[i].ipucu); ImGui::PopID(); } ImGui::SameLine(0.0f, 10.0f); if (ImGui::SmallButton("🧹 Boşalt")) { durumBosalt(d); d.onizleme = govdeKur(d); } ipucu("Tüm müşteri/paket/komut/kota alanlarını sıfırlar.\n" "Anahtar ve çıktı yolu KORUNUR (bunlar makine ayarıdır)."); ImGui::Spacing(); // Sütun yüksekliği ÖNCE ölçülür: tablo hücresi içinde BeginChild(y=0) "kalan alan" // demektir ve tablo satırı içeriğe göre büyüdüğü için birbirini kovalar (sütunlar // ezilir). Açık yükseklik vererek üç sütun da pencereye oturur. const float sutunYuk = std::max(160.0f, ImGui::GetContentRegionAvail().y - 4.0f); // NoSavedSettings KALDIRILDI: kullanıcı sütun genişliklerini ayarlayınca (dar ekranda // sıkışma) tercih .ini'ye yazılsın, her açılışta yeniden ayarlamasın. if (ImGui::BeginTable("##uretici3", 3, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV, ImVec2(0.0f, sutunYuk))) { ImGui::TableSetupColumn("katalog", ImGuiTableColumnFlags_WidthStretch, 1.00f); ImGui::TableSetupColumn("paketler", ImGuiTableColumnFlags_WidthStretch, 1.05f); ImGui::TableSetupColumn("uretim", ImGuiTableColumnFlags_WidthStretch, 1.15f); ImGui::TableNextRow(); const ImVec2 cocukBoyut{0.0f, sutunYuk - 8.0f}; ImGui::TableSetColumnIndex(0); if (ImGui::BeginChild("##sol", cocukBoyut)) solSutun(b, d); ImGui::EndChild(); ImGui::TableSetColumnIndex(1); if (ImGui::BeginChild("##orta", cocukBoyut)) ortaSutun(d); ImGui::EndChild(); ImGui::TableSetColumnIndex(2); if (ImGui::BeginChild("##sag", cocukBoyut)) sagSutun(d); ImGui::EndChild(); ImGui::EndTable(); } } } // namespace fiko