1 /* 2 * Copyright 2005, Axel Dörfler, axeld@pinc-software.de. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "Locale.h" 8 #include "LocaleWindow.h" 9 10 #include <Alert.h> 11 #include <Application.h> 12 #include <Bitmap.h> 13 #include <Button.h> 14 #include <Catalog.h> 15 #include <GroupLayout.h> 16 #include <GroupLayoutBuilder.h> 17 #include <ListView.h> 18 #include <Locale.h> 19 #include <LocaleRoster.h> 20 #include <Screen.h> 21 #include <ScrollView.h> 22 #include <StringView.h> 23 #include <TabView.h> 24 25 #include <ICUWrapper.h> 26 27 #include <unicode/locid.h> 28 #include <unicode/datefmt.h> 29 30 #include "TimeFormatSettingsView.h" 31 32 33 #define TR_CONTEXT "Locale Preflet Window" 34 #define MAX_DRAG_HEIGHT 200.0 35 #define ALPHA 170 36 #define TEXT_OFFSET 5.0 37 38 // Language lists management 39 40 41 // This is a language item. It's a BStringItem but we also want to keep the 42 // language code and not only the displayName 43 class LanguageListItem: public BStringItem 44 { 45 public: 46 LanguageListItem(const char* display, const char* code) 47 : BStringItem(display) 48 , LanguageCode(code) 49 {} 50 51 ~LanguageListItem() {}; 52 53 const inline BString getLanguageCode() { return LanguageCode; } 54 void Draw(BView *owner, BRect frame); 55 private: 56 const BString LanguageCode; 57 }; 58 59 60 void 61 LanguageListItem::Draw(BView *owner, BRect frame) 62 { 63 owner->SetLowColor(255, 255, 255, 255); 64 owner->FillRect(frame, B_SOLID_LOW); 65 // label 66 owner->SetHighColor(0, 0, 0, 255); 67 font_height fh; 68 owner->GetFontHeight(&fh); 69 const char* text = Text(); 70 BString truncatedString(text); 71 owner->TruncateString(&truncatedString, B_TRUNCATE_MIDDLE, 72 frame.Width() - TEXT_OFFSET - 4.0); 73 float height = frame.Height(); 74 float textHeight = fh.ascent + fh.descent; 75 BPoint textPoint; 76 textPoint.x = frame.left + TEXT_OFFSET; 77 textPoint.y = frame.top 78 + ceilf(height / 2.0 - textHeight / 2.0 + fh.ascent); 79 owner->DrawString(truncatedString.String(), textPoint); 80 } 81 82 83 // This is a language list. Basically, a drag-n-drop enabled list. 84 class LanguageListView: public BListView 85 { 86 public: 87 LanguageListView(const char* name, list_view_type type) 88 : BListView(name, type) 89 {} 90 91 bool InitiateDrag(BPoint point, int32 index, bool wasSelected); 92 void MouseMoved(BPoint where, uint32 transit, const BMessage *msg); 93 void MoveItems(BList& items, int32 index); 94 void AttachedToWindow() 95 { 96 BListView::AttachedToWindow(); 97 ScrollToSelection(); 98 } 99 100 void MessageReceived (BMessage* message) 101 { 102 if (message->what == 'DRAG') { 103 LanguageListView *list = NULL; 104 if (message->FindPointer("list", (void **)&list) == B_OK) { 105 if (list == this) { 106 int32 count = CountItems(); 107 if (fDropIndex < 0 || fDropIndex > count) 108 fDropIndex = count; 109 110 // gather all the items from the BMessage 111 BList items; 112 int32 index; 113 for (int32 i = 0; message->FindInt32("index", i, &index) 114 == B_OK; i++) 115 if (BListItem* item = ItemAt(index)) 116 items.AddItem((void*)item); 117 118 // handle them 119 if (items.CountItems() > 0) { 120 MoveItems(items, fDropIndex); 121 } 122 fDropIndex = -1; 123 } else { 124 int32 count = CountItems(); 125 if (fDropIndex < 0 || fDropIndex > count) 126 fDropIndex = count; 127 128 // gather all the items from the BMessage 129 int32 index; 130 for (int32 i = 0; message->FindInt32("index", i, &index) 131 == B_OK; i++) 132 if (BListItem* item = list->RemoveItem(index)) { 133 AddItem(item, fDropIndex); 134 fDropIndex++; 135 } 136 137 fDropIndex = -1; 138 } 139 } 140 } else BListView::MessageReceived(message); 141 } 142 private: 143 int32 fDropIndex; 144 }; 145 146 147 void 148 LanguageListView::MoveItems(BList& items, int32 index) 149 { 150 DeselectAll(); 151 // we remove the items while we look at them, the insertion index is 152 // decreaded when the items index is lower, so that we insert at the right 153 // spot after removal 154 BList removedItems; 155 int32 count = items.CountItems(); 156 for (int32 i = 0; i < count; i++) { 157 BListItem* item = (BListItem*)items.ItemAt(i); 158 int32 removeIndex = IndexOf(item); 159 if (RemoveItem(item) && removedItems.AddItem((void*)item)) { 160 if (removeIndex < index) 161 index--; 162 } 163 // else ??? -> blow up 164 } 165 for (int32 i = 0; 166 BListItem* item = (BListItem*)removedItems.ItemAt(i); i++) { 167 if (AddItem(item, index)) { 168 // after we're done, the newly inserted items will be selected 169 Select(index, true); 170 // next items will be inserted after this one 171 index++; 172 } else 173 delete item; 174 } 175 } 176 177 178 bool 179 LanguageListView::InitiateDrag(BPoint point, int32 index, bool) 180 { 181 bool success = false; 182 BListItem* item = ItemAt(CurrentSelection(0)); 183 if (!item) { 184 // workarround a timing problem 185 Select(index); 186 item = ItemAt(index); 187 } 188 if (item) { 189 // create drag message 190 BMessage msg('DRAG'); 191 msg.AddPointer("list",(void*)(this)); 192 int32 index; 193 for (int32 i = 0; (index = CurrentSelection(i)) >= 0; i++) 194 msg.AddInt32("index", index); 195 // figure out drag rect 196 float width = Bounds().Width(); 197 BRect dragRect(0.0, 0.0, width, -1.0); 198 // figure out, how many items fit into our bitmap 199 int32 numItems; 200 bool fade = false; 201 for (numItems = 0; BListItem* item = ItemAt(CurrentSelection(numItems)); 202 numItems++) { 203 dragRect.bottom += ceilf(item->Height()) + 1.0; 204 if (dragRect.Height() > MAX_DRAG_HEIGHT) { 205 fade = true; 206 dragRect.bottom = MAX_DRAG_HEIGHT; 207 numItems++; 208 break; 209 } 210 } 211 BBitmap* dragBitmap = new BBitmap(dragRect, B_RGB32, true); 212 if (dragBitmap && dragBitmap->IsValid()) { 213 if (BView *v = new BView(dragBitmap->Bounds(), "helper", 214 B_FOLLOW_NONE, B_WILL_DRAW)) { 215 dragBitmap->AddChild(v); 216 dragBitmap->Lock(); 217 BRect itemBounds(dragRect) ; 218 itemBounds.bottom = 0.0; 219 // let all selected items, that fit into our drag_bitmap, draw 220 for (int32 i = 0; i < numItems; i++) { 221 int32 index = CurrentSelection(i); 222 LanguageListItem* item 223 = static_cast<LanguageListItem*>(ItemAt(index)); 224 itemBounds.bottom = itemBounds.top + ceilf(item->Height()); 225 if (itemBounds.bottom > dragRect.bottom) 226 itemBounds.bottom = dragRect.bottom; 227 item->Draw(v, itemBounds); 228 itemBounds.top = itemBounds.bottom + 1.0; 229 } 230 // make a black frame arround the edge 231 v->SetHighColor(0, 0, 0, 255); 232 v->StrokeRect(v->Bounds()); 233 v->Sync(); 234 235 uint8 *bits = (uint8 *)dragBitmap->Bits(); 236 int32 height = (int32)dragBitmap->Bounds().Height() + 1; 237 int32 width = (int32)dragBitmap->Bounds().Width() + 1; 238 int32 bpr = dragBitmap->BytesPerRow(); 239 240 if (fade) { 241 for (int32 y = 0; y < height - ALPHA / 2; 242 y++, bits += bpr) { 243 uint8 *line = bits + 3; 244 for (uint8 *end = line + 4 * width; line < end; 245 line += 4) 246 *line = ALPHA; 247 } 248 for (int32 y = height - ALPHA / 2; y < height; 249 y++, bits += bpr) { 250 uint8 *line = bits + 3; 251 for (uint8 *end = line + 4 * width; line < end; 252 line += 4) 253 *line = (height - y) << 1; 254 } 255 } else { 256 for (int32 y = 0; y < height; y++, bits += bpr) { 257 uint8 *line = bits + 3; 258 for (uint8 *end = line + 4 * width; line < end; 259 line += 4) 260 *line = ALPHA; 261 } 262 } 263 dragBitmap->Unlock(); 264 } 265 } else { 266 delete dragBitmap; 267 dragBitmap = NULL; 268 } 269 if (dragBitmap) 270 DragMessage(&msg, dragBitmap, B_OP_ALPHA, BPoint(0.0, 0.0)); 271 else 272 DragMessage(&msg, dragRect.OffsetToCopy(point), this); 273 274 success = true; 275 } 276 return success; 277 } 278 279 280 void 281 LanguageListView::MouseMoved(BPoint where, uint32 transit, const BMessage *msg) 282 { 283 if (msg && (msg->what == 'DRAG')) { 284 switch (transit) { 285 case B_ENTERED_VIEW: 286 case B_INSIDE_VIEW: { 287 // set drop target through virtual function 288 // offset where by half of item height 289 BRect r = ItemFrame(0); 290 where.y += r.Height() / 2.0; 291 292 int32 index = IndexOf(where); 293 if (index < 0) 294 index = CountItems(); 295 if (fDropIndex != index) { 296 fDropIndex = index; 297 if (fDropIndex >= 0) { 298 int32 count = CountItems(); 299 if (fDropIndex == count) { 300 BRect r; 301 if (ItemAt(count - 1)) { 302 r = ItemFrame(count - 1); 303 r.top = r.bottom; 304 r.bottom = r.top + 1.0; 305 } else { 306 r = Bounds(); 307 r.bottom--; 308 // compensate for scrollbars moved slightly 309 // out of window 310 } 311 } else { 312 BRect r = ItemFrame(fDropIndex); 313 r.top--; 314 r.bottom = r.top + 1.0; 315 } 316 } 317 } 318 break; 319 } 320 } 321 } else { 322 BListView::MouseMoved(where, transit, msg); 323 } 324 } 325 326 327 const static uint32 kMsgSelectLanguage = 'slng'; 328 const static uint32 kMsgDefaults = 'dflt'; 329 const static uint32 kMsgRevert = 'revt'; 330 331 332 LocaleWindow::LocaleWindow(BRect rect) 333 : BWindow(rect, "Locale", B_TITLED_WINDOW, 334 B_NOT_RESIZABLE | B_NOT_ZOOMABLE | B_ASYNCHRONOUS_CONTROLS 335 | B_AUTO_UPDATE_SIZE_LIMITS) 336 { 337 SetLayout(new BGroupLayout(B_HORIZONTAL)); 338 339 // Buttons at the bottom 340 341 BButton *button = new BButton(TR("Defaults"), new BMessage(kMsgDefaults)); 342 343 fRevertButton = new BButton(TR("Revert"), new BMessage(kMsgRevert)); 344 fRevertButton->SetEnabled(false); 345 346 // Tabs 347 BTabView *tabView = new BTabView("tabview"); 348 349 // Language tab 350 BView *tab = new BView(TR("Language"), B_WILL_DRAW); 351 //tab->SetViewColor(tabView->ViewColor()); 352 tab->SetLayout(new BGroupLayout(B_VERTICAL, 0)); 353 tabView->AddTab(tab); 354 355 { 356 // first list: available languages 357 LanguageListView *listView = new LanguageListView("available", 358 B_MULTIPLE_SELECTION_LIST); 359 BScrollView *scrollView = new BScrollView("scroller", listView, 360 0, false, true, B_FANCY_BORDER); 361 362 // Fill the language list from the LocaleRoster data 363 BMessage installedLanguages; 364 if (be_locale_roster->GetInstalledLanguages(&installedLanguages) 365 == B_OK) { 366 367 BString currentLanguage; 368 for (int i = 0; installedLanguages.FindString("langs", 369 i, ¤tLanguage) == B_OK; i++) { 370 371 // Now get an human-readable, loacalized name for each language 372 // TODO: sort them using collators. 373 Locale currentLocale 374 = Locale::createFromName(currentLanguage.String()); 375 UnicodeString languageFullName; 376 BString str; 377 BStringByteSink bbs(&str); 378 currentLocale.getDisplayName(languageFullName); 379 languageFullName.toUTF8(bbs); 380 LanguageListItem* si 381 = new LanguageListItem(str, currentLanguage.String()); 382 listView->AddItem(si); 383 } 384 385 } else { 386 BAlert* myAlert = new BAlert("Error", 387 TR("Unable to find the available languages! You can't use this " 388 "preflet!"), 389 TR("Ok"), NULL, NULL, 390 B_WIDTH_AS_USUAL, B_OFFSET_SPACING, B_STOP_ALERT); 391 myAlert->Go(); 392 } 393 394 // Second list: active languages 395 fPreferredListView = new LanguageListView("preferred", 396 B_MULTIPLE_SELECTION_LIST); 397 BScrollView *scrollViewEnabled = new BScrollView("scroller", 398 fPreferredListView, 0, false, true, B_FANCY_BORDER); 399 400 // get the preferred languages from the Settings. Move them here from 401 // the other list. 402 BMessage msg; 403 be_locale_roster->GetPreferredLanguages(&msg); 404 BString langCode; 405 for (int index = 0; msg.FindString("language", index, &langCode) 406 == B_OK; 407 index++) { 408 for (int listPos = 0; LanguageListItem* lli 409 = static_cast<LanguageListItem*>(listView->ItemAt(listPos)); 410 listPos++) { 411 if (langCode == lli->getLanguageCode()) { 412 fPreferredListView->AddItem(lli); 413 listView->RemoveItem(lli); 414 } 415 } 416 } 417 418 tab->AddChild(BGroupLayoutBuilder(B_HORIZONTAL, 10) 419 .Add(BGroupLayoutBuilder(B_VERTICAL, 10) 420 .Add(new BStringView("", TR("Available languages"))) 421 .Add(scrollView) 422 ) 423 .Add(BGroupLayoutBuilder(B_VERTICAL, 10) 424 .Add(new BStringView("", TR("Preferred languages"))) 425 .Add(scrollViewEnabled) 426 ) 427 ); 428 429 } 430 431 // Country tab 432 tab = new BView(TR("Country"), B_WILL_DRAW); 433 //tab->SetViewColor(tabView->ViewColor()); 434 tab->SetLayout(new BGroupLayout(B_VERTICAL, 0)); 435 tabView->AddTab(tab); 436 437 { 438 BListView* listView = new BListView("country", B_SINGLE_SELECTION_LIST); 439 BScrollView *scrollView = new BScrollView("scroller", 440 listView, 0, false, true, B_FANCY_BORDER); 441 442 BMessage* msg = new BMessage('csel'); 443 listView->SetSelectionMessage(msg); 444 445 446 // get all available countries from ICU 447 // Use DateFormat::getAvailableLocale so we get only the one we can 448 // use. Maybe check the NumberFormat one and see if there is more. 449 int32_t localeCount; 450 const Locale* currentLocale 451 = Locale::getAvailableLocales(localeCount); 452 BCountry* defaultCountry; 453 be_locale_roster->GetDefaultCountry(&defaultCountry); 454 455 for (int index = 0; index < localeCount; index++) 456 { 457 UnicodeString countryFullName; 458 BString str; 459 BStringByteSink bbs(&str); 460 currentLocale[index].getDisplayName(countryFullName); 461 countryFullName.toUTF8(bbs); 462 LanguageListItem* si 463 = new LanguageListItem(str, currentLocale[index].getName()); 464 listView->AddItem(si); 465 if (strcmp(currentLocale[index].getName(), 466 defaultCountry->Code()) == 0) 467 listView->Select(listView->CountItems() - 1); 468 } 469 470 fTimeFormatSettings 471 = new TimeFormatSettingsView(defaultCountry); 472 473 tab->AddChild(BGroupLayoutBuilder(B_HORIZONTAL, 5) 474 .Add(scrollView) 475 .Add(new BScrollView("advanced", fTimeFormatSettings, 0, 476 false, true, B_NO_BORDER)) 477 ); 478 479 listView->ScrollToSelection(); 480 } 481 482 // check if the window is on screen 483 rect = BScreen().Frame(); 484 rect.right -= 20; 485 rect.bottom -= 20; 486 487 BPoint position = Frame().LeftTop(); 488 if (!rect.Contains(position)) { 489 // center window on screen as it doesn't fit on the saved position 490 position.x = (rect.Width() - Bounds().Width()) / 2; 491 position.y = (rect.Height() - Bounds().Height()) / 2; 492 } 493 MoveTo(position); 494 495 // Layout management 496 AddChild(BGroupLayoutBuilder(B_VERTICAL, 3) 497 .Add(tabView) 498 .Add(BGroupLayoutBuilder(B_HORIZONTAL, 3) 499 .Add(button) 500 .Add(fRevertButton) 501 .AddGlue() 502 ) 503 .SetInsets(5, 5, 5, 5) 504 ); 505 } 506 507 508 bool 509 LocaleWindow::QuitRequested() 510 { 511 BMessage update(kMsgSettingsChanged); 512 update.AddRect("window_frame", Frame()); 513 int index = 0; 514 while (index < fPreferredListView->CountItems()) { 515 update.AddString("language", static_cast<LanguageListItem*> 516 (fPreferredListView->ItemAt(index))->getLanguageCode()); 517 index++; 518 } 519 // TODO also save Country tab settings 520 be_app_messenger.SendMessage(&update); 521 522 be_app_messenger.SendMessage(B_QUIT_REQUESTED); 523 return true; 524 } 525 526 527 void 528 LocaleWindow::MessageReceived(BMessage *message) 529 { 530 switch (message->what) { 531 case kMsgDefaults: 532 // reset default settings 533 break; 534 535 case kMsgRevert: 536 // revert to last settings 537 break; 538 539 case 'csel': 540 { 541 // Country selection changed. 542 // Get the new selected country from the ListView and send it to the 543 // main app event handler. 544 void* ptr; 545 message->FindPointer("source", &ptr); 546 BListView* countryList = static_cast<BListView*>(ptr); 547 LanguageListItem* lli = static_cast<LanguageListItem*> 548 (countryList->ItemAt(countryList->CurrentSelection())); 549 BMessage* newMessage = new BMessage(kMsgSettingsChanged); 550 newMessage->AddString("country",lli->getLanguageCode()); 551 be_app_messenger.SendMessage(newMessage); 552 553 BCountry* country = new BCountry(lli->getLanguageCode()); 554 fTimeFormatSettings->SetCountry(country); 555 break; 556 } 557 558 default: 559 BWindow::MessageReceived(message); 560 break; 561 } 562 } 563 564