1 /* 2 * Copyright 2006-2010, Haiku Inc. All rights reserved. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Stephan Aßmus <superstippi@gmx.de> 7 * Adrien Destugues <pulkomandy@gmail.com> 8 * Axel Dörfler, axeld@pinc-software.de 9 * Oliver Tappe <zooey@hirschkaefer.de> 10 */ 11 12 13 #include "LanguageListView.h" 14 15 #include <stdio.h> 16 17 #include <new> 18 19 #include <Bitmap.h> 20 #include <Catalog.h> 21 #include <FormattingConventions.h> 22 #include <GradientLinear.h> 23 #include <LocaleRoster.h> 24 #include <Region.h> 25 #include <Window.h> 26 27 28 #define MAX_DRAG_HEIGHT 200.0 29 #define ALPHA 170 30 31 #undef B_TRANSLATE_CONTEXT 32 #define B_TRANSLATE_CONTEXT "LanguageListView" 33 34 35 static const float kLeftInset = 4; 36 37 LanguageListItem::LanguageListItem(const char* text, const char* id, 38 const char* languageCode) 39 : 40 BStringItem(text), 41 fID(id), 42 fCode(languageCode) 43 { 44 } 45 46 47 LanguageListItem::LanguageListItem(const LanguageListItem& other) 48 : 49 BStringItem(other.Text()), 50 fID(other.fID), 51 fCode(other.fCode) 52 { 53 } 54 55 56 void 57 LanguageListItem::DrawItem(BView* owner, BRect frame, bool complete) 58 { 59 DrawItemWithTextOffset(owner, frame, complete, 0); 60 } 61 62 63 void 64 LanguageListItem::DrawItemWithTextOffset(BView* owner, BRect frame, 65 bool complete, float textOffset) 66 { 67 if (IsSelected() || complete) { 68 rgb_color color; 69 if (IsSelected()) 70 color = ui_color(B_MENU_SELECTED_BACKGROUND_COLOR); 71 else 72 color = owner->ViewColor(); 73 owner->SetHighColor(color); 74 owner->SetLowColor(color); 75 owner->FillRect(frame); 76 } else 77 owner->SetLowColor(owner->ViewColor()); 78 79 BString text = Text(); 80 if (IsEnabled()) 81 owner->SetHighColor(ui_color(B_CONTROL_TEXT_COLOR)); 82 else { 83 owner->SetHighColor(tint_color(owner->LowColor(), B_DARKEN_3_TINT)); 84 text << " [" << B_TRANSLATE("already chosen") << "]"; 85 } 86 87 owner->MovePenTo(frame.left + kLeftInset + textOffset, 88 frame.top + BaselineOffset()); 89 owner->DrawString(text.String()); 90 } 91 92 93 // #pragma mark - 94 95 96 LanguageListItemWithFlag::LanguageListItemWithFlag(const char* text, 97 const char* id, const char* languageCode, const char* countryCode) 98 : 99 LanguageListItem(text, id, languageCode), 100 fCountryCode(countryCode), 101 fIcon(NULL) 102 { 103 } 104 105 106 LanguageListItemWithFlag::LanguageListItemWithFlag( 107 const LanguageListItemWithFlag& other) 108 : 109 LanguageListItem(other), 110 fCountryCode(other.fCountryCode), 111 fIcon(other.fIcon != NULL ? new BBitmap(*other.fIcon) : NULL) 112 { 113 } 114 115 116 LanguageListItemWithFlag::~LanguageListItemWithFlag() 117 { 118 delete fIcon; 119 } 120 121 122 void 123 LanguageListItemWithFlag::Update(BView* owner, const BFont* font) 124 { 125 LanguageListItem::Update(owner, font); 126 127 float iconSize = Height(); 128 SetWidth(Width() + iconSize + 4); 129 130 if (fCountryCode.IsEmpty()) 131 return; 132 133 fIcon = new(std::nothrow) BBitmap(BRect(0, 0, iconSize - 1, iconSize - 1), 134 B_RGBA32); 135 if (fIcon != NULL && BLocaleRoster::Default()->GetFlagIconForCountry(fIcon, 136 fCountryCode.String()) != B_OK) { 137 delete fIcon; 138 fIcon = NULL; 139 } 140 } 141 142 143 void 144 LanguageListItemWithFlag::DrawItem(BView* owner, BRect frame, bool complete) 145 { 146 if (fIcon == NULL || !fIcon->IsValid()) { 147 DrawItemWithTextOffset(owner, frame, complete, 0); 148 return; 149 } 150 151 float iconSize = fIcon->Bounds().Width(); 152 DrawItemWithTextOffset(owner, frame, complete, iconSize + 4); 153 154 BRect iconFrame(frame.left + kLeftInset, frame.top, 155 frame.left + kLeftInset + iconSize - 1, frame.top + iconSize - 1); 156 owner->SetDrawingMode(B_OP_OVER); 157 owner->DrawBitmap(fIcon, iconFrame); 158 owner->SetDrawingMode(B_OP_COPY); 159 } 160 161 162 // #pragma mark - 163 164 165 LanguageListView::LanguageListView(const char* name, list_view_type type) 166 : 167 BOutlineListView(name, type), 168 fDropIndex(-1), 169 fDropTargetHighlightFrame(), 170 fGlobalDropTargetIndicator(false), 171 fDeleteMessage(NULL), 172 fDragMessage(NULL) 173 { 174 } 175 176 177 LanguageListView::~LanguageListView() 178 { 179 } 180 181 182 LanguageListItem* 183 LanguageListView::ItemForLanguageID(const char* id, int32* _index) const 184 { 185 for (int32 index = 0; index < FullListCountItems(); index++) { 186 LanguageListItem* item 187 = static_cast<LanguageListItem*>(FullListItemAt(index)); 188 189 if (item->ID() == id) { 190 if (_index != NULL) 191 *_index = index; 192 return item; 193 } 194 } 195 196 return NULL; 197 } 198 199 200 LanguageListItem* 201 LanguageListView::ItemForLanguageCode(const char* code, int32* _index) const 202 { 203 for (int32 index = 0; index < FullListCountItems(); index++) { 204 LanguageListItem* item 205 = static_cast<LanguageListItem*>(FullListItemAt(index)); 206 207 if (item->Code() == code) { 208 if (_index != NULL) 209 *_index = index; 210 return item; 211 } 212 } 213 214 return NULL; 215 } 216 217 218 void 219 LanguageListView::SetDeleteMessage(BMessage* message) 220 { 221 delete fDeleteMessage; 222 fDeleteMessage = message; 223 } 224 225 226 void 227 LanguageListView::SetDragMessage(BMessage* message) 228 { 229 delete fDragMessage; 230 fDragMessage = message; 231 } 232 233 234 void 235 LanguageListView::SetGlobalDropTargetIndicator(bool isGlobal) 236 { 237 fGlobalDropTargetIndicator = isGlobal; 238 } 239 240 241 void 242 LanguageListView::AttachedToWindow() 243 { 244 BOutlineListView::AttachedToWindow(); 245 ScrollToSelection(); 246 } 247 248 249 void 250 LanguageListView::MessageReceived(BMessage* message) 251 { 252 if (message->WasDropped() && _AcceptsDragMessage(message)) { 253 // Someone just dropped something on us 254 BMessage dragMessage(*message); 255 dragMessage.AddInt32("drop_index", fDropIndex); 256 dragMessage.AddPointer("drop_target", this); 257 Messenger().SendMessage(&dragMessage); 258 } else 259 BOutlineListView::MessageReceived(message); 260 } 261 262 263 void 264 LanguageListView::Draw(BRect updateRect) 265 { 266 BOutlineListView::Draw(updateRect); 267 268 if (fDropIndex >= 0 && fDropTargetHighlightFrame.IsValid()) { 269 // TODO: decide if drawing of a drop target indicator should be moved 270 // into ControlLook 271 BGradientLinear gradient; 272 int step = fGlobalDropTargetIndicator ? 64 : 128; 273 for (int i = 0; i < 256; i += step) 274 gradient.AddColor(i % (step * 2) == 0 275 ? ViewColor() : ui_color(B_CONTROL_HIGHLIGHT_COLOR), i); 276 gradient.AddColor(ViewColor(), 255); 277 gradient.SetStart(fDropTargetHighlightFrame.LeftTop()); 278 gradient.SetEnd(fDropTargetHighlightFrame.RightBottom()); 279 if (fGlobalDropTargetIndicator) { 280 BRegion region(fDropTargetHighlightFrame); 281 region.Exclude(fDropTargetHighlightFrame.InsetByCopy(2.0, 2.0)); 282 ConstrainClippingRegion(®ion); 283 FillRect(fDropTargetHighlightFrame, gradient); 284 ConstrainClippingRegion(NULL); 285 } else 286 FillRect(fDropTargetHighlightFrame, gradient); 287 } 288 } 289 290 291 bool 292 LanguageListView::InitiateDrag(BPoint point, int32 dragIndex, 293 bool /*wasSelected*/) 294 { 295 if (fDragMessage == NULL) 296 return false; 297 298 BListItem* item = ItemAt(CurrentSelection(0)); 299 if (item == NULL) { 300 // workaround for a timing problem 301 // TODO: this should support extending the selection 302 item = ItemAt(dragIndex); 303 Select(dragIndex); 304 } 305 if (item == NULL) 306 return false; 307 308 // create drag message 309 BMessage message = *fDragMessage; 310 message.AddPointer("listview", this); 311 312 for (int32 i = 0;; i++) { 313 int32 index = CurrentSelection(i); 314 if (index < 0) 315 break; 316 317 message.AddInt32("index", index); 318 } 319 320 // figure out drag rect 321 322 BRect dragRect(0.0, 0.0, Bounds().Width(), -1.0); 323 int32 numItems = 0; 324 bool fade = false; 325 326 // figure out, how many items fit into our bitmap 327 for (int32 i = 0, index; message.FindInt32("index", i, &index) == B_OK; 328 i++) { 329 BListItem* item = ItemAt(index); 330 if (item == NULL) 331 break; 332 333 dragRect.bottom += ceilf(item->Height()) + 1.0; 334 numItems++; 335 336 if (dragRect.Height() > MAX_DRAG_HEIGHT) { 337 dragRect.bottom = MAX_DRAG_HEIGHT; 338 fade = true; 339 break; 340 } 341 } 342 343 BBitmap* dragBitmap = new BBitmap(dragRect, B_RGB32, true); 344 if (dragBitmap->IsValid()) { 345 BView* view = new BView(dragBitmap->Bounds(), "helper", B_FOLLOW_NONE, 346 B_WILL_DRAW); 347 dragBitmap->AddChild(view); 348 dragBitmap->Lock(); 349 BRect itemBounds(dragRect) ; 350 itemBounds.bottom = 0.0; 351 // let all selected items, that fit into our drag_bitmap, draw 352 for (int32 i = 0; i < numItems; i++) { 353 int32 index = message.FindInt32("index", i); 354 LanguageListItem* item 355 = static_cast<LanguageListItem*>(ItemAt(index)); 356 itemBounds.bottom = itemBounds.top + ceilf(item->Height()); 357 if (itemBounds.bottom > dragRect.bottom) 358 itemBounds.bottom = dragRect.bottom; 359 item->DrawItem(view, itemBounds); 360 itemBounds.top = itemBounds.bottom + 1.0; 361 } 362 // make a black frame arround the edge 363 view->SetHighColor(0, 0, 0, 255); 364 view->StrokeRect(view->Bounds()); 365 view->Sync(); 366 367 uint8* bits = (uint8*)dragBitmap->Bits(); 368 int32 height = (int32)dragBitmap->Bounds().Height() + 1; 369 int32 width = (int32)dragBitmap->Bounds().Width() + 1; 370 int32 bpr = dragBitmap->BytesPerRow(); 371 372 if (fade) { 373 for (int32 y = 0; y < height - ALPHA / 2; y++, bits += bpr) { 374 uint8* line = bits + 3; 375 for (uint8* end = line + 4 * width; line < end; line += 4) 376 *line = ALPHA; 377 } 378 for (int32 y = height - ALPHA / 2; y < height; 379 y++, bits += bpr) { 380 uint8* line = bits + 3; 381 for (uint8* end = line + 4 * width; line < end; line += 4) 382 *line = (height - y) << 1; 383 } 384 } else { 385 for (int32 y = 0; y < height; y++, bits += bpr) { 386 uint8* line = bits + 3; 387 for (uint8* end = line + 4 * width; line < end; line += 4) 388 *line = ALPHA; 389 } 390 } 391 dragBitmap->Unlock(); 392 } else { 393 delete dragBitmap; 394 dragBitmap = NULL; 395 } 396 397 if (dragBitmap != NULL) 398 DragMessage(&message, dragBitmap, B_OP_ALPHA, BPoint(0.0, 0.0)); 399 else 400 DragMessage(&message, dragRect.OffsetToCopy(point), this); 401 402 return true; 403 } 404 405 406 void 407 LanguageListView::MouseMoved(BPoint where, uint32 transit, 408 const BMessage* dragMessage) 409 { 410 if (dragMessage != NULL && _AcceptsDragMessage(dragMessage)) { 411 switch (transit) { 412 case B_ENTERED_VIEW: 413 case B_INSIDE_VIEW: 414 { 415 BRect highlightFrame; 416 417 if (fGlobalDropTargetIndicator) { 418 highlightFrame = Bounds(); 419 fDropIndex = 0; 420 } else { 421 // offset where by half of item height 422 BRect r = ItemFrame(0); 423 where.y += r.Height() / 2.0; 424 425 int32 index = IndexOf(where); 426 if (index < 0) 427 index = CountItems(); 428 highlightFrame = ItemFrame(index); 429 if (highlightFrame.IsValid()) 430 highlightFrame.bottom = highlightFrame.top; 431 else { 432 highlightFrame = ItemFrame(index - 1); 433 if (highlightFrame.IsValid()) 434 highlightFrame.top = highlightFrame.bottom; 435 else { 436 // empty view, show indicator at top 437 highlightFrame = Bounds(); 438 highlightFrame.bottom = highlightFrame.top; 439 } 440 } 441 fDropIndex = index; 442 } 443 444 if (fDropTargetHighlightFrame != highlightFrame) { 445 Invalidate(fDropTargetHighlightFrame); 446 fDropTargetHighlightFrame = highlightFrame; 447 Invalidate(fDropTargetHighlightFrame); 448 } 449 450 BOutlineListView::MouseMoved(where, transit, dragMessage); 451 return; 452 } 453 } 454 } 455 456 if (fDropTargetHighlightFrame.IsValid()) { 457 Invalidate(fDropTargetHighlightFrame); 458 fDropTargetHighlightFrame = BRect(); 459 } 460 BOutlineListView::MouseMoved(where, transit, dragMessage); 461 } 462 463 464 void 465 LanguageListView::MouseUp(BPoint point) 466 { 467 BOutlineListView::MouseUp(point); 468 if (fDropTargetHighlightFrame.IsValid()) { 469 Invalidate(fDropTargetHighlightFrame); 470 fDropTargetHighlightFrame = BRect(); 471 } 472 } 473 474 475 void 476 LanguageListView::KeyDown(const char* bytes, int32 numBytes) 477 { 478 if (bytes[0] == B_DELETE && fDeleteMessage != NULL) { 479 Invoke(fDeleteMessage); 480 return; 481 } 482 483 BOutlineListView::KeyDown(bytes, numBytes); 484 } 485 486 487 bool 488 LanguageListView::_AcceptsDragMessage(const BMessage* message) const 489 { 490 LanguageListView* sourceView = NULL; 491 return message != NULL 492 && message->FindPointer("listview", (void**)&sourceView) == B_OK; 493 } 494