1 /* 2 * Copyright 2001-2010, Haiku. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Michael Pfeiffer 7 */ 8 9 10 #include "PrinterListView.h" 11 12 #include <Application.h> 13 #include <Bitmap.h> 14 #include <Catalog.h> 15 #include <Directory.h> 16 #include <IconUtils.h> 17 #include <Locale.h> 18 #include <Mime.h> 19 #include <NodeInfo.h> 20 #include <Resources.h> 21 #include <String.h> 22 #include <StringFormat.h> 23 24 #include "pr_server.h" 25 #include "Messages.h" 26 #include "Globals.h" 27 #include "PrintersWindow.h" 28 #include "SpoolFolder.h" 29 30 31 #undef B_TRANSLATION_CONTEXT 32 #define B_TRANSLATION_CONTEXT "PrinterListView" 33 34 35 // #pragma mark -- PrinterListView 36 37 38 PrinterListView::PrinterListView(BRect frame) 39 : Inherited(frame, "printers_list", B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL, 40 B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE), 41 fFolder(NULL), 42 fActivePrinter(NULL) 43 { 44 fLayoutData.fLeftColumnMaximumWidth = 100; 45 fLayoutData.fRightColumnMaximumWidth = 100; 46 } 47 48 49 PrinterListView::~PrinterListView() 50 { 51 while (!IsEmpty()) 52 delete RemoveItem((int32)0); 53 } 54 55 56 void 57 PrinterListView::BuildPrinterList() 58 { 59 // clear list 60 while (!IsEmpty()) 61 delete RemoveItem((int32)0); 62 63 // Find directory containing printer definition nodes 64 BPath path; 65 if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK) 66 return; 67 68 BDirectory dir(path.Path()); 69 if (dir.InitCheck() != B_OK) 70 return; 71 72 BEntry entry; 73 while(dir.GetNextEntry(&entry) == B_OK) { 74 BDirectory printer(&entry); 75 _AddPrinter(printer, false); 76 } 77 78 _LayoutPrinterItems(); 79 } 80 81 82 void 83 PrinterListView::AttachedToWindow() 84 { 85 Inherited::AttachedToWindow(); 86 87 SetSelectionMessage(new BMessage(kMsgPrinterSelected)); 88 SetInvocationMessage(new BMessage(kMsgMakeDefaultPrinter)); 89 SetTarget(Window()); 90 91 BPath path; 92 if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK) 93 return; 94 95 BDirectory dir(path.Path()); 96 if (dir.InitCheck() != B_OK) { 97 // directory has to exist in order to start watching it 98 if (create_directory(path.Path(), 0777) != B_OK) 99 return; 100 dir.SetTo(path.Path()); 101 } 102 103 fFolder = new FolderWatcher(Window(), dir, true); 104 fFolder->SetListener(this); 105 106 BuildPrinterList(); 107 108 // Select active printer 109 BString activePrinterName(ActivePrinterName()); 110 for (int32 i = 0; i < CountItems(); i ++) { 111 PrinterItem* item = dynamic_cast<PrinterItem*>(ItemAt(i)); 112 if (item != NULL && item->Name() == activePrinterName) { 113 Select(i); 114 fActivePrinter = item; 115 break; 116 } 117 } 118 } 119 120 121 bool 122 PrinterListView::QuitRequested() 123 { 124 delete fFolder; 125 return true; 126 } 127 128 129 void 130 PrinterListView::UpdateItem(PrinterItem* item) 131 { 132 item->UpdatePendingJobs(); 133 InvalidateItem(IndexOf(item)); 134 } 135 136 137 PrinterItem* 138 PrinterListView::ActivePrinter() const 139 { 140 return fActivePrinter; 141 } 142 143 144 void 145 PrinterListView::SetActivePrinter(PrinterItem* item) 146 { 147 fActivePrinter = item; 148 } 149 150 151 PrinterItem* 152 PrinterListView::SelectedItem() const 153 { 154 return dynamic_cast<PrinterItem*>(ItemAt(CurrentSelection())); 155 } 156 157 158 // FolderListener interface 159 160 void 161 PrinterListView::EntryCreated(node_ref* node, entry_ref* entry) 162 { 163 BDirectory printer(node); 164 _AddPrinter(printer, true); 165 } 166 167 168 void 169 PrinterListView::EntryRemoved(node_ref* node) 170 { 171 PrinterItem* item = _FindItem(node); 172 if (item) { 173 if (item == fActivePrinter) 174 fActivePrinter = NULL; 175 176 RemoveItem(item); 177 delete item; 178 } 179 } 180 181 182 void 183 PrinterListView::AttributeChanged(node_ref* node) 184 { 185 BDirectory printer(node); 186 _AddPrinter(printer, true); 187 } 188 189 190 // private methods 191 192 void 193 PrinterListView::_AddPrinter(BDirectory& printer, bool calculateLayout) 194 { 195 BString state; 196 node_ref node; 197 // If the entry is a directory 198 if (printer.InitCheck() == B_OK 199 && printer.GetNodeRef(&node) == B_OK 200 && _FindItem(&node) == NULL 201 && printer.ReadAttrString(PSRV_PRINTER_ATTR_STATE, &state) == B_OK 202 && state == "free") { 203 // Check it's Mime type for a spool director 204 BNodeInfo info(&printer); 205 char buffer[256]; 206 207 if (info.GetType(buffer) == B_OK 208 && strcmp(buffer, PSRV_PRINTER_FILETYPE) == 0) { 209 // Yes, it is a printer definition node 210 AddItem(new PrinterItem(static_cast<PrintersWindow*>(Window()), 211 printer, fLayoutData)); 212 if (calculateLayout) 213 _LayoutPrinterItems(); 214 } 215 } 216 } 217 218 219 void 220 PrinterListView::_LayoutPrinterItems() 221 { 222 float& leftColumnMaximumWidth = fLayoutData.fLeftColumnMaximumWidth; 223 float& rightColumnMaximumWidth = fLayoutData.fRightColumnMaximumWidth; 224 225 for (int32 i = 0; i < CountItems(); i ++) { 226 PrinterItem* item = static_cast<PrinterItem*>(ItemAt(i)); 227 228 float leftColumnWidth = 0; 229 float rightColumnWidth = 0; 230 item->GetColumnWidth(this, leftColumnWidth, rightColumnWidth); 231 232 leftColumnMaximumWidth = MAX(leftColumnMaximumWidth, 233 leftColumnWidth); 234 rightColumnMaximumWidth = MAX(rightColumnMaximumWidth, 235 rightColumnWidth); 236 } 237 238 Invalidate(); 239 } 240 241 242 PrinterItem* 243 PrinterListView::_FindItem(node_ref* node) const 244 { 245 for (int32 i = CountItems() - 1; i >= 0; i--) { 246 PrinterItem* item = dynamic_cast<PrinterItem*>(ItemAt(i)); 247 node_ref ref; 248 if (item && item->Node()->GetNodeRef(&ref) == B_OK && ref == *node) 249 return item; 250 } 251 return NULL; 252 } 253 254 255 256 // #pragma mark -- PrinterItem 257 258 259 BBitmap* PrinterItem::sIcon = NULL; 260 BBitmap* PrinterItem::sSelectedIcon = NULL; 261 262 263 PrinterItem::PrinterItem(PrintersWindow* window, const BDirectory& node, 264 PrinterListLayoutData& layoutData) 265 : BListItem(0, false), 266 fFolder(NULL), 267 fNode(node), 268 fLayoutData(layoutData) 269 { 270 BRect rect(0, 0, B_LARGE_ICON - 1, B_LARGE_ICON - 1); 271 if (sIcon == NULL) { 272 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 273 sIcon = new BBitmap(rect, B_RGBA32); 274 #else 275 sIcon = new BBitmap(rect, B_CMAP8); 276 #endif 277 BMimeType type(PSRV_PRINTER_FILETYPE); 278 type.GetIcon(sIcon, B_LARGE_ICON); 279 } 280 281 if (sIcon && sIcon->IsValid() && sSelectedIcon == NULL) { 282 const float checkMarkIconSize = 20.0; 283 BBitmap *checkMark = _LoadVectorIcon("check_mark_icon", 284 checkMarkIconSize); 285 if (checkMark && checkMark->IsValid()) { 286 sSelectedIcon = new BBitmap(rect, B_RGBA32, true); 287 if (sSelectedIcon && sSelectedIcon->IsValid()) { 288 // draw check mark at bottom left over printer icon 289 BView *view = new BView(rect, "offscreen", B_FOLLOW_ALL, 290 B_WILL_DRAW); 291 float y = rect.Height() - checkMark->Bounds().Height(); 292 sSelectedIcon->Lock(); 293 sSelectedIcon->AddChild(view); 294 view->DrawBitmap(sIcon); 295 view->SetDrawingMode(B_OP_ALPHA); 296 view->DrawBitmap(checkMark, BPoint(0, y)); 297 view->Sync(); 298 view->RemoveSelf(); 299 sSelectedIcon->Unlock(); 300 delete view; 301 } 302 } 303 delete checkMark; 304 } 305 306 // Get Name of printer 307 _GetStringProperty(PSRV_PRINTER_ATTR_PRT_NAME, fName); 308 _GetStringProperty(PSRV_PRINTER_ATTR_COMMENTS, fComments); 309 _GetStringProperty(PSRV_PRINTER_ATTR_TRANSPORT, fTransport); 310 _GetStringProperty(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, fTransportAddress); 311 _GetStringProperty(PSRV_PRINTER_ATTR_DRV_NAME, fDriverName); 312 313 BPath path; 314 if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK) 315 return; 316 317 // Setup spool folder 318 path.Append(fName.String()); 319 BDirectory dir(path.Path()); 320 if (dir.InitCheck() == B_OK) { 321 fFolder = new SpoolFolder(window, this, dir); 322 UpdatePendingJobs(); 323 } 324 } 325 326 327 PrinterItem::~PrinterItem() 328 { 329 delete fFolder; 330 } 331 332 333 void 334 PrinterItem::GetColumnWidth(BView* view, float& leftColumn, float& rightColumn) 335 { 336 BFont font; 337 view->GetFont(&font); 338 339 leftColumn = font.StringWidth(fName.String()); 340 leftColumn = MAX(leftColumn, font.StringWidth(fDriverName.String())); 341 342 rightColumn = font.StringWidth(fPendingJobs.String()); 343 rightColumn = MAX(rightColumn, font.StringWidth(fTransport.String())); 344 rightColumn = MAX(rightColumn, font.StringWidth(fComments.String())); 345 } 346 347 348 void 349 PrinterItem::Update(BView *owner, const BFont *font) 350 { 351 BListItem::Update(owner,font); 352 353 font_height height; 354 font->GetHeight(&height); 355 356 SetHeight((height.ascent + height.descent + height.leading) * 3.0 + 8.0); 357 } 358 359 360 bool PrinterItem::Remove(BListView* view) 361 { 362 BMessenger msgr; 363 if (GetPrinterServerMessenger(msgr) == B_OK) { 364 BMessage script(B_DELETE_PROPERTY); 365 script.AddSpecifier("Printer", view->IndexOf(this)); 366 367 BMessage reply; 368 if (msgr.SendMessage(&script,&reply) == B_OK) 369 return true; 370 } 371 return false; 372 } 373 374 375 void 376 PrinterItem::DrawItem(BView *owner, BRect /*bounds*/, bool complete) 377 { 378 BListView* list = dynamic_cast<BListView*>(owner); 379 if (list == NULL) 380 return; 381 382 BFont font; 383 owner->GetFont(&font); 384 385 font_height height; 386 font.GetHeight(&height); 387 388 float fntheight = height.ascent + height.descent + height.leading; 389 390 BRect bounds = list->ItemFrame(list->IndexOf(this)); 391 392 rgb_color color = owner->ViewColor(); 393 rgb_color oldLowColor = owner->LowColor(); 394 rgb_color oldHighColor = owner->HighColor(); 395 396 if (IsSelected()) 397 color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR); 398 399 owner->SetLowColor(color); 400 owner->SetHighColor(color); 401 402 owner->FillRect(bounds); 403 404 owner->SetLowColor(oldLowColor); 405 owner->SetHighColor(oldHighColor); 406 407 float iconColumnWidth = B_LARGE_ICON + 8.0; 408 float x = iconColumnWidth; 409 BPoint iconPt(bounds.LeftTop() + BPoint(2.0, 2.0)); 410 BPoint namePt(iconPt + BPoint(x, fntheight)); 411 BPoint driverPt(iconPt + BPoint(x, fntheight * 2.0)); 412 BPoint defaultPt(iconPt + BPoint(x, fntheight * 3.0)); 413 BPoint transportPt(iconPt + BPoint(x, fntheight * 3.0)); 414 415 float totalWidth = bounds.Width() - iconColumnWidth; 416 float maximumWidth = fLayoutData.fLeftColumnMaximumWidth + 417 fLayoutData.fRightColumnMaximumWidth; 418 float width; 419 if (totalWidth < maximumWidth) { 420 width = fLayoutData.fRightColumnMaximumWidth * totalWidth / 421 maximumWidth; 422 } else { 423 width = fLayoutData.fRightColumnMaximumWidth; 424 } 425 426 BPoint pendingPt(bounds.right - width - 8.0, namePt.y); 427 BPoint commentPt(bounds.right - width - 8.0, driverPt.y); 428 429 430 drawing_mode mode = owner->DrawingMode(); 431 #ifdef HAIKU_TARGET_PLATFORM_HAIKU 432 owner->SetDrawingMode(B_OP_ALPHA); 433 #else 434 owner->SetDrawingMode(B_OP_OVER); 435 #endif 436 if (IsActivePrinter()) { 437 if (sSelectedIcon && sSelectedIcon->IsValid()) 438 owner->DrawBitmap(sSelectedIcon, iconPt); 439 else 440 owner->DrawString(B_TRANSLATE("Default Printer"), defaultPt); 441 } else { 442 if (sIcon && sIcon->IsValid()) 443 owner->DrawBitmap(sIcon, iconPt); 444 } 445 446 owner->SetDrawingMode(B_OP_OVER); 447 448 // left of item 449 BString s = fName; 450 owner->SetFont(be_bold_font); 451 owner->TruncateString(&s, B_TRUNCATE_MIDDLE, pendingPt.x - namePt.x); 452 owner->DrawString(s.String(), s.Length(), namePt); 453 owner->SetFont(&font); 454 455 s = B_TRANSLATE("Driver: %driver%"); 456 s.ReplaceFirst("%driver%", fDriverName); 457 owner->TruncateString(&s, B_TRUNCATE_END, commentPt.x - driverPt.x); 458 owner->DrawString(s.String(), s.Length(), driverPt); 459 460 461 if (fTransport.Length() > 0) { 462 s = B_TRANSLATE("Transport: %transport% %transport_address%"); 463 s.ReplaceFirst("%transport%", fTransport); 464 s.ReplaceFirst("%transport_address%", fTransportAddress); 465 owner->TruncateString(&s, B_TRUNCATE_BEGINNING, totalWidth); 466 owner->DrawString(s.String(), s.Length(), transportPt); 467 } 468 469 // right of item 470 s = fPendingJobs; 471 owner->TruncateString(&s, B_TRUNCATE_END, bounds.Width() - pendingPt.x); 472 owner->DrawString(s.String(), s.Length(), pendingPt); 473 474 s = fComments; 475 owner->TruncateString(&s, B_TRUNCATE_MIDDLE, bounds.Width() - commentPt.x); 476 owner->DrawString(s.String(), s.Length(), commentPt); 477 478 owner->SetDrawingMode(mode); 479 } 480 481 482 bool 483 PrinterItem::IsActivePrinter() const 484 { 485 return fName == ActivePrinterName(); 486 } 487 488 489 bool 490 PrinterItem::HasPendingJobs() const 491 { 492 return fFolder && fFolder->CountJobs() > 0; 493 } 494 495 496 SpoolFolder* 497 PrinterItem::Folder() const 498 { 499 return fFolder; 500 } 501 502 503 BDirectory* 504 PrinterItem::Node() 505 { 506 return &fNode; 507 } 508 509 510 void 511 PrinterItem::UpdatePendingJobs() 512 { 513 uint32 pendingJobs = 0; 514 if (fFolder) 515 pendingJobs = fFolder->CountJobs(); 516 517 static BStringFormat format(B_TRANSLATE("{0, plural," 518 "=0{No pending jobs}" 519 "=1{1 pending job}" 520 "other{# pending jobs}}")); 521 522 format.Format(fPendingJobs, pendingJobs); 523 } 524 525 526 void 527 PrinterItem::_GetStringProperty(const char* propName, BString& outString) 528 { 529 fNode.ReadAttrString(propName, &outString); 530 } 531 532 533 BBitmap* 534 PrinterItem::_LoadVectorIcon(const char* resourceName, float iconSize) 535 { 536 size_t dataSize; 537 BResources* resources = BApplication::AppResources(); 538 const void* data = resources->LoadResource(B_VECTOR_ICON_TYPE, 539 resourceName, &dataSize); 540 541 if (data != NULL){ 542 BBitmap *iconBitmap = new BBitmap(BRect(0, 0, iconSize - 1, 543 iconSize - 1), 0, B_RGBA32); 544 if (BIconUtils::GetVectorIcon( 545 reinterpret_cast<const uint8*>(data), 546 dataSize, iconBitmap) == B_OK) 547 return iconBitmap; 548 else 549 delete iconBitmap; 550 }; 551 return NULL; 552 } 553