PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
LauncherUI Class Reference

#include <LauncherUI.h>

Public Member Functions

 LauncherUI (LauncherConfig *config, SDL_Renderer *renderer)
 
 ~LauncherUI ()
 
void Render (bool *p_open)
 

Detailed Description

Definition at line 333 of file LauncherUI.h.

Constructor & Destructor Documentation

◆ LauncherUI()

LauncherUI::LauncherUI ( LauncherConfig config,
SDL_Renderer *  renderer 
)

Definition at line 130 of file LauncherUI.cpp.

131 : mConfig(config)
132 , mRenderer(renderer)
133 , mSelectedExperiment(-1)
134 , mFullscreen(false)
135 , mShowAbout(false)
136 , mScreenshotTexture(nullptr)
137 , mScreenshotWidth(0)
138 , mScreenshotHeight(0)
139 , mRunningExperiment(nullptr)
140 , mShowStderr(false)
141 , mOutputExpanded(false)
142 , mSelectedStudyIndex(-1)
143 , mSelectedChainIndex(-1)
144 , mSelectedStudyTestIndex(-1)
145 , mStudyTestScreenshot(nullptr)
146 , mStudyTestScreenshotW(0)
147 , mStudyTestScreenshotH(0)
148 , mRunningChain(false)
149 , mCurrentChainItemIndex(-1)
150 , mShowParameterEditor(false)
151 , mShowVariantNameDialog(false)
152 , mEditingTestIndex(-1)
153 , mEditingDefaultParams(true)
154 , mShowDuplicateSubjectWarning(false)
155 , mShowSnapshotCreated(false)
156 , mShowSettings(false)
157 , mShowNewStudyDialog(false)
158 , mShowNewChainDialog(false)
159 , mShowStudySettingsDialog(false)
160 , mShowFirstRunDialog(false)
161 , mShowGettingStartedDialog(false)
162 , mShowEditParticipantCodeDialog(false)
163 , mAddTestSubTab(0)
164 , mQuickLaunchSelectedFile(-1)
165 , mShowCodeEditor(false)
166 , mShowScaleBuilder(false)
167 , mSelectedScaleIndex(-1)
168 , mSelectedDimensionIndex(-1)
169 , mScaleBrowserScreenshot(nullptr)
170 , mScaleBrowserScreenshotW(0)
171 , mScaleBrowserScreenshotH(0)
172 , mScaleBrowserScreenshotForIndex(-1)
173 , mScaleTransSelectedKey(-1)
174{
175 mScaleTransLanguage[0] = '\0';
176 // Configure PEBL syntax highlighting based on doc/pebl.lang
177 auto lang = TextEditor::LanguageDefinition();
178
179 lang.mName = "PEBL";
180
181 // PEBL keywords (case-insensitive in actual language, but TextEditor is case-sensitive)
182 // Include both cases for common usage
183 lang.mKeywords = {
184 "if", "If", "IF",
185 "elseif", "ElseIf", "ELSEIF",
186 "else", "Else", "ELSE",
187 "loop", "Loop", "LOOP",
188 "while", "While", "WHILE",
189 "return", "Return", "RETURN",
190 "define", "Define", "DEFINE",
191 "and", "And", "AND",
192 "or", "Or", "OR",
193 "not", "Not", "NOT",
194 "break", "Break", "BREAK"
195 };
196
197 // PEBL uses # for single-line comments (shell-like)
198 lang.mSingleLineComment = "#";
199 lang.mCommentStart = ""; // No multi-line comments in PEBL
200 lang.mCommentEnd = "";
201
202 // PEBL-specific token regex patterns (regex, palette index)
203 lang.mTokenRegexStrings = {
204 { "\\b[0-9]*\\.[0-9]+\\b", TextEditor::PaletteIndex::Number }, // Floats first
205 { "\\b[0-9]+\\b", TextEditor::PaletteIndex::Number }, // Then integers
206 { "\\bg[A-Za-z0-9_]*(\\.[A-Za-z0-9_]+)?\\b", TextEditor::PaletteIndex::Identifier }, // Global variables
207 { "\\b[a-fh-z][A-Za-z0-9_]*(\\.[A-Za-z0-9_]+)?\\b", TextEditor::PaletteIndex::Identifier }, // Local variables
208 { ":?[A-Z][A-Za-z0-9_]*(?=\\s*\\()", TextEditor::PaletteIndex::KnownIdentifier }, // Function names
209 { "<-", TextEditor::PaletteIndex::Preprocessor }, // Assignment operator
210 };
211
212 lang.mCaseSensitive = true; // For variable names (g vs others)
213 lang.mAutoIndentation = true;
214
215 mCodeEditor.SetLanguageDefinition(lang);
216 mCodeEditor.SetShowWhitespaces(false);
217 mCodeEditor.SetTabSize(4);
219 // Initialize UI state from config
220 std::strncpy(mSubjectCode, config->GetSubjectCode().c_str(), sizeof(mSubjectCode) - 1);
221 mSubjectCode[sizeof(mSubjectCode) - 1] = '\0';
222 mParticipantCode[0] = '\0';
223 mStudyCode[0] = '\0';
224 mQuickLaunchPath[0] = '\0';
225 mQuickLaunchParamFile[0] = '\0';
226 mLastSnapshotName[0] = '\0';
227 mLastSnapshotPath[0] = '\0';
228
229 // Set Quick Launch to start in workspace directory
230 // Portable mode: portable root directory (for access to PEBL/battery, demo, tutorial)
231 // Installed mode: Documents/pebl-exp.2.4
232 std::string peblExpPath = config->GetWorkspacePath();
233 if (!peblExpPath.empty()) {
234 try {
235 if (fs::exists(peblExpPath) && fs::is_directory(peblExpPath)) {
236 mQuickLaunchDirectory = peblExpPath;
237
238 // Scan for .pbl files on startup
239 for (const auto& entry : fs::directory_iterator(peblExpPath)) {
240 if (!entry.is_regular_file()) continue;
241 std::string name = entry.path().filename().string();
242 if (name.length() > 4 && name.substr(name.length() - 4) == ".pbl") {
243 mQuickLaunchFiles.push_back(name);
244 }
245 }
246 std::sort(mQuickLaunchFiles.begin(), mQuickLaunchFiles.end());
247 } else {
248 // Fall back to config directory
249 mQuickLaunchDirectory = config->GetExperimentDirectory();
250 }
251 } catch (const fs::filesystem_error&) {
252 // Fall back to config directory
253 mQuickLaunchDirectory = config->GetExperimentDirectory();
254 }
255 }
256
257 std::strncpy(mLanguageCode, config->GetLanguage().c_str(), sizeof(mLanguageCode) - 1);
258 mLanguageCode[sizeof(mLanguageCode) - 1] = '\0';
259 std::strncpy(mExperimentDir, config->GetExperimentDirectory().c_str(), sizeof(mExperimentDir) - 1);
260 mExperimentDir[sizeof(mExperimentDir) - 1] = '\0';
261 mFullscreen = config->GetFullscreen();
262
263 // Initialize additional run settings
264 mScreenResolution = 0; // Default to first resolution (auto)
265 mVSync = false;
266 mGraphicsDriver[0] = '\0';
267 mCustomArguments[0] = '\0';
268
269 // Initialize study system
270 mWorkspace = std::make_shared<WorkspaceManager>();
271 mSnapshots = std::make_shared<SnapshotManager>();
272
273 // If the config has an explicit workspace path (loaded from saved settings), use that.
274 // This keeps WorkspaceManager in sync with LauncherConfig so scale tests and the UI
275 // settings panel both operate on the same directory.
276 if (!config->GetWorkspacePath().empty()) {
277 mWorkspace->SetWorkspacePath(config->GetWorkspacePath());
278 }
279
280 // Initialize scale manager with battery and workspace paths
281 std::string batteryPathForScales = config->GetBatteryPath();
282 std::string workspacePathForScales = mWorkspace->GetWorkspacePath();
283 if (!batteryPathForScales.empty()) {
284 mScaleManager = std::make_shared<ScaleManager>(batteryPathForScales, workspacePathForScales);
285 printf("Initialized Scale Manager with battery path: %s, workspace path: %s\n",
286 batteryPathForScales.c_str(), workspacePathForScales.c_str());
287 fflush(stdout);
288 } else {
289 printf("Warning: Battery path not set, Scale Builder will not be available\n");
290 fflush(stdout);
291 }
292
293 // Check for first run
294 if (mWorkspace->IsFirstRun()) {
295 mShowFirstRunDialog = true;
296 } else {
297 // Initialize workspace (creates directories) only if not first run
298 // On first run, we wait for user confirmation in the dialog
299 if (!mWorkspace->Initialize()) {
300 printf("Warning: Failed to initialize workspace\n");
301 }
302 }
303
304 // Initialize page editor state
305 mPageEditor.show = false;
306 mPageEditor.editingIndex = -1;
307 mPageEditor.title[0] = '\0';
308 mPageEditor.content[0] = '\0';
309 mPageEditor.pageType = 0;
310
311 // Initialize test editor state
312 mTestEditor.show = false;
313 mTestEditor.editingIndex = -1;
314 mTestEditor.selectedTestIndex = -1;
315 mTestEditor.selectedVariantIndex = 0; // Default variant
316 mTestEditor.language[0] = '\0';
317 mTestEditor.randomGroup = 0; // No randomization by default
318
319 // Initialize translation editor state (handled by constructor now)
320 // mTranslationEditor is default-constructed
321
322 // Initialize variant naming dialog
323 mVariantName[0] = '\0';
324
325 // Initialize top-level tab (default to Study)
326 mTopLevelTab = 0;
327
328 // Initialize new study dialog state
329 mNewStudyName[0] = '\0';
330 mNewStudyDescription[0] = '\0';
331 mNewStudyAuthor[0] = '\0';
332
333 // Initialize new chain dialog state
334 mNewChainName[0] = '\0';
335 mNewChainDescription[0] = '\0';
336
337 // Scan battery directory for tests
338 // Use battery path from config, fall back to experiment directory
339 std::string batteryPath = config->GetBatteryPath();
340 if (batteryPath.empty()) {
341 batteryPath = mExperimentDir;
342 }
343
344 // Store battery path for template loading
345 mBatteryPath = batteryPath;
346
347 if (!batteryPath.empty() && batteryPath.length() < sizeof(mExperimentDir)) {
348 std::strncpy(mExperimentDir, batteryPath.c_str(), sizeof(mExperimentDir) - 1);
349 mExperimentDir[sizeof(mExperimentDir) - 1] = '\0';
350 ScanExperimentDirectory(batteryPath);
351 printf("Scanned battery directory: %s - found %zu tests\n",
352 batteryPath.c_str(), mExperiments.size());
353 fflush(stdout);
354 }
355
356 // Scan templates directory
357 ScanTemplates();
358
359 // Restore previously selected study and chain
360 std::string lastStudyPath = config->GetCurrentStudyPath();
361 if (!lastStudyPath.empty()) {
362 printf("Restoring last study: %s\n", lastStudyPath.c_str());
363 LoadStudy(lastStudyPath);
364
365 // If study loaded successfully, restore the chain
366 if (mCurrentStudy) {
367 std::string lastChainName = config->GetCurrentChainName();
368 if (!lastChainName.empty()) {
369 std::string chainPath = lastStudyPath + "/chains/" + lastChainName;
370 printf("Restoring last chain: %s\n", chainPath.c_str());
371 LoadChain(chainPath);
372 }
373 }
374 }
375}
std::string GetBatteryPath() const
bool GetFullscreen() const
std::string GetExperimentDirectory() const
std::string GetLanguage() const
std::string GetSubjectCode() const
std::string GetCurrentChainName() const
std::string GetCurrentStudyPath() const
std::string GetWorkspacePath() const
static const Palette & GetDarkPalette()
void SetShowWhitespaces(bool aValue)
Definition TextEditor.h:230
void SetPalette(const Palette &aValue)
void SetTabSize(int aValue)
void SetLanguageDefinition(const LanguageDefinition &aLanguageDef)
char content[4096]
Definition LauncherUI.h:42
char title[256]
Definition LauncherUI.h:41
int selectedVariantIndex
Definition LauncherUI.h:51
char language[16]
Definition LauncherUI.h:52

References PageEditorState::content, PageEditorState::editingIndex, TestEditorState::editingIndex, LauncherConfig::GetBatteryPath(), LauncherConfig::GetCurrentChainName(), LauncherConfig::GetCurrentStudyPath(), TextEditor::GetDarkPalette(), LauncherConfig::GetExperimentDirectory(), LauncherConfig::GetFullscreen(), LauncherConfig::GetLanguage(), LauncherConfig::GetSubjectCode(), LauncherConfig::GetWorkspacePath(), TextEditor::Identifier, TextEditor::KnownIdentifier, TestEditorState::language, TextEditor::Number, PageEditorState::pageType, TextEditor::Preprocessor, TestEditorState::randomGroup, TestEditorState::selectedTestIndex, TestEditorState::selectedVariantIndex, TextEditor::SetLanguageDefinition(), TextEditor::SetPalette(), TextEditor::SetShowWhitespaces(), TextEditor::SetTabSize(), PageEditorState::show, TestEditorState::show, and PageEditorState::title.

◆ ~LauncherUI()

LauncherUI::~LauncherUI ( )

Definition at line 377 of file LauncherUI.cpp.

378{
379 FreeScreenshot();
380 FreeStudyTestScreenshot();
381
382 if (mScaleBrowserScreenshot) {
383 SDL_DestroyTexture(mScaleBrowserScreenshot);
384 mScaleBrowserScreenshot = nullptr;
385 }
386
387 // Clean up running experiment if any
388 if (mRunningExperiment) {
389 delete mRunningExperiment;
390 mRunningExperiment = nullptr;
391 }
392}

Member Function Documentation

◆ Render()

void LauncherUI::Render ( bool *  p_open)

Definition at line 394 of file LauncherUI.cpp.

395{
396 // Update running experiment output if any
397 if (mRunningExperiment && mRunningExperiment->IsRunning()) {
398 mRunningExperiment->UpdateOutput();
399 }
400
401 // Check if chain execution needs to advance to next item
402 if (mRunningChain && mRunningExperiment) {
403 bool isRunning = mRunningExperiment->IsRunning();
404 if (!isRunning) {
405 printf("DEBUG: Chain item finished (IsRunning=false), advancing...\n");
406
407 // Check exit code - only abort chain if it's a consent form with exit code 1
408 // Exit code 1 = user explicitly declined consent
409 // Other non-zero codes = crash or error, not consent decline
410 int exitCode = mRunningExperiment->GetExitCode();
411
412 // Debug logging to file and stdout
413 FILE* debugLog = fopen("chain_debug.log", "a");
414 if (debugLog) {
415 fprintf(debugLog, "=== Chain item finished ===\n");
416 fprintf(debugLog, " Exit code: %d\n", exitCode);
417 fprintf(debugLog, " mCurrentChainItemIndex: %d\n", mCurrentChainItemIndex);
418 fflush(debugLog);
419 }
420 printf("=== Chain item finished ===\n");
421 printf(" Exit code from GetExitCode(): %d\n", exitCode);
422 printf(" mCurrentChainItemIndex: %d\n", mCurrentChainItemIndex);
423
424 // Get the current chain item to check its type
425 bool shouldAbortChain = false;
426 bool isConsentDecline = false;
427 if (debugLog) {
428 fprintf(debugLog, " Checking: exitCode=%d, chainItemIndex=%d\n", exitCode, mCurrentChainItemIndex);
429 }
430 if (exitCode != 0 && mCurrentChain && mCurrentChainItemIndex >= 0 &&
431 mCurrentChainItemIndex < (int)mCurrentChain->GetItems().size()) {
432 const ChainItem& currentItem = mCurrentChain->GetItems()[mCurrentChainItemIndex];
433 if (debugLog) {
434 fprintf(debugLog, " Item type=%d (Consent=%d)\n", (int)currentItem.type, (int)ItemType::Consent);
435 fflush(debugLog);
436 }
437
438 // Treat exit code 1 as "declined/ineligible" for:
439 // - ItemType::Consent (built-in consent page)
440 // - ItemType::Test when exit code is 1 (ScaleRunner gateTriggered path)
441 // Other non-zero codes = crash/error, continue chain.
442 if (exitCode == 1 && (currentItem.type == ItemType::Consent ||
443 currentItem.type == ItemType::Test)) {
444 shouldAbortChain = true;
445 isConsentDecline = true;
446 if (debugLog) fprintf(debugLog, " -> CONSENT/GATE DECLINED (code 1), aborting chain\n");
447 } else if (currentItem.type == ItemType::Consent && exitCode != 0) {
448 if (debugLog) fprintf(debugLog, " -> Consent error (code %d), continuing\n", exitCode);
449 } else {
450 if (debugLog) fprintf(debugLog, " -> Non-consent item or error (code %d), continuing\n", exitCode);
451 }
452 } else {
453 if (debugLog) fprintf(debugLog, " exitCode==0 or invalid index, continuing chain\n");
454 }
455 if (debugLog) {
456 fflush(debugLog);
457 fclose(debugLog);
458 }
459
460 if (shouldAbortChain && isConsentDecline) {
461 // Exit code 1 on consent form - user explicitly declined consent
462 printf("Chain terminated: User declined consent\n");
463
464 // Accumulate output from this final item
465 mChainAccumulatedStdout += mRunningExperiment->GetStdout();
466 mChainAccumulatedStderr += mRunningExperiment->GetStderr();
467 mChainAccumulatedStdout += "\n=== Chain terminated: User declined consent ===\n";
468
469 // Stop chain execution
470 mRunningChain = false;
471 mCurrentChainItemIndex = -1;
472
473 // Increment participant counter so next run gets a fresh subject number
474 if (mCurrentChain) {
475 mCurrentChain->IncrementParticipantCounter();
476 printf("Participant counter incremented to: %d\n", mCurrentChain->GetParticipantCounter());
477 }
478
479 // Clean up runner
480 delete mRunningExperiment;
481 mRunningExperiment = nullptr;
482
483 // Return early - don't continue to next item
484 goto render_ui;
485 }
486
487 // Accumulate output from completed item BEFORE deleting runner
488 mChainAccumulatedStdout += mRunningExperiment->GetStdout();
489 mChainAccumulatedStderr += mRunningExperiment->GetStderr();
490
491 // Add separator between items for clarity
492 mChainAccumulatedStdout += "\n=== End of item " + std::to_string(mCurrentChainItemIndex + 1) + " ===\n\n";
493 mChainAccumulatedStderr += "\n=== End of item " + std::to_string(mCurrentChainItemIndex + 1) + " ===\n\n";
494
495 // Current item has finished, advance to next
496 printf("Chain item %d finished, advancing...\n", mCurrentChainItemIndex + 1);
497 mCurrentChainItemIndex++;
498
499 if (mCurrentChain && mCurrentChainItemIndex < (int)mCurrentChain->GetItems().size()) {
500 // Execute next item
501 const ChainItem& item = mCurrentChain->GetItems()[mCurrentChainItemIndex];
502 printf("Advancing to chain item %d/%zu: %s\n",
503 mCurrentChainItemIndex + 1,
504 mCurrentChain->GetItems().size(),
505 item.GetDisplayName().c_str());
506
507 // Clean up previous runner (output already accumulated)
508 delete mRunningExperiment;
509 mRunningExperiment = nullptr;
510
511 // Execute the next item
512 ExecuteChainItem(mCurrentChainItemIndex);
513 } else {
514 // Chain completed
515 printf("Chain execution completed (all %zu items finished)\n", mCurrentChain->GetItems().size());
516 mRunningChain = false;
517 mCurrentChainItemIndex = -1;
518
519 // Increment participant counter for next run
520 if (mCurrentChain) {
521 mCurrentChain->IncrementParticipantCounter();
522 printf("Participant counter incremented to: %d\n", mCurrentChain->GetParticipantCounter());
523 }
524
525 // Clean up runner (output already accumulated)
526 if (mRunningExperiment) {
527 delete mRunningExperiment;
528 mRunningExperiment = nullptr;
529 }
530 }
531 }
532 }
533
534render_ui:
535 // Main window takes up full viewport
536 ImGuiViewport* viewport = ImGui::GetMainViewport();
537 ImGui::SetNextWindowPos(viewport->WorkPos);
538 ImGui::SetNextWindowSize(viewport->WorkSize);
539
540 ImGuiWindowFlags window_flags = ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoTitleBar |
541 ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize |
542 ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoBringToFrontOnFocus;
543
544 ImGui::Begin("PEBL Launcher", p_open, window_flags);
545
546 RenderMenuBar();
547
548 // Reserve space at bottom for output panel, then wrap tab content
549 float outputPanelHeight = mOutputExpanded ? 250.0f : 30.0f;
550 float contentHeight = ImGui::GetContentRegionAvail().y - outputPanelHeight;
551 ImGui::BeginChild("MainTabArea", ImVec2(0, contentHeight));
552
553 // Top-level tabbed interface: Manage Studies vs Quick Launch
554 // Make tab headers larger and more prominent (colors, padding, and larger font)
555 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(30, 8)); // Larger horizontal and vertical padding
556 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 8)); // More spacing around tabs
557 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 8.0f); // Rounded corners for pill effect
558 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f)); // Darker blue for inactive
559 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f)); // Brighter blue on hover
560 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f)); // Vivid blue for active
561
562 if (ImGui::BeginTabBar("TopLevelTabs", ImGuiTabBarFlags_None)) {
563 // Manage Studies tab
564 ImGui::SetWindowFontScale(1.5f); // Large font for tab header
565 if (ImGui::BeginTabItem("Manage Studies")) {
566 if (mTopLevelTab != 1) { // Don't override if we're switching to Quick Launch
567 mTopLevelTab = 0;
568 }
569 ImGui::SetWindowFontScale(1.0f);
570 ImGui::PopStyleColor(3);
571 ImGui::PopStyleVar(3);
572
573 // Show study bar (study selector, new study button, etc.)
574 RenderStudyBar();
575
576 // Small spacing before second-level tabs
577 ImGui::Dummy(ImVec2(0, 5));
578
579 // Second-level tab styling
580 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 6));
581 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 6));
582 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 6.0f);
583 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
584 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
585 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
586
587 if (ImGui::BeginTabBar("StudyTabs", ImGuiTabBarFlags_None)) {
588 ImGui::SetWindowFontScale(1.3f); // Medium font for second-level tabs
589 if (ImGui::BeginTabItem("Tests")) {
590 ImGui::SetWindowFontScale(1.0f);
591 ImGui::PopStyleColor(3);
592 ImGui::PopStyleVar(3);
593
594 RenderTestsTab();
595 ImGui::EndTabItem();
596
597 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 6));
598 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 6));
599 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 6.0f);
600 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
601 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
602 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
603 ImGui::SetWindowFontScale(1.3f);
604 }
605
606 if (ImGui::BeginTabItem("Chains")) {
607 ImGui::SetWindowFontScale(1.0f);
608 ImGui::PopStyleColor(3);
609 ImGui::PopStyleVar(3);
610
611 RenderChainsTab();
612 ImGui::EndTabItem();
613
614 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 6));
615 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 6));
616 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 6.0f);
617 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
618 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
619 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
620 ImGui::SetWindowFontScale(1.3f);
621 }
622
623 if (ImGui::BeginTabItem("Run")) {
624 ImGui::SetWindowFontScale(1.0f);
625 ImGui::PopStyleColor(3);
626 ImGui::PopStyleVar(3);
627
628 RenderRunTab();
629 ImGui::EndTabItem();
630
631 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(20, 6));
632 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(8, 6));
633 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 6.0f);
634 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
635 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
636 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
637 }
638
639 // Final cleanup for second-level tabs
640 ImGui::SetWindowFontScale(1.0f);
641 ImGui::PopStyleColor(3);
642 ImGui::PopStyleVar(3);
643
644 ImGui::EndTabBar();
645 } else {
646 // If tab bar didn't begin, clean up styles
647 ImGui::SetWindowFontScale(1.0f);
648 ImGui::PopStyleColor(3);
649 ImGui::PopStyleVar(3);
650 }
651
652 ImGui::EndTabItem();
653
654 // Restore styles for next top-level tab header
655 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(30, 8));
656 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 8));
657 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 8.0f);
658 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
659 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
660 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
661 ImGui::SetWindowFontScale(1.5f);
662 }
663
664 // Quick Launch tab
665 ImGuiTabItemFlags quickLaunchFlags = (mTopLevelTab == 1) ? ImGuiTabItemFlags_SetSelected : 0;
666 if (ImGui::BeginTabItem("Quick Launch", nullptr, quickLaunchFlags)) {
667 if (mTopLevelTab == 1) {
668 mTopLevelTab = -1; // Reset flag after first frame
669 }
670 ImGui::SetWindowFontScale(1.0f);
671 ImGui::PopStyleColor(3);
672 ImGui::PopStyleVar(3);
673
674 RenderQuickLaunchTab();
675 ImGui::EndTabItem();
676
677 // Restore styles for next top-level tab header
678 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(30, 8));
679 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 8));
680 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 8.0f);
681 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
682 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
683 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
684 ImGui::SetWindowFontScale(1.5f);
685 }
686
687 // Scales/Surveys tab
688 ImGuiTabItemFlags scaleBuilderFlags = (mTopLevelTab == 2) ? ImGuiTabItemFlags_SetSelected : 0;
689 if (ImGui::BeginTabItem("Scales/Surveys", nullptr, scaleBuilderFlags)) {
690 if (mTopLevelTab == 2) {
691 mTopLevelTab = -1; // Reset flag after first frame
692 }
693 ImGui::SetWindowFontScale(1.0f);
694 ImGui::PopStyleColor(3);
695 ImGui::PopStyleVar(3);
696
697 ShowScaleBuilder();
698 ImGui::EndTabItem();
699
700 // Restore styles for consistency
701 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(30, 8));
702 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(12, 8));
703 ImGui::PushStyleVar(ImGuiStyleVar_TabRounding, 8.0f);
704 ImGui::PushStyleColor(ImGuiCol_Tab, ImVec4(0.35f, 0.40f, 0.48f, 1.0f));
705 ImGui::PushStyleColor(ImGuiCol_TabHovered, ImVec4(0.35f, 0.60f, 0.85f, 1.0f));
706 ImGui::PushStyleColor(ImGuiCol_TabActive, ImVec4(0.20f, 0.60f, 0.95f, 1.0f));
707 }
708
709 // Final cleanup - pop the styles that are still on the stack
710 ImGui::SetWindowFontScale(1.0f);
711 ImGui::PopStyleColor(3);
712 ImGui::PopStyleVar(3);
713
714 ImGui::EndTabBar();
715 } else {
716 // If tab bar didn't begin, clean up styles
717 ImGui::SetWindowFontScale(1.0f);
718 ImGui::PopStyleColor(3);
719 ImGui::PopStyleVar(3);
720 }
721
722 ImGui::EndChild(); // MainTabArea
723
724 // Output panel at bottom of window
725 RenderOutputPanel();
726
727 ImGui::End();
728
729 // Show about dialog if requested
730 if (mShowAbout) {
731 ShowAboutDialog();
732 }
733
734 // Show variant naming dialog if requested
735 if (mShowVariantNameDialog) {
736 ShowVariantNameDialog();
737 }
738
739 // Show parameter editor if requested
740 if (mShowParameterEditor) {
741 ShowParameterEditor();
742 }
743
744 // Show settings dialog if requested
745 if (mShowSettings) {
746 ShowSettingsDialog();
747 }
748
749 // Show page editor if requested
750 if (mPageEditor.show) {
751 ShowPageEditor();
752 }
753
754 // Show test editor if requested
755 if (mTestEditor.show) {
756 ShowTestEditor();
757 }
758
759 // Show code editor if requested
760 if (mShowCodeEditor) {
761 ShowCodeEditor();
762 }
763
764 // Show question editor dialog if requested
765 if (mQuestionEditor.show) {
766 ShowQuestionEditor();
767 }
768
769 // Show batch import dialog if requested
770 if (mBatchImport.show) {
771 ShowBatchImportDialog();
772 }
773
774 // Show dimension editor dialog if requested
775 if (mDimensionEditor.show) {
776 ShowDimensionEditor();
777 }
778
779 // Show create study from scale dialog if requested
780 if (mCreateStudyDialog.show) {
781 ShowCreateStudyFromScaleDialog();
782 }
783
784 // Show correct answers editor dialog if requested
785 if (mCorrectAnswersEditor.show) {
786 ShowCorrectAnswersEditor();
787 }
788
789 // Show norms editor dialog if requested
790 if (mNormsEditor.show) {
791 ShowNormsEditor();
792 }
793
794 // Show translation editor dialog if requested.
795 // When opened from inside the test editor popup (fromTestEditor), it renders
796 // inline there (correct ImGui stack level). Skip rendering it here in that case.
797 bool translationEditorWasShown = mTranslationEditor.show;
798 if (mTranslationEditor.show && !mTranslationEditor.fromTestEditor) {
799 ShowTranslationEditorDialog();
800 }
801 // When scale-mode translation editor closes, reload translations from disk into mCurrentScale
802 if (translationEditorWasShown && !mTranslationEditor.show &&
803 mTranslationEditor.scaleMode && mCurrentScale && mScaleManager) {
804 auto reloaded = mScaleManager->LoadScale(mCurrentScale->GetScaleInfo().code);
805 if (reloaded) {
806 mCurrentScale->GetTranslations() = reloaded->GetTranslations();
807 }
808 mTranslationEditor.ClearScaleMode();
809 }
810
811 // Show new study dialog if requested
812 if (mShowNewStudyDialog) {
813 ShowNewStudyDialog();
814 }
815
816 // Show new chain dialog if requested
817 if (mShowNewChainDialog) {
818 ShowNewChainDialog();
819 }
820
821 // Show study settings dialog if requested
822 if (mShowStudySettingsDialog) {
823 ShowStudySettingsDialog();
824 }
825
826 // Show first run dialog if requested
827 if (mShowFirstRunDialog) {
828 ShowFirstRunDialog();
829 }
830
831 // Show getting started dialog if there are no studies
832 if (mShowGettingStartedDialog) {
833 ShowGettingStartedDialog();
834 }
835
836 // Show duplicate subject code warning if requested
837 if (mShowDuplicateSubjectWarning) {
838 ShowDuplicateSubjectWarning();
839 }
840
841 // Show edit participant code dialog if requested
842 if (mShowEditParticipantCodeDialog) {
843 ShowEditParticipantCodeDialog();
844 }
845
846 // Show snapshot created dialog if requested
847 if (mShowSnapshotCreated) {
848 ShowSnapshotCreatedDialog();
849 }
850
851 // Show OpenScales browser if requested
852 mOpenScalesBrowser.Render();
853}
std::string GetStdout() const
int GetExitCode() const
std::string GetStderr() const
std::string GetDisplayName() const
Definition Chain.cpp:106
ItemType type
Definition Chain.h:26

References TranslationEditorState::ClearScaleMode(), Consent, TranslationEditorState::fromTestEditor, ChainItem::GetDisplayName(), ExperimentRunner::GetExitCode(), ExperimentRunner::GetStderr(), ExperimentRunner::GetStdout(), ExperimentRunner::IsRunning(), OpenScalesBrowser::Render(), TranslationEditorState::scaleMode, PageEditorState::show, TestEditorState::show, TranslationEditorState::show, QuestionEditorState::show, DimensionEditorState::show, BatchImportState::show, NormsEditorState::show, CorrectAnswersEditorState::show, CreateStudyFromScaleState::show, Test, ChainItem::type, and ExperimentRunner::UpdateOutput().

Referenced by main().


The documentation for this class was generated from the following files: