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
38PrinterListView::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
49PrinterListView::~PrinterListView()
50{
51	while (!IsEmpty())
52		delete RemoveItem((int32)0);
53}
54
55
56void
57PrinterListView::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
82void
83PrinterListView::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
121bool
122PrinterListView::QuitRequested()
123{
124	delete fFolder;
125	return true;
126}
127
128
129void
130PrinterListView::UpdateItem(PrinterItem* item)
131{
132	item->UpdatePendingJobs();
133	InvalidateItem(IndexOf(item));
134}
135
136
137PrinterItem*
138PrinterListView::ActivePrinter() const
139{
140	return fActivePrinter;
141}
142
143
144void
145PrinterListView::SetActivePrinter(PrinterItem* item)
146{
147	fActivePrinter = item;
148}
149
150
151PrinterItem*
152PrinterListView::SelectedItem() const
153{
154	return dynamic_cast<PrinterItem*>(ItemAt(CurrentSelection()));
155}
156
157
158// FolderListener interface
159
160void
161PrinterListView::EntryCreated(node_ref* node, entry_ref* entry)
162{
163	BDirectory printer(node);
164	_AddPrinter(printer, true);
165}
166
167
168void
169PrinterListView::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
182void
183PrinterListView::AttributeChanged(node_ref* node)
184{
185	BDirectory printer(node);
186	_AddPrinter(printer, true);
187}
188
189
190// private methods
191
192void
193PrinterListView::_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
219void
220PrinterListView::_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
242PrinterItem*
243PrinterListView::_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
259BBitmap* PrinterItem::sIcon = NULL;
260BBitmap* PrinterItem::sSelectedIcon = NULL;
261
262
263PrinterItem::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
327PrinterItem::~PrinterItem()
328{
329	delete fFolder;
330}
331
332
333void
334PrinterItem::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
348void
349PrinterItem::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
360bool 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
375void
376PrinterItem::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
482bool
483PrinterItem::IsActivePrinter() const
484{
485	return fName == ActivePrinterName();
486}
487
488
489bool
490PrinterItem::HasPendingJobs() const
491{
492	return fFolder && fFolder->CountJobs() > 0;
493}
494
495
496SpoolFolder*
497PrinterItem::Folder() const
498{
499	return fFolder;
500}
501
502
503BDirectory*
504PrinterItem::Node()
505{
506	return &fNode;
507}
508
509
510void
511PrinterItem::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
526void
527PrinterItem::_GetStringProperty(const char* propName, BString& outString)
528{
529	fNode.ReadAttrString(propName, &outString);
530}
531
532
533BBitmap*
534PrinterItem::_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