xref: /haiku/src/preferences/printers/PrinterListView.cpp (revision 39c461f50cc0bc1c419428d123e5e017e7494076)
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 
PrinterListView(BRect frame)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 
~PrinterListView()49 PrinterListView::~PrinterListView()
50 {
51 	while (!IsEmpty())
52 		delete RemoveItem((int32)0);
53 }
54 
55 
56 void
BuildPrinterList()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
AttachedToWindow()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
QuitRequested()122 PrinterListView::QuitRequested()
123 {
124 	delete fFolder;
125 	return true;
126 }
127 
128 
129 void
UpdateItem(PrinterItem * item)130 PrinterListView::UpdateItem(PrinterItem* item)
131 {
132 	item->UpdatePendingJobs();
133 	InvalidateItem(IndexOf(item));
134 }
135 
136 
137 PrinterItem*
ActivePrinter() const138 PrinterListView::ActivePrinter() const
139 {
140 	return fActivePrinter;
141 }
142 
143 
144 void
SetActivePrinter(PrinterItem * item)145 PrinterListView::SetActivePrinter(PrinterItem* item)
146 {
147 	fActivePrinter = item;
148 }
149 
150 
151 PrinterItem*
SelectedItem() const152 PrinterListView::SelectedItem() const
153 {
154 	return dynamic_cast<PrinterItem*>(ItemAt(CurrentSelection()));
155 }
156 
157 
158 // FolderListener interface
159 
160 void
EntryCreated(node_ref * node,entry_ref * entry)161 PrinterListView::EntryCreated(node_ref* node, entry_ref* entry)
162 {
163 	BDirectory printer(node);
164 	_AddPrinter(printer, true);
165 }
166 
167 
168 void
EntryRemoved(node_ref * node)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
AttributeChanged(node_ref * node)183 PrinterListView::AttributeChanged(node_ref* node)
184 {
185 	BDirectory printer(node);
186 	_AddPrinter(printer, true);
187 }
188 
189 
190 // private methods
191 
192 void
_AddPrinter(BDirectory & printer,bool calculateLayout)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
_LayoutPrinterItems()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*
_FindItem(node_ref * node) const243 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 
PrinterItem(PrintersWindow * window,const BDirectory & node,PrinterListLayoutData & layoutData)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 
~PrinterItem()327 PrinterItem::~PrinterItem()
328 {
329 	delete fFolder;
330 }
331 
332 
333 void
GetColumnWidth(BView * view,float & leftColumn,float & rightColumn)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
Update(BView * owner,const BFont * font)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 
Remove(BListView * view)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
DrawItem(BView * owner,BRect,bool complete)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
IsActivePrinter() const483 PrinterItem::IsActivePrinter() const
484 {
485 	return fName == ActivePrinterName();
486 }
487 
488 
489 bool
HasPendingJobs() const490 PrinterItem::HasPendingJobs() const
491 {
492 	return fFolder && fFolder->CountJobs() > 0;
493 }
494 
495 
496 SpoolFolder*
Folder() const497 PrinterItem::Folder() const
498 {
499 	return fFolder;
500 }
501 
502 
503 BDirectory*
Node()504 PrinterItem::Node()
505 {
506 	return &fNode;
507 }
508 
509 
510 void
UpdatePendingJobs()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
_GetStringProperty(const char * propName,BString & outString)527 PrinterItem::_GetStringProperty(const char* propName, BString& outString)
528 {
529 	fNode.ReadAttrString(propName, &outString);
530 }
531 
532 
533 BBitmap*
_LoadVectorIcon(const char * resourceName,float iconSize)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