#include "calisma_alani_penceresi.hpp" #include #include #include #include #include #include #include #include #include #include #include "catia_kesif.hpp" #include "calisma_alani_plan.hpp" #include "fiko/ui/bilesen_ek.hpp" // toastGoster, katlanirBasla/Bitir, bosDurum #include "fiko/ui/form.hpp" // bilgiSeridi #include "fiko/ui/pencere_cerceve.hpp" #include "imgui.h" #include "sanal_masaustu.hpp" #include "uygulama_baglam.hpp" #include "windows_surum.hpp" namespace fiko { namespace { namespace fs = std::filesystem; bool g_acik = false; bool g_pin = false; // Diskteki plan dosyaları (calisma_alanlari/*.json), en yeni üstte. std::vector g_planDosyalari; int g_secilenPlan = -1; CalismaAlaniPlani g_plan; bool g_planYuklendi = false; CatiaKesifSonucu g_guncel; bool g_guncelYuklendi = false; // Her plan oturumu için UI seçim/duzenleme durumu (g_plan.oturumlar ile aynı sırada). struct OturumUiDurum { bool secili = true; bool katmanAcik = true; // katlanirBasla persistan durumu (nullptr veremez — dereflenir) int masaustuKip = 0; // 0 = Kayıtlı masaüstü, 1 = Şu anki masaüstü std::vector belgeSecili; std::vector surecYoluBuf; // ImGui::InputText tamponu (surecYolu) std::vector> belgeYolBuf; // her belge için InputText tamponu }; std::vector g_oturumUi; std::mutex g_logMutex; // logEkle arka plan thread'inden de çağrılır (bkz. oturumuAc) std::vector g_durumLog; void logEkle(const std::string& s) { std::lock_guard kilit(g_logMutex); g_durumLog.push_back(s); if (g_durumLog.size() > 200) g_durumLog.erase(g_durumLog.begin()); } } // namespace namespace { // calisma_alanlari/*.json dosyalarını değişim zamanına göre (en yeni önce) listeler. std::vector planDosyalariniListele() { std::vector> gecici; std::error_code ec; const fs::path klasor = calismaAlaniKlasoru(); for (auto& g : fs::directory_iterator(klasor, ec)) { if (g.path().extension() == ".json") gecici.emplace_back(g.last_write_time(ec), g.path().string()); } std::sort(gecici.begin(), gecici.end(), [](const auto& a, const auto& b) { return a.first > b.first; }); std::vector sonuc; sonuc.reserve(gecici.size()); for (auto& [_, yol] : gecici) sonuc.push_back(yol); return sonuc; } void planiYukle(int index) { g_planYuklendi = false; g_oturumUi.clear(); if (index < 0 || index >= static_cast(g_planDosyalari.size())) return; std::string hata; if (!calismaAlaniPlaniOku(g_planDosyalari[index], g_plan, hata)) { logEkle("❌ Plan okunamadı: " + hata); return; } g_planYuklendi = true; g_secilenPlan = index; g_oturumUi.resize(g_plan.oturumlar.size()); for (size_t i = 0; i < g_plan.oturumlar.size(); ++i) { auto& po = g_plan.oturumlar[i]; auto& ui = g_oturumUi[i]; ui.belgeSecili.assign(po.belgeler.size(), true); const size_t yolTampon = std::max(po.surecYolu.size() + 1, 512); ui.surecYoluBuf.assign(yolTampon, '\0'); std::copy(po.surecYolu.begin(), po.surecYolu.end(), ui.surecYoluBuf.begin()); ui.belgeYolBuf.resize(po.belgeler.size()); for (size_t j = 0; j < po.belgeler.size(); ++j) { const size_t bt = std::max(po.belgeler[j].tamYol.size() + 1, 512); ui.belgeYolBuf[j].assign(bt, '\0'); std::copy(po.belgeler[j].tamYol.begin(), po.belgeler[j].tamYol.end(), ui.belgeYolBuf[j].begin()); } } } void guncelKesfetVeGoster() { ComOturum com; if (!com.basariliMi()) { logEkle("❌ COM başlatılamadı — güncel durum keşfedilemedi"); return; } g_guncel = catiaKesifYap(); g_guncelYuklendi = true; } // Dosya seçme diyaloğu (CATIA exe veya belge) — istakip_kayit_penceresi.cpp'deki // belgeYoluSec() kalıbıyla aynı (GetOpenFileNameW → UTF-8). std::string dosyaSecDiyalogu(const wchar_t* filtre) { 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.Flags = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST; if (!GetOpenFileNameW(&ofn)) return {}; const int bayt = WideCharToMultiByte(CP_UTF8, 0, yol, -1, nullptr, 0, nullptr, nullptr); if (bayt <= 1) return {}; std::string s(static_cast(bayt) - 1, 0); WideCharToMultiByte(CP_UTF8, 0, yol, -1, s.data(), bayt, nullptr, nullptr); return s; } } // namespace namespace { // Bir komut satırı argümanını Windows kurallarına göre tırnaklar (boşluk/tırnak içerebilir). std::wstring argKac(const std::wstring& a) { if (!a.empty() && a.find_first_of(L" \t\"") == std::wstring::npos) return a; std::wstring o = L"\""; for (wchar_t c : a) { if (c == L'"') o += L"\\\""; else o += c; } return o + L"\""; } std::wstring utf8ToW(const std::string& s) { if (s.empty()) return {}; const int n = MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), nullptr, 0); std::wstring w(n, L'\0'); MultiByteToWideChar(CP_UTF8, 0, s.data(), (int)s.size(), w.data(), n); return w; } void pencereGeometrisiUygula(HWND h, const PlanOturum& po); // ileri bildirim (asagida tanimli) // GERÇEK eylem: CNEXT.exe'yi başlatır, sonra ARKA PLAN thread'inde COM // hazır olana kadar bekleyip Documents.Open() ile seçili belgeleri açar. // DÜZELTME (bu oturumda kullanıcı bildirdi): dosya yollarını CreateProcess // komut satırı ARGÜMANI olarak vermek CATIA'yı BOŞ açıyordu — CNEXT.exe // argümanları belge yükleme için kullanmıyor. Artık süreç argümansız // başlatılıyor, COM Application nesnesi hazır olunca gerçek // Application.Documents.Open(yol) çağrılıyor (bkz. catia_kesif.cpp). // UI thread'i BLOKLANMASIN diye bekleme+açma std::thread'e alındı (Open() // büyük CATProduct'larda uzun sürebilir, poll de 45 sn'ye kadar çıkabilir). void oturumuAc(const PlanOturum& po, const OturumUiDurum& ui) { if (po.surecYolu.empty()) { logEkle("❌ \"" + po.pencereBasligi + "\": süreç yolu boş, açılamıyor"); return; } if (!fs::exists(po.surecYolu)) { logEkle("❌ \"" + po.pencereBasligi + "\": CNEXT.exe bulunamadı → " + po.surecYolu); return; } std::vector seciliYollar; for (size_t i = 0; i < po.belgeler.size(); ++i) { if (i >= ui.belgeSecili.size() || !ui.belgeSecili[i]) continue; if (po.belgeler[i].tamYol.empty()) continue; if (!fs::exists(po.belgeler[i].tamYol)) { logEkle("⚠ \"" + po.belgeler[i].ad + "\" atlandı — dosya bulunamadı: " + po.belgeler[i].tamYol); continue; } seciliYollar.push_back(po.belgeler[i].tamYol); } std::wstring komutSatiri = argKac(utf8ToW(po.surecYolu)); // ARTIK belge argümanı YOK STARTUPINFOW si{}; si.cb = sizeof(si); PROCESS_INFORMATION pi{}; std::vector tampon(komutSatiri.begin(), komutSatiri.end()); tampon.push_back(L'\0'); const BOOL ok = CreateProcessW(nullptr, tampon.data(), nullptr, nullptr, FALSE, 0, nullptr, nullptr, &si, &pi); if (!ok) { logEkle("❌ \"" + po.pencereBasligi + "\": CreateProcess başarısız (GetLastError=" + std::to_string(GetLastError()) + ")"); return; } CloseHandle(pi.hThread); const DWORD pid = pi.dwProcessId; const std::string baslikKopya = po.pencereBasligi; logEkle("⏳ \"" + baslikKopya + "\" başlatıldı (PID " + std::to_string(pid) + ") — COM arayüzü hazır olması bekleniyor (bu 45 sn'ye kadar sürebilir)…"); HANDLE surecTutamagi = pi.hProcess; // thread bitene kadar kapatma const int masaustuKip = ui.masaustuKip; const PlanOturum poKopya = po; // thread'e deger olarak tasinir (referans omru garanti degil) std::thread([pid, seciliYollar, baslikKopya, surecTutamagi, masaustuKip, poKopya]() { ComOturum com; // bu thread'e ÖZEL COM apartment — ana UI thread'inden ayrı if (!com.basariliMi()) { logEkle("❌ \"" + baslikKopya + "\": arka plan iş parçacığında COM başlatılamadı"); CloseHandle(surecTutamagi); return; } void* uygulama = nullptr; const int denemeSayisi = 30; // 30 * 1500 ms ≈ 45 sn azami bekleme for (int i = 0; i < denemeSayisi && !uygulama; ++i) { Sleep(1500); DWORD kod = 0; if (GetExitCodeProcess(surecTutamagi, &kod) && kod != STILL_ACTIVE) { logEkle("❌ \"" + baslikKopya + "\": CATIA süreci COM hazır olmadan kapandı"); CloseHandle(surecTutamagi); return; } uygulama = catiaUygulamasiPidIle(pid); } if (!uygulama) { logEkle("❌ \"" + baslikKopya + "\": ~45 sn içinde COM arayüzü hazır olmadı — belgeler " "açılamadı (CATIA hâlâ yükleniyor olabilir, elle deneyin)"); CloseHandle(surecTutamagi); return; } // Faz 2 — sanal masaüstü yerleştirme (COM hazır olduysa pencere de hazırdır). void* hwndFaz2 = nullptr; // asagida geometri uygulamak icin de kullanilir if (masaustuKip == 1) { // "Şu anki masaüstü": hiçbir şey yapma — yeni pencere zaten aktif masaüstünde açılır. } else if (masaustuKip == 0 && poKopya.masaustuGuid.empty()) { logEkle("ℹ \"" + baslikKopya + "\": planda kayıtlı masaüstü bilgisi yok — şu anki " "masaüstünde kalıyor"); } else { void* hwnd = catiaPencereBulPidIle(pid); hwndFaz2 = hwnd; if (!hwnd) { logEkle("⚠ \"" + baslikKopya + "\": masaüstü yerleştirme için pencere bulunamadı — CATIA şu anki masaüstünde " "kaldı"); } else if (masaustuKip == 2) { std::string yeniGuid; MasaustuDurum d = masaustuOlustur(yeniGuid); if (d != MasaustuDurum::Ok) { logEkle("❌ \"" + baslikKopya + "\": yeni masaüstü oluşturulamadı — " + masaustuDurumMetni(d)); } else { MasaustuDurum d2 = pencereyiMasaustunaTasi(hwnd, yeniGuid); if (d2 == MasaustuDurum::Ok) logEkle("✅ \"" + baslikKopya + "\": yeni masaüstüne taşındı (" + yeniGuid + ")"); else logEkle("❌ \"" + baslikKopya + "\": yeni masaüstü oluştu (" + yeniGuid + ") ama pencere taşınamadı — " + masaustuDurumMetni(d2)); } } else { // masaustuKip == 0, guid dolu MasaustuDurum d = pencereyiMasaustunaTasi(hwnd, poKopya.masaustuGuid); if (d == MasaustuDurum::Ok) { logEkle("✅ \"" + baslikKopya + "\": kayıtlı masaüstüne taşındı"); } else if (poKopya.masaustuSira >= 0) { // YEDEK: GUID artik gecerli degil (orn. reboot sonrasi sifirlandi) — // calisma_alani_plan.hpp'nin kendi tasarim notunda soz verilen "sira" // yedegine dus (bkz. docs/catia_calisma_alani.md). std::string siradanGuid; MasaustuDurum ds = masaustuSiradanGuidBul(poKopya.masaustuSira, siradanGuid); MasaustuDurum d3 = (ds == MasaustuDurum::Ok) ? pencereyiMasaustunaTasi(hwnd, siradanGuid) : ds; if (d3 == MasaustuDurum::Ok) { logEkle("✅ \"" + baslikKopya + "\": kayıtlı GUID (" + poKopya.masaustuGuid + ") artık geçersizdi, SIRA yedeği (#" + std::to_string(poKopya.masaustuSira) + ") ile taşındı"); } else { logEkle("❌ \"" + baslikKopya + "\": kayıtlı masaüstüne (" + poKopya.masaustuGuid + ") taşınamadı (" + masaustuDurumMetni(d) + "), sıra yedeği (#" + std::to_string(poKopya.masaustuSira) + ") de başarısız (" + masaustuDurumMetni(d3) + ") — CATIA şu anki masaüstünde kaldı"); } } else { logEkle("❌ \"" + baslikKopya + "\": kayıtlı masaüstüne (" + poKopya.masaustuGuid + ") taşınamadı — " + masaustuDurumMetni(d) + " (masaüstü artık mevcut olmayabilir, sıra yedeği de yok — CATIA şu anki " "masaüstünde kaldı)"); } } } // Faz 2 — kayıtlı pencere konum/boyutunu uygula (bkz. pencereGeometrisiUygula). if (poKopya.genislik > 0 && poKopya.yukseklik > 0) { void* hwnd = hwndFaz2 ? hwndFaz2 : catiaPencereBulPidIle(pid); if (hwnd) { pencereGeometrisiUygula(static_cast(hwnd), poKopya); logEkle("✅ \"" + baslikKopya + "\": kayıtlı pencere konum/boyutu uygulandı"); } else { logEkle("⚠ \"" + baslikKopya + "\": pencere konum/boyutu uygulanamadı — HWND bulunamadı"); } } if (seciliYollar.empty()) { logEkle("✅ \"" + baslikKopya + "\": COM bağlantısı kuruldu (açılacak belge seçilmemiş)"); } else { logEkle("✅ \"" + baslikKopya + "\": COM bağlantısı kuruldu, " + std::to_string(seciliYollar.size()) + " belge açılıyor…"); int basarili = 0; for (auto& yol : seciliYollar) { std::string hata; if (catiaBelgeAc(uygulama, yol, hata)) { ++basarili; logEkle(" ✅ açıldı: " + yol); } else { logEkle(" ❌ açılamadı: " + yol + " — " + hata); } } logEkle("\"" + baslikKopya + "\": " + std::to_string(basarili) + "/" + std::to_string(seciliYollar.size()) + " belge açıldı"); } reinterpret_cast(uygulama)->Release(); CloseHandle(surecTutamagi); }).detach(); } // Kayıtlı pencere konum/boyutunu (PlanOturum) GERÇEKTEN uygular — // SetWindowPlacement (GetWindowPlacement ile önce mevcut yerleşimi alıp // yalnız rcNormalPosition+showCmd'yi değiştiriyoruz, diğer alanlar OS'nin // kendi dahili durumu, elle uydurmuyoruz). genislik/yukseklik <= 0 ise // (eski/eksik plan) DOKUNULMAZ — sahte/rastgele geometri uygulanmaz. void pencereGeometrisiUygula(HWND h, const PlanOturum& po) { if (po.genislik <= 0 || po.yukseklik <= 0) return; WINDOWPLACEMENT wp{}; wp.length = sizeof(wp); if (!GetWindowPlacement(h, &wp)) return; wp.rcNormalPosition.left = po.x; wp.rcNormalPosition.top = po.y; wp.rcNormalPosition.right = po.x + po.genislik; wp.rcNormalPosition.bottom = po.y + po.yukseklik; wp.showCmd = po.maksimize ? SW_SHOWMAXIMIZED : (po.minimize ? SW_SHOWMINIMIZED : SW_SHOWNORMAL); SetWindowPlacement(h, &wp); } // Zaten AÇIK olan bir CATIA oturumunu (g_guncel keşfinden gelen hwnd ile) // öne alır + kayıtlı olduğu sanal masaüstüne geçer. Yeni süreç BAŞLATMAZ — // oturumuAc()'ten farklı olarak yalnızca MEVCUT pencereyle ilgilenir. // po != nullptr ise (plan eşleşmesi varsa) kayıtlı pencere konum/boyutunu da // uygular (bkz. pencereGeometrisiUygula) — yoksa geometri dokunulmaz. // DÜRÜST SINIR: SetForegroundWindow, Windows'un "foreground lock" kısıtı // yüzünden bazı durumlarda (örn. fikocad.exe şu an odakta değilse) sessizce // başarısız olabilir — bu API'nin kendi belgelenmiş davranışı, burada ekstra // bir hile (AttachThreadInput vb.) uygulanmadı. void oturumuAktifEt(const CatiaOturumBilgi& g, const PlanOturum* po = nullptr) { if (!g.hwnd) { logEkle("❌ \"" + g.pencereBasligi + "\": pencere kimliği (HWND) yok, aktif edilemiyor"); return; } HWND h = static_cast(g.hwnd); if (!IsWindow(h)) { logEkle("❌ \"" + g.pencereBasligi + "\": pencere artık geçerli değil (kapatılmış olabilir) — \"🔄 Şimdi Yeniden " "Keşfet\" ile listeyi tazele"); return; } std::string hedefGuid; MasaustuDurum d = pencereMasaustuGetir(h, hedefGuid); if (d == MasaustuDurum::Ok && !hedefGuid.empty()) { MasaustuDurum d2 = masaustunaGec(hedefGuid); if (d2 != MasaustuDurum::Ok) logEkle("⚠ \"" + g.pencereBasligi + "\": kayıtlı masaüstüne geçilemedi (" + masaustuDurumMetni(d2) + ") — yine de pencere öne alınmaya deneniyor"); } else { logEkle("⚠ \"" + g.pencereBasligi + "\": pencerenin masaüstü tespit edilemedi (" + masaustuDurumMetni(d) + ") — yalnız öne alınıyor, masaüstü değişmeyebilir"); } if (po) pencereGeometrisiUygula(h, *po); if (IsIconic(h)) ShowWindow(h, SW_RESTORE); const BOOL ok = SetForegroundWindow(h); if (ok) logEkle("🔼 \"" + g.pencereBasligi + "\" öne alındı"); else logEkle("⚠ \"" + g.pencereBasligi + "\": SetForegroundWindow reddetti (Windows'un odak kilidi kısıtı — fikocad.exe şu " "an odakta değilse beklenir bir durum, pencereyi görev çubuğundan elle tıkla)"); } void planiFarkliKaydet() { CalismaAlaniPlani duzenlenmis = g_plan; for (size_t i = 0; i < duzenlenmis.oturumlar.size() && i < g_oturumUi.size(); ++i) { // (Şimdilik) yol/masaüstü düzenlemeleri doğrudan g_plan üzerinde tutuluyor — // ImGui InputText hedefleri zaten g_plan.oturumlar[i].* alanlarına bağlı. } std::error_code ec; wchar_t ad[64]; SYSTEMTIME st; GetLocalTime(&st); swprintf(ad, 64, L"ozel_%04d%02d%02d_%02d%02d%02d.json", st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond); const std::string yol = (fs::path(calismaAlaniKlasoru()) / ad).string(); std::string hata; if (calismaAlaniPlaniYaz(duzenlenmis, yol, hata)) { logEkle("✅ Düzenlenmiş plan kaydedildi: " + yol); g_planDosyalari = planDosyalariniListele(); } else { logEkle("❌ Farklı kaydetme başarısız: " + hata); } } } // namespace void calismaAlaniPencereAc() { g_acik = true; g_planDosyalari = planDosyalariniListele(); if (!g_planDosyalari.empty() && g_secilenPlan < 0) planiYukle(0); guncelKesfetVeGoster(); } void calismaAlaniPenceresiCiz(UygulamaBaglam&) { if (!g_acik) return; ui::Pencere p{"🗂 Çalışma Alanı Yöneticisi###calisma_alani", &g_acik, {.boyut = {720, 620}, .minBoyut = {520, 400}, .pinDurum = &g_pin}}; if (!p) return; // ---- Üst bar: plan dosyası seçici ---- if (ImGui::Button("🔄 Şimdi Yeniden Keşfet")) guncelKesfetVeGoster(); ImGui::SameLine(); if (ImGui::Button("↻ Plan Listesini Tazele")) g_planDosyalari = planDosyalariniListele(); if (g_planDosyalari.empty()) { ui::bosDurum("🗂", "Kayıtlı çalışma alanı yok", "Önce \"Araçlar → 💾 Çalışma Alanını Kaydet\" ile bir plan oluştur."); return; } std::string mevcutEtiket = (g_secilenPlan >= 0) ? fs::path(g_planDosyalari[g_secilenPlan]).filename().string() : "(seçilmedi)"; if (ImGui::BeginCombo("Plan dosyası", mevcutEtiket.c_str())) { for (int i = 0; i < static_cast(g_planDosyalari.size()); ++i) { const std::string ad = fs::path(g_planDosyalari[i]).filename().string(); if (ImGui::Selectable(ad.c_str(), i == g_secilenPlan)) planiYukle(i); } ImGui::EndCombo(); } if (!g_planYuklendi) return; ImGui::TextDisabled("Kaydedildi: %s | %s", g_plan.olusturmaZamani.c_str(), g_plan.windowsSurumOzeti.c_str()); if (g_guncelYuklendi) ImGui::TextDisabled("Şu an açık CATIA oturumu: %d", (int)g_guncel.oturumlar.size()); if (ImGui::Button("☑ Tümünü Seç")) { for (auto& u : g_oturumUi) { u.secili = true; std::fill(u.belgeSecili.begin(), u.belgeSecili.end(), true); } } ImGui::SameLine(); if (ImGui::Button("☐ Tümünü Kaldır")) { for (auto& u : g_oturumUi) { u.secili = false; std::fill(u.belgeSecili.begin(), u.belgeSecili.end(), false); } } ImGui::Separator(); ImGui::BeginChild("oturum_liste", ImVec2(0, -160), true); for (size_t i = 0; i < g_plan.oturumlar.size(); ++i) { auto& po = g_plan.oturumlar[i]; auto& ui2 = g_oturumUi[i]; ImGui::PushID(static_cast(i)); ImGui::Checkbox("##sec", &ui2.secili); ImGui::SameLine(); bool acikMi = false; const CatiaOturumBilgi* eslesenOturum = nullptr; if (g_guncelYuklendi) { for (auto& g : g_guncel.oturumlar) if (g.pencereBasligi == po.pencereBasligi && g.surecYolu == po.surecYolu) { acikMi = true; eslesenOturum = &g; break; } } bool acildi = ui::katlanirBasla(po.pencereBasligi.c_str(), &ui2.katmanAcik); if (acildi) { ImGui::TextColored(acikMi ? ImVec4(0.35f, 0.78f, 0.42f, 1) : ImVec4(0.6f, 0.6f, 0.6f, 1), acikMi ? "● şu an açık" : "○ şu an kapalı"); if (acikMi && eslesenOturum) { ImGui::SameLine(); if (ImGui::SmallButton("🔼 Aktif Et")) oturumuAktifEt(*eslesenOturum, &po); if (ImGui::IsItemHovered()) ImGui::SetTooltip( "Yeni bir CATIA süreci BAŞLATMAZ — zaten açık olan bu pencereyi kayıtlı " "masaüstüne geçip öne alır, kayıtlı konum/boyutu uygular. RUNTIME test edilmedi."); } ImGui::TextUnformatted("Süreç yolu (CNEXT.exe)"); ImGui::SetNextItemWidth(-70); ImGui::InputText("##surecyolu", ui2.surecYoluBuf.data(), ui2.surecYoluBuf.size()); po.surecYolu = ui2.surecYoluBuf.data(); ImGui::SameLine(); if (ImGui::Button("📂##surecgozat")) { const std::string secilen = dosyaSecDiyalogu(L"Program (*.exe)\0*.exe\0Tüm dosyalar (*.*)\0*.*\0"); if (!secilen.empty()) { po.surecYolu = secilen; ui2.surecYoluBuf.assign(std::max(secilen.size() + 1, 512), '\0'); std::copy(secilen.begin(), secilen.end(), ui2.surecYoluBuf.begin()); } } if (!fs::exists(po.surecYolu)) ImGui::TextColored(ImVec4(0.86f, 0.36f, 0.36f, 1), "⚠ bulunamadı"); const char* kipler[] = {"Kayıtlı masaüstü (sıra bilgisi)", "Şu anki masaüstü", "🆕 Yeni masaüstü oluştur"}; ImGui::SetNextItemWidth(260); ImGui::Combo("Masaüstü", &ui2.masaustuKip, kipler, 3); ImGui::SameLine(); ImGui::TextDisabled("(?)"); if (ImGui::IsItemHovered()) ImGui::SetTooltip( "Faz 2 (IVirtualDesktopManagerInternal, build'e göre 3 farklı vtable) — henüz " "GERÇEK CATIA ile RUNTIME test edilmedi, yalnız derlendi. \"Kayıtlı masaüstü\": " "plandaki GUID artık yoksa (masaüstü silinmiş) DÜRÜSTÇE hata loglanır, sessizce " "\"şu anki\"ye düşülmez."); ImGui::SeparatorText("Belgeler"); if (!po.belgeler.empty()) { if (ImGui::SmallButton("Tümünü Seç##belge")) std::fill(ui2.belgeSecili.begin(), ui2.belgeSecili.end(), true); ImGui::SameLine(); if (ImGui::SmallButton("Tümünü Kaldır##belge")) std::fill(ui2.belgeSecili.begin(), ui2.belgeSecili.end(), false); } for (size_t j = 0; j < po.belgeler.size(); ++j) { auto& belge = po.belgeler[j]; ImGui::PushID(static_cast(j)); bool selBuf = (j < ui2.belgeSecili.size()) ? ui2.belgeSecili[j] : true; if (ImGui::Checkbox("##belgesec", &selBuf) && j < ui2.belgeSecili.size()) ui2.belgeSecili[j] = selBuf; ImGui::SameLine(); ImGui::TextUnformatted(belge.ad.c_str()); if (belge.aktif) { ImGui::SameLine(); ImGui::TextColored(ImVec4(0.95f, 0.75f, 0.2f, 1), "⭐"); } ImGui::SetNextItemWidth(-70); ImGui::InputText("##belgeyol", ui2.belgeYolBuf[j].data(), ui2.belgeYolBuf[j].size()); belge.tamYol = ui2.belgeYolBuf[j].data(); ImGui::SameLine(); if (ImGui::Button("📂")) { const std::string secilen = dosyaSecDiyalogu( L"CAD belgeleri (*.fikocad;*.CATPart;*.CATProduct;*.stp;*.step)\0" L"*.fikocad;*.CATPart;*.CATProduct;*.stp;*.step\0Tüm dosyalar (*.*)\0*.*\0"); if (!secilen.empty()) { belge.tamYol = secilen; ui2.belgeYolBuf[j].assign(std::max(secilen.size() + 1, 512), '\0'); std::copy(secilen.begin(), secilen.end(), ui2.belgeYolBuf[j].begin()); } } if (!belge.tamYol.empty() && !fs::exists(belge.tamYol)) ImGui::TextColored(ImVec4(0.86f, 0.36f, 0.36f, 1), " ⚠ dosya bulunamadı: %s", belge.tamYol.c_str()); ImGui::PopID(); } if (ImGui::Button("▶ Bu oturumu aç")) oturumuAc(po, ui2); ui::katlanirBitir(); } ImGui::PopID(); } ImGui::EndChild(); // ---- Alt bar: toplu eylemler ---- if (ImGui::Button("▶ Seçilenleri Aç")) { for (size_t i = 0; i < g_plan.oturumlar.size(); ++i) if (g_oturumUi[i].secili) oturumuAc(g_plan.oturumlar[i], g_oturumUi[i]); } ImGui::SameLine(); if (ImGui::Button("▶▶ Tümünü Aç")) { for (size_t i = 0; i < g_plan.oturumlar.size(); ++i) oturumuAc(g_plan.oturumlar[i], g_oturumUi[i]); } ImGui::SameLine(); if (ImGui::Button("🔼 Açık Olanları Aktif Et")) { if (!g_guncelYuklendi) { logEkle("⚠ Önce \"🔄 Şimdi Yeniden Keşfet\" ile güncel durumu al"); } else { int adet = 0; for (size_t i = 0; i < g_plan.oturumlar.size(); ++i) { if (!g_oturumUi[i].secili) continue; const auto& po = g_plan.oturumlar[i]; for (auto& g : g_guncel.oturumlar) { if (g.pencereBasligi == po.pencereBasligi && g.surecYolu == po.surecYolu) { oturumuAktifEt(g, &po); ++adet; break; } } } if (adet == 0) logEkle("ℹ Seçili oturumlardan hiçbiri şu an açık değil"); } } if (ImGui::IsItemHovered()) ImGui::SetTooltip( "Seçili VE zaten açık olan oturumları sırayla öne alır + kayıtlı masaüstüne geçirir — " "kapalı olanlara dokunmaz (yeni süreç başlatmaz)."); ImGui::SameLine(); if (ImGui::Button("💾 Bu Düzenlemeyi Farklı Kaydet")) planiFarkliKaydet(); ImGui::Separator(); ImGui::TextUnformatted("Durum günlüğü"); ImGui::BeginChild("durum_log", ImVec2(0, 0), true); { std::lock_guard kilit(g_logMutex); for (auto& satir : g_durumLog) ImGui::TextWrapped("%s", satir.c_str()); if (!g_durumLog.empty()) ImGui::SetScrollHereY(1.0f); } ImGui::EndChild(); } } // namespace fiko