PEBL 2.2
Psychology Experiment Building Language - Cross-platform psychological experiment development system
LauncherUI.h
Go to the documentation of this file.
1// LauncherUI.h - User interface for PEBL Launcher
2// Copyright (c) 2026 Shane T. Mueller
3// Licensed under GPL
4
5#ifndef LAUNCHER_UI_H
6#define LAUNCHER_UI_H
7
8#include <string>
9#include <vector>
10#include <map>
11#include <memory>
12#include <SDL2/SDL.h>
13#include "TextEditor.h"
14#include "ScaleManager.h"
15#include "OpenScalesBrowser.h"
16
17class LauncherConfig;
18class Study;
19class Chain;
20class ChainItem;
22class SnapshotManager;
23
25 std::string path;
26 std::string name;
27 std::string directory;
28 std::string description;
29 std::string screenshotPath;
33};
34
35// Old ChainItem struct removed - using Chain.h ChainItem instead
36
37// Page editor dialog state
39 bool show;
40 int editingIndex; // -1 for new, >= 0 for editing existing
41 char title[256];
42 char content[4096];
43 int pageType; // 0=instruction, 1=consent, 2=completion
44};
45
46// Test editor dialog state (for adding/editing test items in chains)
48 bool show;
49 int editingIndex; // -1 for new, >= 0 for editing existing
50 int selectedTestIndex; // Index in study's test list
51 int selectedVariantIndex; // Index in test's parameter variants list
52 char language[16];
53 int randomGroup; // Randomization group ID (0 = no randomization)
54};
55
56// Translation editor dialog state
58 bool show;
59 int testIndex; // Which test we're editing translations for (-1 in scale mode)
60 char language[16]; // Target language to edit/create
61 char testPath[512]; // Full path to test directory (unused in scale mode)
62
63 // Scale mode: editing translations for a scale definition (not a battery test)
65 char scaleCode[64]; // Scale code, e.g. "GAD7"
66 char scaleDir[512]; // Full path to scale directory (translations live here directly)
67
68 // In-app translation editor data
70 bool dirty; // True if there are unsaved changes
71 int selectedKeyIndex; // Currently selected key in the list
72 std::vector<std::string> keys; // Keys in order
73 std::map<std::string, std::string> englishValues; // Key -> English text
74 std::map<std::string, std::string> targetValues; // Key -> Target language text (editable)
75
76 bool fromTestEditor; // True when opened from chain/test editor popup (renders inline there)
77
79 dataLoaded(false), dirty(false), selectedKeyIndex(-1),
80 fromTestEditor(false) {
81 language[0] = '\0';
82 testPath[0] = '\0';
83 scaleCode[0] = '\0';
84 scaleDir[0] = '\0';
85 }
86
87 void Clear() {
88 dataLoaded = false;
89 dirty = false;
91 keys.clear();
92 englishValues.clear();
93 targetValues.clear();
94 }
95
97 scaleMode = false;
98 scaleCode[0] = '\0';
99 scaleDir[0] = '\0';
100 }
101};
102
103// Condition editor entry (shared by question and dimension editors)
105 int sourceType; // 0=parameter, 1=question
106 char sourceName[64];
107 int op; // 0=equals, 1=not_equals, 2=greater_than, 3=less_than, 4=in, 5=not_in
108 char value[256];
109 EditorCondition() : sourceType(0), op(0) { sourceName[0]='\0'; value[0]='\0'; }
110};
111
112// Question editor dialog state
114 bool show;
115 int editingIndex; // -1 for new, >= 0 for editing existing
116 bool isSection; // true when editing/adding a section marker
117 bool isVirtualStart; // true when editing the implicit section-0 (insert at front on save)
118 char id[64];
119 char textKey[64];
120 char questionText[2048]; // Actual question text (from translation)
121 int questionType; // 0=likert, 1=multi, 2=multicheck, 3=vas, 4=short, 5=long
122
123 // Likert-specific fields
129 std::vector<bool> selectedResponseOptions; // Tracks which scale-level options are selected
130
131 // VAS-specific fields
134 char vasLeftLabel[256];
135 char vasRightLabel[256];
136 int vasOrientationIdx; // 0 = horizontal, 1 = vertical
137 struct AnchorEdit { float value; char label[256]; AnchorEdit() : value(0) { label[0] = '\0'; } };
138 std::vector<AnchorEdit> vasAnchors;
139
140 // Multi/multicheck-specific fields
141 char multiOptions[4096]; // Pipe-separated list of options (one per line for editing)
142
143 // Grid-specific fields
144 char gridColumns[2048]; // Column headers (one per line)
145 char gridRows[4096]; // Row labels/sub-questions (one per line)
146
147 // Image-specific fields
148 char imagePath[512]; // Path to image file
149
151 int requiredState; // -1 = use default, 0 = optional, 1 = required
152
153 // Conditional display
155 int visibleWhenLogic; // 0=AND, 1=OR
157 std::vector<EditorCondition> visibleWhenConditions;
158
159 // Answer alias — optional semantic name for {answer.alias} piping (S3)
160 char questionHead[256];
161 char answerAlias[64];
162
163 // Gate (blocking) — multi: exact match; short: numeric operator + threshold
165 char gateRequiredValue[64]; // multi: value that allows continuation
166 int gateOperator; // short: 0=greater_than,1=less_than,2=equals,3=not_equals
167 double gateValue; // short: numeric threshold
168 char gateTerminateMessageKey[64]; // Translation key for the termination message
169 char gateTerminateMessageText[512]; // Actual message text (English)
170
171 // Section-specific
172 bool sectionRevisable; // true = Back button allowed within this section (default true)
173 bool sectionRandomize; // true = shuffle non-fixed questions within section
174 char sectionRandomizeFixed[512]; // comma-separated question IDs to keep in fixed position
175
176 // Input validation — per-constraint (each has enabled flag, value, error message text)
183 bool valPatternEnabled; char valPattern[256]; char valPatternError[256];
186
191 hasGate(false), gateOperator(0), gateValue(0.0), sectionRevisable(true), sectionRandomize(false),
198 valPatternEnabled(false),
201 id[0] = '\0';
202 textKey[0] = '\0';
203 questionText[0] = '\0';
204 vasLeftLabel[0] = '\0';
205 vasRightLabel[0] = '\0';
206 multiOptions[0] = '\0';
207 gridColumns[0] = '\0';
208 gridRows[0] = '\0';
209 imagePath[0] = '\0';
210 questionHead[0] = '\0';
211 answerAlias[0] = '\0';
212 gateRequiredValue[0] = '\0';
213 gateTerminateMessageKey[0] = '\0';
214 gateTerminateMessageText[0] = '\0';
216 valMinWordsError[0] = valMaxWordsError[0] = '\0';
218 valPattern[0] = valPatternError[0] = '\0';
220 sectionRandomizeFixed[0] = '\0';
221 }
222};
223
224// Dimension editor dialog state
226 bool show;
227 int editingIndex; // -1 for new, >= 0 for editing existing
228 char id[64];
229 char name[256];
230 char abbreviation[64];
231 char description[512];
232
233 // Parameter-driven enable/disable
236 char enabledParam[128];
237
238 // Conditional display
240 int visibleWhenLogic; // 0=AND, 1=OR
241 std::vector<EditorCondition> visibleWhenConditions;
242
244 selectable(false), defaultEnabled(true),
246 id[0] = '\0';
247 name[0] = '\0';
248 abbreviation[0] = '\0';
249 description[0] = '\0';
250 enabledParam[0] = '\0';
251 }
252};
253
254// Batch import dialog state
256 bool show;
257 char questionText[8192]; // Multiline input for questions
258 char idPrefix[64]; // e.g., "moci"
259 char dimension[64]; // Common dimension
260 int questionType; // Type for all questions
261 int codingMode; // 0=all normal, 1=all reverse, 2=alternating
262 int startNumber; // Starting number for IDs (e.g., 1)
263
264 // Likert-specific configuration
265 int likertPreset; // 0=custom, 1=TRUE/FALSE, 2=Agree5, 3=Agree7, etc.
266 int likertPoints; // Number of response options
267 int likertMin; // Minimum value (-1 = use default)
268 int likertMax; // Maximum value (-1 = use default)
269 char likertLabels[512]; // Pipe-separated labels (e.g., "True|False")
270
273 questionText[0] = '\0';
274 idPrefix[0] = '\0';
275 dimension[0] = '\0';
276 likertLabels[0] = '\0';
277 }
278};
279
280// Norms editor dialog state
282 bool show = false;
283 std::string dimensionId;
284 std::string dimensionName;
286 float minVal = 0.0f;
287 float maxVal = 0.0f;
288 char label[128] = {};
289 };
290 std::vector<ThresholdEdit> rows;
291};
292
293// Correct answers editor dialog state (for sum_correct scoring)
295 bool show;
296 std::string questionId; // Which question we're editing answers for
297 std::string dimensionId; // Which dimension's scoring
298 std::string questionText; // Display text for context
299 std::string questionType; // Question type (short, multi, etc.)
300 std::vector<std::string> answers; // Individual answer patterns being edited
301 std::vector<bool> caseSensitive; // Per-answer case sensitivity flag
302
304};
305
306// Create Study from Scale dialog state
308 bool show;
309 char studyName[256];
310 char errorMessage[512];
311 bool confirmOverwrite; // True if waiting for user to confirm overwrite
312 bool needScaleSelection; // True if opened from Study Bar (need to select scale)
313 int selectedScaleIndex; // Index in scale list when needScaleSelection is true
314 bool addToExisting; // True = add to existing study, False = create new study
315 int selectedStudyIndex; // Index in study list when adding to existing
316
320 studyName[0] = '\0';
321 errorMessage[0] = '\0';
322 }
323};
324
325struct Parameter {
326 std::string name;
327 std::string value;
328 std::string defaultValue;
329 std::string description;
330 std::vector<std::string> options; // Optional list of allowed values
331};
332
334public:
335 LauncherUI(LauncherConfig* config, SDL_Renderer* renderer);
336 ~LauncherUI();
337
338 void Render(bool* p_open);
339
340private:
341 // Main UI components
342 void RenderMenuBar();
343 void RenderStudyBar();
344 void RenderTestsTab();
345 void RenderChainsTab();
346 void RenderRunTab();
347 void RenderQuickLaunchTab();
348 void RenderOutputPanel();
349
350 // Tests tab sub-components
351 void RenderTestsInStudy();
352 void RenderAddTestPanel();
353 void RenderBatteryBrowser();
354 void RenderScaleBrowser();
355 void RenderFileImport();
356 void RenderNewTestTemplate();
357
358 // Dialogs
359 void ShowAboutDialog();
360 void ShowParameterEditor();
361 void ShowVariantNameDialog();
362 void ShowSettingsDialog();
363 void ShowPageEditor();
364 void ShowTestEditor();
365 void ShowNewStudyDialog();
366 void ShowNewChainDialog();
367 void ShowStudySettingsDialog();
368 void ShowFirstRunDialog();
369 void ShowGettingStartedDialog();
370 void ShowDuplicateSubjectWarning();
371 void ShowEditParticipantCodeDialog();
372 void ShowCodeEditor();
373 void ShowTranslationEditorDialog();
374 void ShowSnapshotCreatedDialog();
375
376 // Scale Builder
377 void ShowScaleBuilder();
378 void RenderScaleList();
379 void RenderScaleInfoEditor();
380 void RenderQuestionsEditor();
381 void RenderScoringEditor();
382 void RenderTranslationsEditor();
383 void RenderSectionsTab();
384 void RenderParametersEditor();
385 void ShowQuestionEditor();
386 void RenderSectionEditorForm();
387 void RenderVisibleWhenEditor(QuestionEditorState& e);
388 void ShowBatchImportDialog();
389 void ShowDimensionEditor();
390 void ShowCreateStudyFromScaleDialog();
391 void ShowCorrectAnswersEditor();
392 void ShowNormsEditor();
393 void TestCurrentScale();
394
395 // Helper to load parameter editor after variant name is entered
396 void LoadParameterEditorForVariant();
397
398 // Legacy methods (will be refactored/removed)
399 void RenderFilePanel();
400 void RenderDetailsPanel();
401 void RenderDetailsTab();
402 void RenderStudyTab();
403 void RenderChainTab();
404
405 void ScanExperimentDirectory(const std::string& path);
406 void LoadExperimentInfo(const std::string& scriptPath);
407 void LoadScreenshot(const std::string& imagePath);
408 void FreeScreenshot();
409 void RunTest(); // Renamed from RunExperiment
410 void OpenDirectoryInFileBrowser(const std::string& path);
411 void OpenFileInTextEditor(const std::string& filePath);
412
413 // Utility launchers
414 void LaunchTranslationEditor();
415 void LaunchDataCombiner(const std::string& workingDirectory = "");
416
417 // Study management
418 void CreateNewStudy();
419 void ImportSnapshotFromPath(const std::string& snapshotPath);
420 void LoadStudy(const std::string& studyPath);
421 void AddTestToStudy();
422 void AddTestFromFile(const std::string& filePath);
423 void CreateTestFromTemplate(const std::string& testName, int templateType);
424 void CreateTestFromGenericStudy(const std::string& testName); // Copy complete battery/template structure
425 void RemoveTestFromStudy(const std::string& testName);
426 void ScanTemplates(); // Dynamically load available templates
427 void EditTestParameters(int testIndex);
428 void ScanParameterVariants(int testIndex);
429 bool SyncScaleSchema(const std::string& testDir, const std::string& scaleCode);
430
431 // Chain management (new system)
432 void CreateNewChain();
433 void LoadChain(const std::string& chainPath);
434 void SaveCurrentChain();
435 void AddInstructionPage();
436 void AddConsentPage();
437 void AddCompletionPage();
438 void AddTestToChain();
439 void RemoveChainItem(int index);
440 void MoveChainItemUp(int index);
441 void MoveChainItemDown(int index);
442 void MoveChainItemTo(int from, int to);
443 void EditChainItem(int index);
444 void TestChainItem(int index);
445 void RunChain();
446 void RunChainConfirmed(); // Actually runs chain after duplicate check
447 void ExecuteChainItem(int index);
448 std::vector<std::string> CheckExistingSubjectCodes();
449 std::vector<std::string> BuildAdditionalArguments();
450
451 // File dialogs
452 std::string OpenDirectoryDialog(const std::string& title = "Select Directory", const std::string& startDir = "");
453 std::string OpenFileDialog(const std::string& title = "Select File", const std::string& filter = "", const std::string& initialDir = "");
454 std::string SaveFileDialog(const std::string& title = "Save File", const std::string& defaultName = "");
455
456 LauncherConfig* mConfig;
457 SDL_Renderer* mRenderer;
458 std::vector<ExperimentInfo> mExperiments;
459 int mSelectedExperiment;
460
461 // UI State
462 char mSubjectCode[256]; // Legacy - now called participant code
463 char mParticipantCode[256]; // Auto-generated from study code + counter
464 char mStudyCode[5]; // 4-character study code prefix (editable)
465 char mLanguageCode[16];
466 bool mFullscreen;
467 int mScreenResolution; // Index into resolution list
468 bool mVSync;
469 char mGraphicsDriver[256];
470 char mCustomArguments[512];
471 bool mShowAbout;
472 char mExperimentDir[512];
473
474 // Available languages (populated from selected experiment)
475 std::vector<std::string> mAvailableLanguages;
476
477 // Screenshot display
478 SDL_Texture* mScreenshotTexture;
479 int mScreenshotWidth;
480 int mScreenshotHeight;
481
482 // Running experiment tracking
483 class ExperimentRunner* mRunningExperiment;
484 bool mShowStderr; // Toggle between stdout and stderr display
485 bool mOutputExpanded; // Toggle output window expansion
486
487 // Chain execution accumulated output (preserved across chain items)
488 std::string mChainAccumulatedStdout;
489 std::string mChainAccumulatedStderr;
490
491 // Study system
492 std::shared_ptr<Study> mCurrentStudy;
493 std::shared_ptr<Chain> mCurrentChain;
494 std::shared_ptr<WorkspaceManager> mWorkspace;
495 std::shared_ptr<SnapshotManager> mSnapshots;
496
497 // Study/chain UI state
498 std::vector<std::string> mStudyList;
499 int mSelectedStudyIndex;
500 std::vector<std::string> mChainList;
501 int mSelectedChainIndex;
502
503 // Study test preview state
504 int mSelectedStudyTestIndex;
505 std::string mStudyTestDescription; // about.txt content for selected study test
506 SDL_Texture* mStudyTestScreenshot;
507 int mStudyTestScreenshotW;
508 int mStudyTestScreenshotH;
509 void LoadStudyTestPreview(int testIndex);
510 void FreeStudyTestScreenshot();
511
512 // Chain execution
513 bool mRunningChain;
514 int mCurrentChainItemIndex;
515 std::vector<int> mChainExecutionOrder; // Randomized execution order (indices into chain items)
516
517 // Parameter editor
518 bool mShowParameterEditor;
519 bool mShowVariantNameDialog; // Prompt for variant name before editing
520 char mVariantName[256]; // User input for variant name (e.g., "mousebutton", "touchscreen")
521 int mEditingTestIndex; // Which test we're creating a variant for
522 bool mEditingDefaultParams; // True when editing default .pbl.par.json, false for named variant
523 std::vector<Parameter> mParameters;
524 std::string mParameterFile;
525
526 // Subject code duplicate warning
527 bool mShowDuplicateSubjectWarning;
528 std::vector<std::string> mDuplicateWarningCodes;
529
530 // Snapshot success dialog
531 bool mShowSnapshotCreated;
532 char mLastSnapshotName[512];
533 char mLastSnapshotPath[1024];
534
535 // Dialogs
536 bool mShowSettings;
537 bool mShowNewStudyDialog;
538 bool mShowNewChainDialog;
539 bool mShowStudySettingsDialog;
540 bool mShowFirstRunDialog;
541 bool mShowGettingStartedDialog; // Shown when no studies exist
542 bool mShowEditParticipantCodeDialog;
543 PageEditorState mPageEditor;
544 TestEditorState mTestEditor;
545 TranslationEditorState mTranslationEditor;
546
547 // Top-level tab state
548 int mTopLevelTab; // 0=Study, 1=Quick Launch
549
550 // Tests tab state
551 int mAddTestSubTab; // 0=Battery, 1=File, 2=New
552 char mNewStudyName[256];
553 char mNewStudyDescription[1024];
554 char mNewStudyAuthor[256];
555 char mNewChainName[256];
556 char mNewChainDescription[1024];
557
558 // Quick Launch tab state
559 char mQuickLaunchPath[512];
560 char mQuickLaunchParamFile[512];
561 std::string mQuickLaunchDirectory;
562 std::vector<std::string> mQuickLaunchFiles;
563 int mQuickLaunchSelectedFile;
564
565 // Template system (data-driven)
566 std::vector<std::string> mTemplateNames; // Display names
567 std::vector<std::string> mTemplateFiles; // Filenames
568 std::string mBatteryPath; // Path to battery directory
569
570 // Code editor
571 bool mShowCodeEditor;
572 std::string mCodeEditorFilePath;
573 TextEditor mCodeEditor;
574
575 // Scale Builder
576 bool mShowScaleBuilder;
577 std::shared_ptr<ScaleManager> mScaleManager;
578 OpenScalesBrowser mOpenScalesBrowser;
579 std::shared_ptr<ScaleDefinition> mCurrentScale;
580 std::vector<std::string> mScaleList;
581 int mSelectedScaleIndex;
582 int mNextSectionId = 1; // Auto-increment for generating "sec_1", "sec_2", etc.
583 int mDeleteConfirmIndex = -1; // Index of question pending delete confirmation (-1 = none)
584 int mSelectedBranchGroupIndex = -1; // Which branch group is selected in the list (-1 = none)
585 int mSelectedDimensionIndex; // For scoring editor
586 QuestionEditorState mQuestionEditor;
587 BatchImportState mBatchImport;
588 DimensionEditorState mDimensionEditor;
589 CreateStudyFromScaleState mCreateStudyDialog;
590 CorrectAnswersEditorState mCorrectAnswersEditor;
591 NormsEditorState mNormsEditor;
592
593 // Scale browser screenshot state
594 SDL_Texture* mScaleBrowserScreenshot;
595 int mScaleBrowserScreenshotW, mScaleBrowserScreenshotH;
596 int mScaleBrowserScreenshotForIndex; // -1 = none loaded
597
598 // Scale Builder translations tab state
599 char mScaleTransLanguage[16];
600 int mScaleTransSelectedKey;
601
602 // Loose OSD files found in workspace/scales/ (not yet installed into subdirectories)
603 std::vector<ScaleManager::LooseOSDEntry> mLooseOSDEntries;
604 bool mLooseOSDEntriesLoaded = false; // true once first scan has been done
605
606 // Install OSD dialog state
607 struct InstallOSDDialogState {
608 bool show = false;
610 char errorMessage[256] = {};
611 } mInstallOSDDialog;
612};
613
614
615#endif // LAUNCHER_UI_H
Definition Chain.h:60
void Render(bool *p_open)
Definition Study.h:44
char idPrefix[64]
Definition LauncherUI.h:258
char dimension[64]
Definition LauncherUI.h:259
char likertLabels[512]
Definition LauncherUI.h:269
char questionText[8192]
Definition LauncherUI.h:257
std::vector< std::string > answers
Definition LauncherUI.h:300
std::vector< bool > caseSensitive
Definition LauncherUI.h:301
std::vector< EditorCondition > visibleWhenConditions
Definition LauncherUI.h:241
char sourceName[64]
Definition LauncherUI.h:106
char value[256]
Definition LauncherUI.h:108
bool hasTranslations
Definition LauncherUI.h:31
std::string name
Definition LauncherUI.h:26
std::string description
Definition LauncherUI.h:28
std::string path
Definition LauncherUI.h:25
std::string directory
Definition LauncherUI.h:27
std::string screenshotPath
Definition LauncherUI.h:29
std::vector< ThresholdEdit > rows
Definition LauncherUI.h:290
std::string dimensionId
Definition LauncherUI.h:283
std::string dimensionName
Definition LauncherUI.h:284
char content[4096]
Definition LauncherUI.h:42
char title[256]
Definition LauncherUI.h:41
std::string name
Definition LauncherUI.h:326
std::vector< std::string > options
Definition LauncherUI.h:330
std::string value
Definition LauncherUI.h:327
std::string description
Definition LauncherUI.h:329
std::string defaultValue
Definition LauncherUI.h:328
char valPatternError[256]
Definition LauncherUI.h:183
char gridRows[4096]
Definition LauncherUI.h:145
char valMaxWordsError[256]
Definition LauncherUI.h:180
char gateTerminateMessageText[512]
Definition LauncherUI.h:169
std::vector< bool > selectedResponseOptions
Definition LauncherUI.h:129
char sectionRandomizeFixed[512]
Definition LauncherUI.h:174
char multiOptions[4096]
Definition LauncherUI.h:141
char valMinSelectedError[256]
Definition LauncherUI.h:184
char questionText[2048]
Definition LauncherUI.h:120
char gridColumns[2048]
Definition LauncherUI.h:144
char valMaxSelectedError[256]
Definition LauncherUI.h:185
char valNumberMinError[256]
Definition LauncherUI.h:181
char valNumberMaxError[256]
Definition LauncherUI.h:182
char gateRequiredValue[64]
Definition LauncherUI.h:165
char gateTerminateMessageKey[64]
Definition LauncherUI.h:168
char valMinWordsError[256]
Definition LauncherUI.h:179
char vasRightLabel[256]
Definition LauncherUI.h:135
std::vector< AnchorEdit > vasAnchors
Definition LauncherUI.h:138
char valMaxLengthError[256]
Definition LauncherUI.h:178
char vasLeftLabel[256]
Definition LauncherUI.h:134
std::vector< EditorCondition > visibleWhenConditions
Definition LauncherUI.h:157
char questionHead[256]
Definition LauncherUI.h:160
char valMinLengthError[256]
Definition LauncherUI.h:177
int selectedVariantIndex
Definition LauncherUI.h:51
char language[16]
Definition LauncherUI.h:52
std::map< std::string, std::string > targetValues
Definition LauncherUI.h:74
std::vector< std::string > keys
Definition LauncherUI.h:72
std::map< std::string, std::string > englishValues
Definition LauncherUI.h:73