@@ -10,6 +10,27 @@ struct ConfigSection {
10
10
std::map<std::string, std::string> keyValues; // Key-value pairs
11
11
};
12
12
13
+ /* *
14
+ * @class IniEditor
15
+ * @brief A class to edit INI configuration files with a graphical user interface using SDL2.
16
+ *
17
+ * The IniEditor class provides a graphical interface for editing INI configuration files.
18
+ * It uses SDL2 for rendering the UI and handling events, and SDL_ttf for text rendering.
19
+ *
20
+ * @details
21
+ * The class supports loading and saving INI files, displaying sections and key-value pairs,
22
+ * and providing tooltips for explanations of each configuration key. The user can interact
23
+ * with the UI to select sections, edit values, and save changes.
24
+ *
25
+ * @note
26
+ * - The class requires SDL2 and SDL_ttf libraries.
27
+ * - The font file path is hardcoded to "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf".
28
+ * - The class assumes a specific window size and layout for the UI elements.
29
+ *
30
+ * @example
31
+ * IniEditor editor("config.ini");
32
+ * editor.run();
33
+ */
13
34
class IniEditor {
14
35
SDL_Window* window = nullptr ;
15
36
SDL_Renderer* renderer = nullptr ;
@@ -105,69 +126,69 @@ class IniEditor {
105
126
106
127
private:
107
128
void loadIniFile (const std::string& filename) {
108
- std::ifstream file (filename);
129
+ std::ifstream file (filename); // Open the INI file
109
130
if (!file.is_open ()) {
110
- std::cerr << " Failed to open " << filename << std::endl;
131
+ std::cerr << " Failed to open " << filename << std::endl; // Error if file cannot be opened
111
132
return ;
112
133
}
113
134
std::string line;
114
135
while (std::getline (file, line)) {
115
136
line.erase (0 , line.find_first_not_of (" \t " )); // Trim leading whitespace
116
137
if (line.empty () || line[0 ] == ' ;' ) continue ; // Skip comments/blank lines
117
- if (line[0 ] == ' [' && line.back () == ' ]' ) {
118
- currentSection = line.substr (1 , line.size () - 2 );
119
- sections.push_back (currentSection);
120
- iniData[currentSection] = ConfigSection ();
121
- } else if (!currentSection.empty () && line.find (' =' ) != std::string::npos) {
122
- auto pos = line.find (' =' );
123
- std::string key = line.substr (0 , pos);
124
- std::string value = line.substr (pos + 1 );
125
- key.erase (key.find_last_not_of (" \t " ) + 1 ); // Trim trailing whitespace
126
- value.erase (0 , value.find_first_not_of (" \t " )); // Trim leading whitespace
127
- iniData[currentSection].keyValues [key] = value;
138
+ if (line[0 ] == ' [' && line.back () == ' ]' ) { // Section header
139
+ currentSection = line.substr (1 , line.size () - 2 ); // Extract section name
140
+ sections.push_back (currentSection); // Add section to list
141
+ iniData[currentSection] = ConfigSection (); // Initialize section in map
142
+ } else if (!currentSection.empty () && line.find (' =' ) != std::string::npos) { // Key-value pair
143
+ auto pos = line.find (' =' ); // Find '=' character
144
+ std::string key = line.substr (0 , pos); // Extract key
145
+ std::string value = line.substr (pos + 1 ); // Extract value
146
+ key.erase (key.find_last_not_of (" \t " ) + 1 ); // Trim trailing whitespace from key
147
+ value.erase (0 , value.find_first_not_of (" \t " )); // Trim leading whitespace from value
148
+ iniData[currentSection].keyValues [key] = value; // Store key-value pair in current section
128
149
}
129
150
}
130
- file.close ();
151
+ file.close (); // Close the file
131
152
}
132
153
133
154
void saveIniFile (const std::string& filename) {
134
- std::vector<std::string> lines;
135
- std::ifstream inFile (filename);
136
- if (!inFile.is_open ()) {
155
+ std::vector<std::string> lines; // Vector to store lines of the file
156
+ std::ifstream inFile (filename); // Open the file for reading
157
+ if (!inFile.is_open ()) { // Check if the file is opened successfully
137
158
std::cerr << " Failed to read " << filename << " for saving" << std::endl;
138
159
return ;
139
160
}
140
- std::string line, currentSection;
141
- while (std::getline (inFile, line)) {
142
- std::string trimmed = line;
143
- trimmed.erase (0 , trimmed.find_first_not_of (" \t " ));
144
- if (trimmed.empty () || trimmed[0 ] == ' ;' ) {
145
- lines.push_back (line);
146
- } else if (trimmed[0 ] == ' [' && trimmed.back () == ' ]' ) {
147
- currentSection = trimmed.substr (1 , trimmed.size () - 2 );
148
- lines.push_back (line);
149
- } else if (!currentSection.empty () && line.find (' =' ) != std::string::npos) {
150
- auto pos = line.find (' =' );
151
- std::string key = line.substr (0 , pos);
152
- key.erase (key.find_last_not_of (" \t " ) + 1 );
153
- if (iniData[currentSection].keyValues .count (key)) {
154
- lines.push_back (key + " = " + iniData[currentSection].keyValues [key]);
161
+ std::string line, currentSection; // Variables to store current line and section
162
+ while (std::getline (inFile, line)) { // Read the file line by line
163
+ std::string trimmed = line; // Copy the line to a new string
164
+ trimmed.erase (0 , trimmed.find_first_not_of (" \t " )); // Trim leading whitespace
165
+ if (trimmed.empty () || trimmed[0 ] == ' ;' ) { // Check if the line is empty or a comment
166
+ lines.push_back (line); // Add the line to the vector
167
+ } else if (trimmed[0 ] == ' [' && trimmed.back () == ' ]' ) { // Check if the line is a section header
168
+ currentSection = trimmed.substr (1 , trimmed.size () - 2 ); // Extract the section name
169
+ lines.push_back (line); // Add the section header to the vector
170
+ } else if (!currentSection.empty () && line.find (' =' ) != std::string::npos) { // Check if the line is a key-value pair
171
+ auto pos = line.find (' =' ); // Find the position of '='
172
+ std::string key = line.substr (0 , pos); // Extract the key
173
+ key.erase (key.find_last_not_of (" \t " ) + 1 ); // Trim trailing whitespace from the key
174
+ if (iniData[currentSection].keyValues .count (key)) { // Check if the key exists in the current section
175
+ lines.push_back (key + " = " + iniData[currentSection].keyValues [key]); // Add the updated key-value pair to the vector
155
176
} else {
156
- lines.push_back (line);
177
+ lines.push_back (line); // Add the original line to the vector
157
178
}
158
179
} else {
159
- lines.push_back (line);
180
+ lines.push_back (line); // Add the line to the vector
160
181
}
161
182
}
162
- inFile.close ();
183
+ inFile.close (); // Close the input file
163
184
164
- std::ofstream outFile (filename);
165
- if (!outFile.is_open ()) {
185
+ std::ofstream outFile (filename); // Open the file for writing
186
+ if (!outFile.is_open ()) { // Check if the file is opened successfully
166
187
std::cerr << " Failed to write " << filename << std::endl;
167
188
return ;
168
189
}
169
- for (const auto & l : lines) outFile << l << " \n " ;
170
- outFile.close ();
190
+ for (const auto & l : lines) outFile << l << " \n " ; // Write each line to the output file
191
+ outFile.close (); // Close the output file
171
192
}
172
193
173
194
void initExplanations () {
@@ -340,10 +361,10 @@ class IniEditor {
340
361
for (size_t i = 0 ; i < sections.size (); ++i) {
341
362
if (static_cast <int >(i) == dropdownHoverIndex) {
342
363
SDL_Rect highlight = {10 , y, 190 , 20 };
343
- SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 );
364
+ SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 ); // Highlight color
344
365
SDL_RenderFillRect (renderer, &highlight);
345
366
}
346
- renderText (sections[i], 15 , y);
367
+ renderText (sections[i], 15 , y); // Render section name
347
368
y += 20 ;
348
369
}
349
370
}
@@ -352,35 +373,35 @@ class IniEditor {
352
373
int y = 50 ;
353
374
if (iniData.count (currentSection)) { // Check if section exists
354
375
for (const auto & [key, value] : iniData[currentSection].keyValues ) {
355
- if (y + 20 - scrollOffset < 40 || y - scrollOffset > 400 ) {
376
+ if (y + 20 - scrollOffset < 40 || y - scrollOffset > 400 ) { // Skip rendering if out of view
356
377
y += 30 ;
357
378
continue ;
358
379
}
359
- renderText (key, 10 , y - scrollOffset);
380
+ renderText (key, 10 , y - scrollOffset); // Render key
360
381
if (key == activeField) {
361
382
SDL_Rect fieldRect = {150 , y - scrollOffset, 300 , 20 };
362
- SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 );
383
+ SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 ); // Active field color
363
384
SDL_RenderFillRect (renderer, &fieldRect);
364
385
}
365
- renderText (value, 150 , y - scrollOffset);
366
- if (explanations.count (key)) renderText (" ?" , 120 , y - scrollOffset);
386
+ renderText (value, 150 , y - scrollOffset); // Render value
387
+ if (explanations.count (key)) renderText (" ?" , 120 , y - scrollOffset); // Render tooltip indicator
367
388
y += 30 ;
368
389
}
369
390
}
370
391
371
392
// Render Save/Exit buttons
372
393
SDL_Rect saveBtn = {10 , 360 , 55 , 25 };
373
394
SDL_Rect exitBtn = {75 , 360 , 55 , 25 };
374
- SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 );
395
+ SDL_SetRenderDrawColor (renderer, 200 , 200 , 200 , 255 ); // Button color
375
396
SDL_RenderFillRect (renderer, &saveBtn);
376
397
SDL_RenderFillRect (renderer, &exitBtn);
377
- renderText (" Save" , 20 , 365 );
378
- renderText (" Exit" , 90 , 365 );
398
+ renderText (" Save" , 20 , 365 ); // Render Save button text
399
+ renderText (" Exit" , 90 , 365 ); // Render Exit button text
379
400
380
401
// Render tooltip
381
402
if (!tooltipKey.empty () && explanations.count (tooltipKey)) {
382
403
SDL_Rect tooltipRect = {150 , 50 , 300 , 100 };
383
- SDL_SetRenderDrawColor (renderer, 240 , 240 , 200 , 255 );
404
+ SDL_SetRenderDrawColor (renderer, 240 , 240 , 200 , 255 ); // Tooltip background color
384
405
SDL_RenderFillRect (renderer, &tooltipRect);
385
406
std::string text = explanations[tooltipKey];
386
407
int y = 55 ;
@@ -389,33 +410,33 @@ class IniEditor {
389
410
size_t next = text.find (' \n ' , pos);
390
411
if (next == std::string::npos) next = text.length ();
391
412
std::string line = text.substr (pos, next - pos);
392
- renderText (line, 155 , y);
413
+ renderText (line, 155 , y); // Render tooltip text line by line
393
414
y += 20 ;
394
415
pos = next + 1 ;
395
416
}
396
417
}
397
418
398
- SDL_RenderPresent (renderer);
419
+ SDL_RenderPresent (renderer); // Present the rendered frame
399
420
}
400
421
401
422
void renderText (const std::string& text, int x, int y) {
402
- if (!font || text.empty ()) return ;
403
- SDL_Color color = {0 , 0 , 0 , 255 }; // Black
404
- SDL_Surface* surface = TTF_RenderText_Solid (font, text.c_str (), color);
423
+ if (!font || text.empty ()) return ; // Check if font is loaded and text is not empty
424
+ SDL_Color color = {0 , 0 , 0 , 255 }; // Black color for text
425
+ SDL_Surface* surface = TTF_RenderText_Solid (font, text.c_str (), color); // Render text to surface
405
426
if (!surface) {
406
- std::cerr << " TTF_RenderText_Solid failed: " << TTF_GetError () << std::endl;
427
+ std::cerr << " TTF_RenderText_Solid failed: " << TTF_GetError () << std::endl; // Error handling
407
428
return ;
408
429
}
409
- SDL_Texture* texture = SDL_CreateTextureFromSurface (renderer, surface);
430
+ SDL_Texture* texture = SDL_CreateTextureFromSurface (renderer, surface); // Create texture from surface
410
431
if (!texture) {
411
- std::cerr << " SDL_CreateTextureFromSurface failed: " << SDL_GetError () << std::endl;
412
- SDL_FreeSurface (surface);
432
+ std::cerr << " SDL_CreateTextureFromSurface failed: " << SDL_GetError () << std::endl; // Error handling
433
+ SDL_FreeSurface (surface); // Free surface if texture creation fails
413
434
return ;
414
435
}
415
- SDL_Rect dst = {x, y, surface->w , surface->h };
416
- SDL_RenderCopy (renderer, texture, nullptr , &dst);
417
- SDL_FreeSurface (surface);
418
- SDL_DestroyTexture (texture);
436
+ SDL_Rect dst = {x, y, surface->w , surface->h }; // Destination rectangle for rendering
437
+ SDL_RenderCopy (renderer, texture, nullptr , &dst); // Copy texture to renderer
438
+ SDL_FreeSurface (surface); // Free the surface
439
+ SDL_DestroyTexture (texture); // Destroy the texture
419
440
}
420
441
};
421
442
0 commit comments