1 /* 2 * Copyright 2022 Haiku, Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "ThemeView.h" 8 9 #include <stdio.h> 10 11 #include <Alert.h> 12 #include <Catalog.h> 13 #include <Directory.h> 14 #include <Entry.h> 15 #include <File.h> 16 #include <Font.h> 17 #include <LayoutBuilder.h> 18 #include <Locale.h> 19 #include <Messenger.h> 20 #include <Path.h> 21 #include <PopUpMenu.h> 22 #include <SpaceLayoutItem.h> 23 24 #include "ThemeWindow.h" 25 #include "Colors.h" 26 #include "ColorPreview.h" 27 #include "ColorListView.h" 28 #include "ColorItem.h" 29 #include "TermConst.h" 30 #include "PrefHandler.h" 31 32 #undef B_TRANSLATION_CONTEXT 33 #define B_TRANSLATION_CONTEXT "Terminal ThemeView" 34 35 #define COLOR_DROPPED 'cldp' 36 #define DECORATOR_CHANGED 'dcch' 37 38 39 /* static */ const char* 40 ThemeView::kColorTable[] = { 41 B_TRANSLATE_MARK("Text"), 42 B_TRANSLATE_MARK("Background"), 43 B_TRANSLATE_MARK("Cursor"), 44 B_TRANSLATE_MARK("Text under cursor"), 45 B_TRANSLATE_MARK("Selected text"), 46 B_TRANSLATE_MARK("Selected background"), 47 B_TRANSLATE_MARK("ANSI black color"), 48 B_TRANSLATE_MARK("ANSI red color"), 49 B_TRANSLATE_MARK("ANSI green color"), 50 B_TRANSLATE_MARK("ANSI yellow color"), 51 B_TRANSLATE_MARK("ANSI blue color"), 52 B_TRANSLATE_MARK("ANSI magenta color"), 53 B_TRANSLATE_MARK("ANSI cyan color"), 54 B_TRANSLATE_MARK("ANSI white color"), 55 B_TRANSLATE_MARK("ANSI bright black color"), 56 B_TRANSLATE_MARK("ANSI bright red color"), 57 B_TRANSLATE_MARK("ANSI bright green color"), 58 B_TRANSLATE_MARK("ANSI bright yellow color"), 59 B_TRANSLATE_MARK("ANSI bright blue color"), 60 B_TRANSLATE_MARK("ANSI bright magenta color"), 61 B_TRANSLATE_MARK("ANSI bright cyan color"), 62 B_TRANSLATE_MARK("ANSI bright white color"), 63 NULL 64 }; 65 66 67 ThemeView::ThemeView(const char* name, const BMessenger& messenger) 68 : 69 BGroupView(name, B_VERTICAL, 5), 70 fTerminalMessenger(messenger) 71 { 72 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 73 74 // Set up list of color attributes 75 fAttrList = new ColorListView("AttributeList"); 76 77 fScrollView = new BScrollView("ScrollView", fAttrList, 0, false, true); 78 fScrollView->SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 79 80 PrefHandler* prefHandler = PrefHandler::Default(); 81 82 for (const char** table = kColorTable; *table != NULL; ++table) { 83 fAttrList->AddItem(new ColorItem(B_TRANSLATE_NOCOLLECT(*table), 84 prefHandler->getRGB(*table))); 85 } 86 87 fColorSchemeMenu = new BPopUpMenu(""); 88 fColorSchemeField = new BMenuField(B_TRANSLATE("Color scheme:"), 89 fColorSchemeMenu); 90 91 fColorPreview = new ColorPreview(new BMessage(COLOR_DROPPED), 0); 92 fColorPreview->SetExplicitAlignment(BAlignment(B_ALIGN_HORIZONTAL_CENTER, 93 B_ALIGN_VERTICAL_CENTER)); 94 95 fPicker = new BColorControl(B_ORIGIN, B_CELLS_32x8, 8.0, 96 "picker", new BMessage(MSG_UPDATE_COLOR)); 97 98 fPreview = new BTextView("preview"); 99 100 BLayoutBuilder::Group<>(this) 101 .AddGrid() 102 .Add(fColorSchemeField->CreateLabelLayoutItem(), 0, 5) 103 .Add(fColorSchemeField->CreateMenuBarLayoutItem(), 1, 5) 104 .End() 105 .Add(fScrollView, 10.0) 106 .Add(fPreview) 107 .AddGroup(B_HORIZONTAL, B_USE_DEFAULT_SPACING) 108 .Add(fColorPreview) 109 .AddGlue() 110 .Add(fPicker); 111 112 fColorPreview->Parent()->SetExplicitMaxSize( 113 BSize(B_SIZE_UNSET, fPicker->Bounds().Height())); 114 fAttrList->SetSelectionMessage(new BMessage(MSG_COLOR_ATTRIBUTE_CHOSEN)); 115 fScrollView->SetExplicitMinSize(BSize(B_SIZE_UNSET, 22 * 16)); 116 fColorSchemeField->SetAlignment(B_ALIGN_RIGHT); 117 118 _MakeColorSchemeMenu(); 119 120 _UpdateStyle(); 121 122 InvalidateLayout(true); 123 } 124 125 126 ThemeView::~ThemeView() 127 { 128 } 129 130 131 void 132 ThemeView::_UpdateStyle() 133 { 134 PrefHandler* prefHandler = PrefHandler::Default(); 135 136 const char* colours[] = { 137 B_TRANSLATE("black"), 138 B_TRANSLATE("red"), 139 B_TRANSLATE("green"), 140 B_TRANSLATE("yellow"), 141 B_TRANSLATE("blue"), 142 B_TRANSLATE("magenta"), 143 B_TRANSLATE("cyan"), 144 B_TRANSLATE("white"), 145 }; 146 147 text_run_array *array = (text_run_array*)malloc(sizeof(text_run_array) 148 + sizeof(text_run) * 16); 149 int offset = 1; 150 int index = 0; 151 array->count = 16; 152 array->runs[0].offset = offset; 153 array->runs[0].font = *be_fixed_font; 154 array->runs[0].color = prefHandler->getRGB(PREF_ANSI_BLACK_COLOR); 155 offset += strlen(colours[index++]) + 1; 156 array->runs[1].offset = offset; 157 array->runs[1].font = *be_fixed_font; 158 array->runs[1].color = prefHandler->getRGB(PREF_ANSI_RED_COLOR); 159 offset += strlen(colours[index++]) + 1; 160 array->runs[2].offset = offset; 161 array->runs[2].font = *be_fixed_font; 162 array->runs[2].color = prefHandler->getRGB(PREF_ANSI_GREEN_COLOR); 163 offset += strlen(colours[index++]) + 1; 164 array->runs[3].offset = offset; 165 array->runs[3].font = *be_fixed_font; 166 array->runs[3].color = prefHandler->getRGB(PREF_ANSI_YELLOW_COLOR); 167 offset += strlen(colours[index++]) + 1; 168 array->runs[4].offset = offset; 169 array->runs[4].font = *be_fixed_font; 170 array->runs[4].color = prefHandler->getRGB(PREF_ANSI_BLUE_COLOR); 171 offset += strlen(colours[index++]) + 1; 172 array->runs[5].offset = offset; 173 array->runs[5].font = *be_fixed_font; 174 array->runs[5].color = prefHandler->getRGB(PREF_ANSI_MAGENTA_COLOR); 175 offset += strlen(colours[index++]) + 1; 176 array->runs[6].offset = offset; 177 array->runs[6].font = *be_fixed_font; 178 array->runs[6].color = prefHandler->getRGB(PREF_ANSI_CYAN_COLOR); 179 offset += strlen(colours[index++]) + 1; 180 array->runs[7].offset = offset; 181 array->runs[7].font = *be_fixed_font; 182 array->runs[7].color = prefHandler->getRGB(PREF_ANSI_WHITE_COLOR); 183 offset += strlen(colours[index++]) + 1; 184 index = 0; 185 offset++; 186 array->runs[8].offset = offset; 187 array->runs[8].font = *be_fixed_font; 188 array->runs[8].color = prefHandler->getRGB(PREF_ANSI_BLACK_HCOLOR); 189 offset += strlen(colours[index++]) + 1; 190 array->runs[9].offset = offset; 191 array->runs[9].font = *be_fixed_font; 192 array->runs[9].color = prefHandler->getRGB(PREF_ANSI_RED_HCOLOR); 193 offset += strlen(colours[index++]) + 1; 194 array->runs[10].offset = offset; 195 array->runs[10].font = *be_fixed_font; 196 array->runs[10].color = prefHandler->getRGB(PREF_ANSI_GREEN_HCOLOR); 197 offset += strlen(colours[index++]) + 1; 198 array->runs[11].offset = offset; 199 array->runs[11].font = *be_fixed_font; 200 array->runs[11].color = prefHandler->getRGB(PREF_ANSI_YELLOW_HCOLOR); 201 offset += strlen(colours[index++]) + 1; 202 array->runs[12].offset = offset; 203 array->runs[12].font = *be_fixed_font; 204 array->runs[12].color = prefHandler->getRGB(PREF_ANSI_BLUE_HCOLOR); 205 offset += strlen(colours[index++]) + 1; 206 array->runs[13].offset = offset; 207 array->runs[13].font = *be_fixed_font; 208 array->runs[13].color = prefHandler->getRGB(PREF_ANSI_MAGENTA_HCOLOR); 209 offset += strlen(colours[index++]) + 1; 210 array->runs[14].offset = offset; 211 array->runs[14].font = *be_fixed_font; 212 array->runs[14].color = prefHandler->getRGB(PREF_ANSI_CYAN_HCOLOR); 213 offset += strlen(colours[index++]) + 1; 214 array->runs[15].offset = offset; 215 array->runs[15].font = *be_fixed_font; 216 array->runs[15].color = prefHandler->getRGB(PREF_ANSI_WHITE_HCOLOR); 217 218 fPreview->SetStylable(true); 219 fPreview->MakeEditable(false); 220 fPreview->MakeSelectable(false); 221 fPreview->SetViewColor(prefHandler->getRGB(PREF_TEXT_BACK_COLOR)); 222 223 BString previewText; 224 previewText << '\n'; 225 for (int ix = 0; ix < 8; ++ix) { 226 previewText << ' ' << colours[ix]; 227 } 228 previewText << '\n'; 229 for (int ix = 0; ix < 8; ++ix) { 230 previewText << ' ' << colours[ix]; 231 } 232 previewText << '\n'; 233 234 fPreview->SetAlignment(B_ALIGN_CENTER); 235 fPreview->SetText(previewText.String(), array); 236 font_height height; 237 be_fixed_font->GetHeight(&height); 238 fPreview->SetExplicitMinSize(BSize(B_SIZE_UNSET, (height.ascent + height.descent) * 5)); 239 } 240 241 242 void 243 ThemeView::AttachedToWindow() 244 { 245 fPicker->SetTarget(this); 246 fAttrList->SetTarget(this); 247 fColorPreview->SetTarget(this); 248 fColorSchemeField->Menu()->SetTargetForItems(this); 249 250 fAttrList->Select(0); 251 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 252 253 _SetCurrentColorScheme(); 254 } 255 256 257 void 258 ThemeView::WindowActivated(bool active) 259 { 260 if (!active) 261 return; 262 263 UpdateMenu(); 264 } 265 266 267 void 268 ThemeView::UpdateMenu() 269 { 270 PrefHandler::Default()->LoadThemes(); 271 272 _MakeColorSchemeMenu(); 273 _SetCurrentColorScheme(); 274 275 fColorSchemeField->Menu()->SetTargetForItems(this); 276 } 277 278 279 void 280 ThemeView::MessageReceived(BMessage *msg) 281 { 282 bool modified = false; 283 284 switch (msg->what) { 285 case MSG_COLOR_SCHEME_CHANGED: 286 { 287 color_scheme* newScheme = NULL; 288 if (msg->FindPointer("color_scheme", 289 (void**)&newScheme) == B_OK) { 290 _ChangeColorScheme(newScheme); 291 modified = true; 292 } 293 break; 294 } 295 296 case MSG_SET_COLOR: 297 { 298 rgb_color* color; 299 ssize_t size; 300 const char* name; 301 302 if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE, 303 (const void**)&color, &size) == B_OK 304 && msg->FindString(kName, &name) == B_OK) { 305 _SetColor(name, *color); 306 modified = true; 307 } 308 break; 309 } 310 311 case MSG_SET_CURRENT_COLOR: 312 { 313 rgb_color* color; 314 ssize_t size; 315 316 if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE, 317 (const void**)&color, &size) == B_OK) { 318 _SetCurrentColor(*color); 319 modified = true; 320 } 321 break; 322 } 323 324 case MSG_UPDATE_COLOR: 325 { 326 // Received from the color fPicker when its color changes 327 rgb_color color = fPicker->ValueAsColor(); 328 _SetCurrentColor(color); 329 modified = true; 330 331 break; 332 } 333 334 case MSG_COLOR_ATTRIBUTE_CHOSEN: 335 { 336 // Received when the user chooses a GUI fAttribute from the list 337 const int32 currentIndex = fAttrList->CurrentSelection(); 338 if (currentIndex < 0) 339 break; 340 341 rgb_color color = PrefHandler::Default()->getRGB(kColorTable[currentIndex]); 342 _SetCurrentColor(color); 343 break; 344 } 345 346 case MSG_THEME_MODIFIED: 347 _SetCurrentColorScheme(); 348 BView::MessageReceived(msg); 349 return; 350 351 default: 352 BView::MessageReceived(msg); 353 break; 354 } 355 356 if (modified) { 357 _UpdateStyle(); 358 fTerminalMessenger.SendMessage(msg); 359 360 BMessenger messenger(this); 361 messenger.SendMessage(MSG_THEME_MODIFIED); 362 } 363 } 364 365 366 void 367 ThemeView::SetDefaults() 368 { 369 PrefHandler* prefHandler = PrefHandler::Default(); 370 371 int32 count = fAttrList->CountItems(); 372 for (int32 index = 0; index < count; ++index) { 373 ColorItem* item = (ColorItem*)fAttrList->ItemAt(index); 374 item->SetColor(prefHandler->getRGB(kColorTable[index])); 375 fAttrList->InvalidateItem(index); 376 } 377 378 int32 currentIndex = fAttrList->CurrentSelection(); 379 ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex); 380 if (item != NULL) { 381 rgb_color color = item->Color(); 382 fPicker->SetValue(color); 383 fColorPreview->SetColor(color); 384 fColorPreview->Invalidate(); 385 } 386 387 _UpdateStyle(); 388 389 BMessage message(MSG_COLOR_SCHEME_CHANGED); 390 fTerminalMessenger.SendMessage(&message); 391 } 392 393 394 void 395 ThemeView::Revert() 396 { 397 _SetCurrentColorScheme(); 398 399 SetDefaults(); 400 } 401 402 403 void 404 ThemeView::_ChangeColorScheme(color_scheme* scheme) 405 { 406 PrefHandler* pref = PrefHandler::Default(); 407 408 pref->setRGB(PREF_TEXT_FORE_COLOR, scheme->text_fore_color); 409 pref->setRGB(PREF_TEXT_BACK_COLOR, scheme->text_back_color); 410 pref->setRGB(PREF_SELECT_FORE_COLOR, scheme->select_fore_color); 411 pref->setRGB(PREF_SELECT_BACK_COLOR, scheme->select_back_color); 412 pref->setRGB(PREF_CURSOR_FORE_COLOR, scheme->cursor_fore_color); 413 pref->setRGB(PREF_CURSOR_BACK_COLOR, scheme->cursor_back_color); 414 pref->setRGB(PREF_ANSI_BLACK_COLOR, scheme->ansi_colors.black); 415 pref->setRGB(PREF_ANSI_RED_COLOR, scheme->ansi_colors.red); 416 pref->setRGB(PREF_ANSI_GREEN_COLOR, scheme->ansi_colors.green); 417 pref->setRGB(PREF_ANSI_YELLOW_COLOR, scheme->ansi_colors.yellow); 418 pref->setRGB(PREF_ANSI_BLUE_COLOR, scheme->ansi_colors.blue); 419 pref->setRGB(PREF_ANSI_MAGENTA_COLOR, scheme->ansi_colors.magenta); 420 pref->setRGB(PREF_ANSI_CYAN_COLOR, scheme->ansi_colors.cyan); 421 pref->setRGB(PREF_ANSI_WHITE_COLOR, scheme->ansi_colors.white); 422 pref->setRGB(PREF_ANSI_BLACK_HCOLOR, scheme->ansi_colors_h.black); 423 pref->setRGB(PREF_ANSI_RED_HCOLOR, scheme->ansi_colors_h.red); 424 pref->setRGB(PREF_ANSI_GREEN_HCOLOR, scheme->ansi_colors_h.green); 425 pref->setRGB(PREF_ANSI_YELLOW_HCOLOR, scheme->ansi_colors_h.yellow); 426 pref->setRGB(PREF_ANSI_BLUE_HCOLOR, scheme->ansi_colors_h.blue); 427 pref->setRGB(PREF_ANSI_MAGENTA_HCOLOR, scheme->ansi_colors_h.magenta); 428 pref->setRGB(PREF_ANSI_CYAN_HCOLOR, scheme->ansi_colors_h.cyan); 429 pref->setRGB(PREF_ANSI_WHITE_HCOLOR, scheme->ansi_colors_h.white); 430 431 int32 count = fAttrList->CountItems(); 432 for (int32 index = 0; index < count; ++index) { 433 ColorItem* item = static_cast<ColorItem*>(fAttrList->ItemAt(index)); 434 rgb_color color = pref->getRGB(kColorTable[index]); 435 item->SetColor(color); 436 437 if (item->IsSelected()) { 438 fPicker->SetValue(color); 439 fColorPreview->SetColor(color); 440 fColorPreview->Invalidate(); 441 } 442 } 443 444 fAttrList->Invalidate(); 445 } 446 447 448 void 449 ThemeView::_SetCurrentColorScheme() 450 { 451 PrefHandler* pref = PrefHandler::Default(); 452 453 pref->LoadColorScheme(&gCustomColorScheme); 454 455 const char* currentSchemeName = NULL; 456 457 int32 i = 0; 458 while (i < gColorSchemes->CountItems()) { 459 const color_scheme *item = gColorSchemes->ItemAt(i); 460 i++; 461 462 if (gCustomColorScheme == *item) { 463 currentSchemeName = item->name; 464 break; 465 } 466 } 467 468 // If the scheme is not one of the known ones, assume a custom one. 469 if (currentSchemeName == NULL) 470 currentSchemeName = "Custom"; 471 472 for (int32 i = 0; i < fColorSchemeField->Menu()->CountItems(); i++) { 473 BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i); 474 if (strcmp(item->Label(), currentSchemeName) == 0) { 475 item->SetMarked(true); 476 break; 477 } 478 } 479 } 480 481 482 void 483 ThemeView::_MakeColorSchemeMenuItem(const color_scheme *item) 484 { 485 if (item == NULL) 486 return; 487 488 BMessage* message = new BMessage(MSG_COLOR_SCHEME_CHANGED); 489 message->AddPointer("color_scheme", (const void*)item); 490 fColorSchemeMenu->AddItem(new BMenuItem(item->name, message)); 491 } 492 493 494 void 495 ThemeView::_MakeColorSchemeMenu() 496 { 497 while (fColorSchemeMenu->CountItems() > 0) 498 delete(fColorSchemeMenu->RemoveItem((int32)0)); 499 500 FindColorSchemeByName comparator("Default"); 501 502 const color_scheme* defaultItem = gColorSchemes->FindIf(comparator); 503 504 _MakeColorSchemeMenuItem(defaultItem); 505 506 int32 index = 0; 507 while (index < gColorSchemes->CountItems()) { 508 const color_scheme *item = gColorSchemes->ItemAt(index); 509 index++; 510 511 if (strcmp(item->name, "") == 0) 512 fColorSchemeMenu->AddSeparatorItem(); 513 else if (item == defaultItem) 514 continue; 515 else 516 _MakeColorSchemeMenuItem(item); 517 } 518 519 // Add the custom item at the very end 520 fColorSchemeMenu->AddSeparatorItem(); 521 _MakeColorSchemeMenuItem(&gCustomColorScheme); 522 } 523 524 525 void 526 ThemeView::_SetCurrentColor(rgb_color color) 527 { 528 int32 currentIndex = fAttrList->CurrentSelection(); 529 ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex); 530 if (item != NULL) { 531 item->SetColor(color); 532 fAttrList->InvalidateItem(currentIndex); 533 534 PrefHandler::Default()->setRGB(kColorTable[currentIndex], color); 535 } 536 537 fPicker->SetValue(color); 538 fColorPreview->SetColor(color); 539 fColorPreview->Invalidate(); 540 } 541 542 543 void 544 ThemeView::_SetColor(const char* name, rgb_color color) 545 { 546 PrefHandler::Default()->setRGB(name, color); 547 548 _UpdateStyle(); 549 } 550