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