xref: /haiku/src/preferences/printers/PrinterListView.cpp (revision f2b4344867e97c3f4e742a1b4a15e6879644601a)
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_TRANSLATE_CONTEXT
28 #define B_TRANSLATE_CONTEXT "PrinterListView"
29 
30 
31 // #pragma mark -- PrinterListView
32 
33 
34 PrinterListView::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 
45 PrinterListView::~PrinterListView()
46 {
47 	while (!IsEmpty())
48 		delete RemoveItem(0L);
49 }
50 
51 
52 void
53 PrinterListView::BuildPrinterList()
54 {
55 	// clear list
56 	while (!IsEmpty())
57 		delete RemoveItem(0L);
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 
78 void
79 PrinterListView::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 
117 bool
118 PrinterListView::QuitRequested()
119 {
120 	delete fFolder;
121 	return true;
122 }
123 
124 
125 void
126 PrinterListView::UpdateItem(PrinterItem* item)
127 {
128 	item->UpdatePendingJobs();
129 	InvalidateItem(IndexOf(item));
130 }
131 
132 
133 PrinterItem*
134 PrinterListView::ActivePrinter() const
135 {
136 	return fActivePrinter;
137 }
138 
139 
140 void
141 PrinterListView::SetActivePrinter(PrinterItem* item)
142 {
143 	fActivePrinter = item;
144 }
145 
146 
147 PrinterItem*
148 PrinterListView::SelectedItem() const
149 {
150 	return dynamic_cast<PrinterItem*>(ItemAt(CurrentSelection()));
151 }
152 
153 
154 // FolderListener interface
155 
156 void
157 PrinterListView::EntryCreated(node_ref* node, entry_ref* entry)
158 {
159 	BDirectory printer(node);
160 	_AddPrinter(printer, true);
161 }
162 
163 
164 void
165 PrinterListView::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 
178 void
179 PrinterListView::AttributeChanged(node_ref* node)
180 {
181 	BDirectory printer(node);
182 	_AddPrinter(printer, true);
183 }
184 
185 
186 // private methods
187 
188 void
189 PrinterListView::_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 
215 void
216 PrinterListView::_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 
238 PrinterItem*
239 PrinterListView::_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 
255 BBitmap* PrinterItem::sIcon = NULL;
256 BBitmap* PrinterItem::sSelectedIcon = NULL;
257 
258 
259 PrinterItem::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 
321 PrinterItem::~PrinterItem()
322 {
323 	delete fFolder;
324 }
325 
326 
327 void
328 PrinterItem::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 
342 void
343 PrinterItem::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 
354 bool 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 
369 void
370 PrinterItem::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 
480 bool
481 PrinterItem::IsActivePrinter() const
482 {
483 	return fName == ActivePrinterName();
484 }
485 
486 
487 bool
488 PrinterItem::HasPendingJobs() const
489 {
490 	return fFolder && fFolder->CountJobs() > 0;
491 }
492 
493 
494 SpoolFolder*
495 PrinterItem::Folder() const
496 {
497 	return fFolder;
498 }
499 
500 
501 BDirectory*
502 PrinterItem::Node()
503 {
504 	return &fNode;
505 }
506 
507 
508 void
509 PrinterItem::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 
525 void
526 PrinterItem::_GetStringProperty(const char* propName, BString& outString)
527 {
528 	fNode.ReadAttrString(propName, &outString);
529 }
530 
531