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 { 227 previewText << ' ' << colours[ix]; 228 } 229 previewText << '\n'; 230 for (int ix = 0; ix < 8; ++ix) 231 { 232 previewText << ' ' << colours[ix]; 233 } 234 previewText << '\n'; 235 236 fPreview->SetAlignment(B_ALIGN_CENTER); 237 fPreview->SetText(previewText.String(), array); 238 font_height height; 239 be_fixed_font->GetHeight(&height); 240 fPreview->SetExplicitMinSize(BSize(B_SIZE_UNSET, (height.ascent + height.descent) * 5)); 241 } 242 243 244 void 245 ThemeView::AttachedToWindow() 246 { 247 fPicker->SetTarget(this); 248 fAttrList->SetTarget(this); 249 fColorPreview->SetTarget(this); 250 fColorSchemeField->Menu()->SetTargetForItems(this); 251 252 fAttrList->Select(0); 253 SetViewUIColor(B_PANEL_BACKGROUND_COLOR); 254 255 _SetCurrentColorScheme(); 256 } 257 258 259 void 260 ThemeView::WindowActivated(bool active) 261 { 262 if (!active) 263 return; 264 265 UpdateMenu(); 266 } 267 268 269 void 270 ThemeView::UpdateMenu() 271 { 272 PrefHandler::Default()->LoadThemes(); 273 274 _MakeColorSchemeMenu(); 275 _SetCurrentColorScheme(); 276 277 fColorSchemeField->Menu()->SetTargetForItems(this); 278 } 279 280 281 void 282 ThemeView::MessageReceived(BMessage *msg) 283 { 284 bool modified = false; 285 286 switch (msg->what) { 287 case MSG_COLOR_SCHEME_CHANGED: 288 { 289 color_scheme* newScheme = NULL; 290 if (msg->FindPointer("color_scheme", 291 (void**)&newScheme) == B_OK) { 292 _ChangeColorScheme(newScheme); 293 modified = true; 294 } 295 break; 296 } 297 298 case MSG_SET_COLOR: 299 { 300 rgb_color* color; 301 ssize_t size; 302 const char* name; 303 304 if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE, 305 (const void**)&color, &size) == B_OK 306 && msg->FindString(kName, &name) == B_OK) { 307 _SetColor(name, *color); 308 modified = true; 309 } 310 break; 311 } 312 313 case MSG_SET_CURRENT_COLOR: 314 { 315 rgb_color* color; 316 ssize_t size; 317 318 if (msg->FindData(kRGBColor, B_RGB_COLOR_TYPE, 319 (const void**)&color, &size) == B_OK) { 320 _SetCurrentColor(*color); 321 modified = true; 322 } 323 break; 324 } 325 326 case MSG_UPDATE_COLOR: 327 { 328 // Received from the color fPicker when its color changes 329 rgb_color color = fPicker->ValueAsColor(); 330 _SetCurrentColor(color); 331 modified = true; 332 333 break; 334 } 335 336 case MSG_COLOR_ATTRIBUTE_CHOSEN: 337 { 338 // Received when the user chooses a GUI fAttribute from the list 339 ColorItem* item = (ColorItem*) 340 fAttrList->ItemAt(fAttrList->CurrentSelection()); 341 if (item == NULL) 342 break; 343 344 const char* label = item->Text(); 345 rgb_color color = PrefHandler::Default()->getRGB(label); 346 _SetCurrentColor(color); 347 break; 348 } 349 350 case MSG_THEME_MODIFIED: 351 _SetCurrentColorScheme(); 352 BView::MessageReceived(msg); 353 return; 354 355 default: 356 BView::MessageReceived(msg); 357 break; 358 } 359 360 if (modified) { 361 _UpdateStyle(); 362 fTerminalMessenger.SendMessage(msg); 363 364 BMessenger messenger(this); 365 messenger.SendMessage(MSG_THEME_MODIFIED); 366 } 367 } 368 369 370 void 371 ThemeView::SetDefaults() 372 { 373 PrefHandler* prefHandler = PrefHandler::Default(); 374 375 int32 count = fAttrList->CountItems(); 376 for (int32 index = 0; index < count; ++index) { 377 ColorItem* item = (ColorItem*)fAttrList->ItemAt(index); 378 item->SetColor(prefHandler->getRGB(item->Text())); 379 fAttrList->InvalidateItem(index); 380 } 381 382 int32 currentIndex = fAttrList->CurrentSelection(); 383 ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex); 384 if (item != NULL) { 385 rgb_color color = item->Color(); 386 fPicker->SetValue(color); 387 fColorPreview->SetColor(color); 388 fColorPreview->Invalidate(); 389 } 390 391 _UpdateStyle(); 392 393 BMessage message(MSG_COLOR_SCHEME_CHANGED); 394 fTerminalMessenger.SendMessage(&message); 395 } 396 397 398 void 399 ThemeView::Revert() 400 { 401 _SetCurrentColorScheme(); 402 403 SetDefaults(); 404 } 405 406 407 void 408 ThemeView::_ChangeColorScheme(color_scheme* scheme) 409 { 410 PrefHandler* pref = PrefHandler::Default(); 411 412 pref->setRGB(PREF_TEXT_FORE_COLOR, scheme->text_fore_color); 413 pref->setRGB(PREF_TEXT_BACK_COLOR, scheme->text_back_color); 414 pref->setRGB(PREF_SELECT_FORE_COLOR, scheme->select_fore_color); 415 pref->setRGB(PREF_SELECT_BACK_COLOR, scheme->select_back_color); 416 pref->setRGB(PREF_CURSOR_FORE_COLOR, scheme->cursor_fore_color); 417 pref->setRGB(PREF_CURSOR_BACK_COLOR, scheme->cursor_back_color); 418 pref->setRGB(PREF_ANSI_BLACK_COLOR, scheme->ansi_colors.black); 419 pref->setRGB(PREF_ANSI_RED_COLOR, scheme->ansi_colors.red); 420 pref->setRGB(PREF_ANSI_GREEN_COLOR, scheme->ansi_colors.green); 421 pref->setRGB(PREF_ANSI_YELLOW_COLOR, scheme->ansi_colors.yellow); 422 pref->setRGB(PREF_ANSI_BLUE_COLOR, scheme->ansi_colors.blue); 423 pref->setRGB(PREF_ANSI_MAGENTA_COLOR, scheme->ansi_colors.magenta); 424 pref->setRGB(PREF_ANSI_CYAN_COLOR, scheme->ansi_colors.cyan); 425 pref->setRGB(PREF_ANSI_WHITE_COLOR, scheme->ansi_colors.white); 426 pref->setRGB(PREF_ANSI_BLACK_HCOLOR, scheme->ansi_colors_h.black); 427 pref->setRGB(PREF_ANSI_RED_HCOLOR, scheme->ansi_colors_h.red); 428 pref->setRGB(PREF_ANSI_GREEN_HCOLOR, scheme->ansi_colors_h.green); 429 pref->setRGB(PREF_ANSI_YELLOW_HCOLOR, scheme->ansi_colors_h.yellow); 430 pref->setRGB(PREF_ANSI_BLUE_HCOLOR, scheme->ansi_colors_h.blue); 431 pref->setRGB(PREF_ANSI_MAGENTA_HCOLOR, scheme->ansi_colors_h.magenta); 432 pref->setRGB(PREF_ANSI_CYAN_HCOLOR, scheme->ansi_colors_h.cyan); 433 pref->setRGB(PREF_ANSI_WHITE_HCOLOR, scheme->ansi_colors_h.white); 434 435 int32 count = fAttrList->CountItems(); 436 for (int32 index = 0; index < count; ++index) 437 { 438 ColorItem* item = static_cast<ColorItem*>(fAttrList->ItemAt(index)); 439 rgb_color color = pref->getRGB(item->Text()); 440 item->SetColor(color); 441 442 if (item->IsSelected()) { 443 fPicker->SetValue(color); 444 fColorPreview->SetColor(color); 445 fColorPreview->Invalidate(); 446 } 447 } 448 449 fAttrList->Invalidate(); 450 } 451 452 453 void 454 ThemeView::_SetCurrentColorScheme() 455 { 456 PrefHandler* pref = PrefHandler::Default(); 457 458 pref->LoadColorScheme(&gCustomColorScheme); 459 460 const char* currentSchemeName = NULL; 461 462 int32 i = 0; 463 while (i < gColorSchemes->CountItems()) { 464 const color_scheme *item = gColorSchemes->ItemAt(i); 465 i++; 466 467 if (gCustomColorScheme == *item) { 468 currentSchemeName = item->name; 469 break; 470 } 471 } 472 473 // If the scheme is not one of the known ones, assume a custom one. 474 if (currentSchemeName == NULL) 475 currentSchemeName = "Custom"; 476 477 for (int32 i = 0; i < fColorSchemeField->Menu()->CountItems(); i++) { 478 BMenuItem* item = fColorSchemeField->Menu()->ItemAt(i); 479 if (strcmp(item->Label(), currentSchemeName) == 0) { 480 item->SetMarked(true); 481 break; 482 } 483 } 484 } 485 486 487 void 488 ThemeView::_MakeColorSchemeMenuItem(const color_scheme *item) 489 { 490 if (item == NULL) 491 return; 492 493 BMessage* message = new BMessage(MSG_COLOR_SCHEME_CHANGED); 494 message->AddPointer("color_scheme", (const void*)item); 495 fColorSchemeMenu->AddItem(new BMenuItem(item->name, message)); 496 } 497 498 499 void 500 ThemeView::_MakeColorSchemeMenu() 501 { 502 while (fColorSchemeMenu->CountItems() > 0) 503 delete(fColorSchemeMenu->RemoveItem((int32)0)); 504 505 FindColorSchemeByName comparator("Default"); 506 507 const color_scheme* defaultItem = gColorSchemes->FindIf(comparator); 508 509 _MakeColorSchemeMenuItem(defaultItem); 510 511 int32 index = 0; 512 while (index < gColorSchemes->CountItems()) { 513 const color_scheme *item = gColorSchemes->ItemAt(index); 514 index++; 515 516 if (strcmp(item->name, "") == 0) 517 fColorSchemeMenu->AddSeparatorItem(); 518 else if (item == defaultItem) 519 continue; 520 else 521 _MakeColorSchemeMenuItem(item); 522 } 523 524 // Add the custom item at the very end 525 fColorSchemeMenu->AddSeparatorItem(); 526 _MakeColorSchemeMenuItem(&gCustomColorScheme); 527 } 528 529 530 void 531 ThemeView::_SetCurrentColor(rgb_color color) 532 { 533 int32 currentIndex = fAttrList->CurrentSelection(); 534 ColorItem* item = (ColorItem*)fAttrList->ItemAt(currentIndex); 535 if (item != NULL) { 536 item->SetColor(color); 537 fAttrList->InvalidateItem(currentIndex); 538 539 PrefHandler::Default()->setRGB(item->Text(), color); 540 } 541 542 fPicker->SetValue(color); 543 fColorPreview->SetColor(color); 544 fColorPreview->Invalidate(); 545 } 546 547 548 void 549 ThemeView::_SetColor(const char* name, rgb_color color) 550 { 551 PrefHandler::Default()->setRGB(name, color); 552 553 _UpdateStyle(); 554 } 555