1 /* 2 * Copyright 2006-2009, Stephan Aßmus <superstippi@gmx.de>. 3 * All rights reserved. Distributed under the terms of the MIT License. 4 */ 5 6 #include "PadView.h" 7 8 #include <stdio.h> 9 10 #include <Directory.h> 11 #include <File.h> 12 #include <Application.h> 13 #include <Catalog.h> 14 #include <GroupLayout.h> 15 #include <MenuItem.h> 16 #include <Message.h> 17 #include <PopUpMenu.h> 18 #include <Region.h> 19 #include <Screen.h> 20 #include <SpaceLayoutItem.h> 21 22 #include "LaunchButton.h" 23 #include "MainWindow.h" 24 #include "App.h" 25 26 27 #undef B_TRANSLATION_CONTEXT 28 #define B_TRANSLATION_CONTEXT "LaunchBox" 29 30 31 static bigtime_t sActivationDelay = 40000; 32 static const uint32 kIconSizes[] = { 16, 20, 24, 32, 40, 48, 64, 96, 128 }; 33 34 35 enum { 36 MSG_TOGGLE_LAYOUT = 'tgll', 37 MSG_SET_ICON_SIZE = 'stis', 38 MSG_SET_IGNORE_DOUBLECLICK = 'strd' 39 }; 40 41 42 PadView::PadView(const char* name) 43 : BView(name, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE, NULL), 44 fDragging(false), 45 fClickTime(0), 46 fButtonLayout(new BGroupLayout(B_VERTICAL, 4)), 47 fIconSize(DEFAULT_ICON_SIZE) 48 { 49 SetViewColor(B_TRANSPARENT_32_BIT); 50 SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); 51 get_click_speed(&sActivationDelay); 52 53 fButtonLayout->SetInsets(2, 7, 2, 2); 54 SetLayout(fButtonLayout); 55 } 56 57 58 PadView::~PadView() 59 { 60 } 61 62 63 void 64 PadView::Draw(BRect updateRect) 65 { 66 rgb_color background = LowColor(); 67 rgb_color light = tint_color(background, B_LIGHTEN_MAX_TINT); 68 rgb_color shadow = tint_color(background, B_DARKEN_2_TINT); 69 BRect r(Bounds()); 70 BeginLineArray(4); 71 AddLine(BPoint(r.left, r.bottom), BPoint(r.left, r.top), light); 72 AddLine(BPoint(r.left + 1.0, r.top), BPoint(r.right, r.top), light); 73 AddLine(BPoint(r.right, r.top + 1.0), BPoint(r.right, r.bottom), shadow); 74 AddLine(BPoint(r.right - 1.0, r.bottom), BPoint(r.left + 1.0, r.bottom), shadow); 75 EndLineArray(); 76 r.InsetBy(1.0, 1.0); 77 StrokeRect(r, B_SOLID_LOW); 78 r.InsetBy(1.0, 1.0); 79 // dots along top 80 BPoint dot = r.LeftTop(); 81 int32 current; 82 int32 stop; 83 BPoint offset; 84 BPoint next; 85 if (Orientation() == B_VERTICAL) { 86 current = (int32)dot.x; 87 stop = (int32)r.right; 88 offset = BPoint(0, 1); 89 next = BPoint(1, -4); 90 r.top += 5.0; 91 } else { 92 current = (int32)dot.y; 93 stop = (int32)r.bottom; 94 offset = BPoint(1, 0); 95 next = BPoint(-4, 1); 96 r.left += 5.0; 97 } 98 int32 num = 1; 99 while (current <= stop) { 100 rgb_color col1; 101 rgb_color col2; 102 if (num == 1) { 103 col1 = shadow; 104 col2 = background; 105 } else if (num == 2) { 106 col1 = background; 107 col2 = light; 108 } else { 109 col1 = background; 110 col2 = background; 111 num = 0; 112 } 113 SetHighColor(col1); 114 StrokeLine(dot, dot, B_SOLID_HIGH); 115 SetHighColor(col2); 116 dot += offset; 117 StrokeLine(dot, dot, B_SOLID_HIGH); 118 dot += offset; 119 StrokeLine(dot, dot, B_SOLID_LOW); 120 dot += offset; 121 SetHighColor(col1); 122 StrokeLine(dot, dot, B_SOLID_HIGH); 123 dot += offset; 124 SetHighColor(col2); 125 StrokeLine(dot, dot, B_SOLID_HIGH); 126 // next pixel 127 num++; 128 dot += next; 129 current++; 130 } 131 FillRect(r, B_SOLID_LOW); 132 } 133 134 135 void 136 PadView::MessageReceived(BMessage* message) 137 { 138 switch (message->what) { 139 case MSG_TOGGLE_LAYOUT: 140 if (fButtonLayout->Orientation() == B_HORIZONTAL) { 141 fButtonLayout->SetInsets(2, 7, 2, 2); 142 fButtonLayout->SetOrientation(B_VERTICAL); 143 } else { 144 fButtonLayout->SetInsets(7, 2, 2, 2); 145 fButtonLayout->SetOrientation(B_HORIZONTAL); 146 } 147 break; 148 149 case MSG_SET_ICON_SIZE: 150 uint32 size; 151 if (message->FindInt32("size", (int32*)&size) == B_OK) 152 SetIconSize(size); 153 break; 154 155 case MSG_SET_IGNORE_DOUBLECLICK: 156 SetIgnoreDoubleClick(!IgnoreDoubleClick()); 157 break; 158 159 default: 160 BView::MessageReceived(message); 161 break; 162 } 163 } 164 165 166 void 167 PadView::MouseDown(BPoint where) 168 { 169 BWindow* window = Window(); 170 if (window == NULL) 171 return; 172 173 BRegion region; 174 GetClippingRegion(®ion); 175 if (!region.Contains(where)) 176 return; 177 178 bool handle = true; 179 for (int32 i = 0; BView* child = ChildAt(i); i++) { 180 if (child->Frame().Contains(where)) { 181 handle = false; 182 break; 183 } 184 } 185 if (!handle) 186 return; 187 188 BMessage* message = window->CurrentMessage(); 189 if (message == NULL) 190 return; 191 192 uint32 buttons; 193 message->FindInt32("buttons", (int32*)&buttons); 194 if (buttons & B_SECONDARY_MOUSE_BUTTON) { 195 BRect r = Bounds(); 196 r.InsetBy(2.0, 2.0); 197 r.top += 6.0; 198 if (r.Contains(where)) { 199 DisplayMenu(where); 200 } else { 201 // sends the window to the back 202 window->Activate(false); 203 } 204 } else { 205 if (system_time() - fClickTime < sActivationDelay) { 206 window->Minimize(true); 207 fClickTime = 0; 208 } else { 209 window->Activate(); 210 fDragOffset = ConvertToScreen(where) - window->Frame().LeftTop(); 211 fDragging = true; 212 SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); 213 fClickTime = system_time(); 214 } 215 } 216 } 217 218 219 void 220 PadView::MouseUp(BPoint where) 221 { 222 if (BWindow* window = Window()) { 223 uint32 buttons; 224 window->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); 225 if (buttons & B_PRIMARY_MOUSE_BUTTON 226 && system_time() - fClickTime < sActivationDelay 227 && window->IsActive()) 228 window->Activate(); 229 } 230 fDragging = false; 231 } 232 233 234 void 235 PadView::MouseMoved(BPoint where, uint32 transit, const BMessage* dragMessage) 236 { 237 MainWindow* window = dynamic_cast<MainWindow*>(Window()); 238 if (window == NULL) 239 return; 240 241 if (fDragging) { 242 window->MoveTo(ConvertToScreen(where) - fDragOffset); 243 } else if (window->AutoRaise()) { 244 where = ConvertToScreen(where); 245 BScreen screen(window); 246 BRect frame = screen.Frame(); 247 BRect windowFrame = window->Frame(); 248 if (where.x == frame.left || where.x == frame.right 249 || where.y == frame.top || where.y == frame.bottom) { 250 BPoint position = window->ScreenPosition(); 251 bool raise = false; 252 if (fabs(0.5 - position.x) > fabs(0.5 - position.y)) { 253 // left or right border 254 if (where.y >= windowFrame.top 255 && where.y <= windowFrame.bottom) { 256 if (position.x < 0.5 && where.x == frame.left) 257 raise = true; 258 else if (position.x > 0.5 && where.x == frame.right) 259 raise = true; 260 } 261 } else { 262 // top or bottom border 263 if (where.x >= windowFrame.left && where.x <= windowFrame.right) { 264 if (position.y < 0.5 && where.y == frame.top) 265 raise = true; 266 else if (position.y > 0.5 && where.y == frame.bottom) 267 raise = true; 268 } 269 } 270 if (raise) 271 window->Activate(); 272 } 273 } 274 } 275 276 277 void 278 PadView::AddButton(LaunchButton* button, LaunchButton* beforeButton) 279 { 280 button->SetIconSize(fIconSize); 281 282 if (beforeButton) 283 fButtonLayout->AddView(fButtonLayout->IndexOfView(beforeButton), button); 284 else 285 fButtonLayout->AddView(button); 286 287 _NotifySettingsChanged(); 288 } 289 290 291 bool 292 PadView::RemoveButton(LaunchButton* button) 293 { 294 bool result = fButtonLayout->RemoveView(button); 295 if (result) 296 _NotifySettingsChanged(); 297 return result; 298 } 299 300 301 LaunchButton* 302 PadView::ButtonAt(int32 index) const 303 { 304 BLayoutItem* item = fButtonLayout->ItemAt(index); 305 if (item == NULL) 306 return NULL; 307 return dynamic_cast<LaunchButton*>(item->View()); 308 } 309 310 311 void 312 PadView::DisplayMenu(BPoint where, LaunchButton* button) const 313 { 314 MainWindow* window = dynamic_cast<MainWindow*>(Window()); 315 if (window == NULL) 316 return; 317 318 LaunchButton* nearestButton = button; 319 if (!nearestButton) { 320 // find the nearest button 321 for (int32 i = 0; (nearestButton = ButtonAt(i)); i++) { 322 if (nearestButton->Frame().top > where.y) 323 break; 324 } 325 } 326 BPopUpMenu* menu = new BPopUpMenu(B_TRANSLATE("launch popup"), false, false); 327 // add button 328 BMessage* message = new BMessage(MSG_ADD_SLOT); 329 message->AddPointer("be:source", (void*)nearestButton); 330 BMenuItem* item = new BMenuItem(B_TRANSLATE("Add button here"), message); 331 item->SetTarget(window); 332 menu->AddItem(item); 333 // button options 334 if (button) { 335 // clear button 336 message = new BMessage(MSG_CLEAR_SLOT); 337 message->AddPointer("be:source", (void*)button); 338 item = new BMenuItem(B_TRANSLATE("Clear button"), message); 339 item->SetTarget(window); 340 menu->AddItem(item); 341 // remove button 342 message = new BMessage(MSG_REMOVE_SLOT); 343 message->AddPointer("be:source", (void*)button); 344 item = new BMenuItem(B_TRANSLATE("Remove button"), message); 345 item->SetTarget(window); 346 menu->AddItem(item); 347 // Open containing folder button 348 if (button->Ref() != NULL) { 349 message = new BMessage(MSG_OPEN_CONTAINING_FOLDER); 350 message->AddPointer("be:source", (void*)button); 351 item = new BMenuItem(B_TRANSLATE("Open containing folder"), message); 352 item->SetTarget(window); 353 menu->AddItem(item); 354 } 355 // set button description 356 if (button->Ref()) { 357 message = new BMessage(MSG_SET_DESCRIPTION); 358 message->AddPointer("be:source", (void*)button); 359 item = new BMenuItem(B_TRANSLATE("Set description" B_UTF8_ELLIPSIS), 360 message); 361 item->SetTarget(window); 362 menu->AddItem(item); 363 } 364 } 365 menu->AddSeparatorItem(); 366 // window settings 367 BMenu* settingsM = new BMenu(B_TRANSLATE("Settings")); 368 settingsM->SetFont(be_plain_font); 369 370 const char* toggleLayoutLabel; 371 if (fButtonLayout->Orientation() == B_HORIZONTAL) 372 toggleLayoutLabel = B_TRANSLATE("Vertical layout"); 373 else 374 toggleLayoutLabel = B_TRANSLATE("Horizontal layout"); 375 item = new BMenuItem(toggleLayoutLabel, new BMessage(MSG_TOGGLE_LAYOUT)); 376 item->SetTarget(this); 377 settingsM->AddItem(item); 378 379 BMenu* iconSizeM = new BMenu(B_TRANSLATE("Icon size")); 380 for (uint32 i = 0; i < sizeof(kIconSizes) / sizeof(uint32); i++) { 381 uint32 iconSize = kIconSizes[i]; 382 message = new BMessage(MSG_SET_ICON_SIZE); 383 message->AddInt32("size", iconSize); 384 BString label; 385 label.SetToFormat(B_TRANSLATE_COMMENT("%" B_PRId32" × %" B_PRId32, 386 "The '×' is the Unicode multiplication sign U+00D7"), 387 iconSize, iconSize); 388 item = new BMenuItem(label, message); 389 item->SetTarget(this); 390 item->SetMarked(IconSize() == iconSize); 391 iconSizeM->AddItem(item); 392 } 393 settingsM->AddItem(iconSizeM); 394 395 item = new BMenuItem(B_TRANSLATE("Ignore double-click"), 396 new BMessage(MSG_SET_IGNORE_DOUBLECLICK)); 397 item->SetTarget(this); 398 item->SetMarked(IgnoreDoubleClick()); 399 settingsM->AddItem(item); 400 401 uint32 what = window->Look() == B_BORDERED_WINDOW_LOOK ? MSG_SHOW_BORDER : MSG_HIDE_BORDER; 402 item = new BMenuItem(B_TRANSLATE("Show window border"), new BMessage(what)); 403 item->SetTarget(window); 404 item->SetMarked(what == MSG_HIDE_BORDER); 405 settingsM->AddItem(item); 406 407 item = new BMenuItem(B_TRANSLATE("Autostart"), new BMessage(MSG_TOGGLE_AUTOSTART)); 408 item->SetTarget(be_app); 409 item->SetMarked(((App*)be_app)->AutoStart()); 410 settingsM->AddItem(item); 411 412 item = new BMenuItem(B_TRANSLATE("Auto-raise"), new BMessage(MSG_TOGGLE_AUTORAISE)); 413 item->SetTarget(window); 414 item->SetMarked(window->AutoRaise()); 415 settingsM->AddItem(item); 416 417 item = new BMenuItem(B_TRANSLATE("Show on all workspaces"), new BMessage(MSG_SHOW_ON_ALL_WORKSPACES)); 418 item->SetTarget(window); 419 item->SetMarked(window->ShowOnAllWorkspaces()); 420 settingsM->AddItem(item); 421 422 menu->AddItem(settingsM); 423 424 menu->AddSeparatorItem(); 425 426 // pad commands 427 BMenu* padM = new BMenu(B_TRANSLATE("Pad")); 428 padM->SetFont(be_plain_font); 429 // new pad 430 item = new BMenuItem(B_TRANSLATE("New"), new BMessage(MSG_ADD_WINDOW)); 431 item->SetTarget(be_app); 432 padM->AddItem(item); 433 // new pad 434 item = new BMenuItem(B_TRANSLATE("Clone"), new BMessage(MSG_ADD_WINDOW)); 435 item->SetTarget(window); 436 padM->AddItem(item); 437 padM->AddSeparatorItem(); 438 // close 439 item = new BMenuItem(B_TRANSLATE("Close"), new BMessage(B_QUIT_REQUESTED)); 440 item->SetTarget(window); 441 padM->AddItem(item); 442 menu->AddItem(padM); 443 // app commands 444 BMenu* appM = new BMenu(B_TRANSLATE_SYSTEM_NAME("LaunchBox")); 445 appM->SetFont(be_plain_font); 446 // quit 447 item = new BMenuItem(B_TRANSLATE("Quit"), new BMessage(B_QUIT_REQUESTED)); 448 item->SetTarget(be_app); 449 appM->AddItem(item); 450 menu->AddItem(appM); 451 // finish popup 452 menu->SetAsyncAutoDestruct(true); 453 menu->SetFont(be_plain_font); 454 where = ConvertToScreen(where); 455 BRect mouseRect(where, where); 456 mouseRect.InsetBy(-4.0, -4.0); 457 menu->Go(where, true, false, mouseRect, true); 458 } 459 460 461 void 462 PadView::SetOrientation(enum orientation orientation) 463 { 464 if (orientation == B_VERTICAL) { 465 fButtonLayout->SetInsets(2, 7, 2, 2); 466 fButtonLayout->SetOrientation(B_VERTICAL); 467 } else { 468 fButtonLayout->SetInsets(7, 2, 2, 2); 469 fButtonLayout->SetOrientation(B_HORIZONTAL); 470 } 471 _NotifySettingsChanged(); 472 } 473 474 475 enum orientation 476 PadView::Orientation() const 477 { 478 return fButtonLayout->Orientation(); 479 } 480 481 482 void 483 PadView::SetIconSize(uint32 size) 484 { 485 if (size == fIconSize) 486 return; 487 488 fIconSize = size; 489 490 for (int32 i = 0; LaunchButton* button = ButtonAt(i); i++) 491 button->SetIconSize(fIconSize); 492 493 _NotifySettingsChanged(); 494 } 495 496 497 uint32 498 PadView::IconSize() const 499 { 500 return fIconSize; 501 } 502 503 504 void 505 PadView::SetIgnoreDoubleClick(bool refuse) 506 { 507 LaunchButton::SetIgnoreDoubleClick(refuse); 508 509 _NotifySettingsChanged(); 510 } 511 512 513 bool 514 PadView::IgnoreDoubleClick() const 515 { 516 return LaunchButton::IgnoreDoubleClick(); 517 } 518 519 520 void 521 PadView::_NotifySettingsChanged() 522 { 523 be_app->PostMessage(MSG_SETTINGS_CHANGED); 524 } 525