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