⛏️ index : haiku.git

/*
 * Copyright 2001-2010, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *		Michael Pfeiffer
 */


#include "PrinterListView.h"

#include <Application.h>
#include <Bitmap.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Directory.h>
#include <IconUtils.h>
#include <Locale.h>
#include <Mime.h>
#include <NodeInfo.h>
#include <Resources.h>
#include <String.h>
#include <StringFormat.h>

#include "pr_server.h"
#include "Messages.h"
#include "Globals.h"
#include "PrintersWindow.h"
#include "SpoolFolder.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PrinterListView"


// #pragma mark -- PrinterListView


PrinterListView::PrinterListView(BRect frame)
	: Inherited(frame, "printers_list", B_SINGLE_SELECTION_LIST, B_FOLLOW_ALL,
		B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE | B_FULL_UPDATE_ON_RESIZE),
	fFolder(NULL),
	fActivePrinter(NULL)
{
	fLayoutData.fLeftColumnMaximumWidth = 100;
	fLayoutData.fRightColumnMaximumWidth = 100;
}


PrinterListView::~PrinterListView()
{
	while (!IsEmpty())
		delete RemoveItem((int32)0);
}


void
PrinterListView::BuildPrinterList()
{
	// clear list
	while (!IsEmpty())
		delete RemoveItem((int32)0);

	// Find directory containing printer definition nodes
	BPath path;
	if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK)
		return;

	BDirectory dir(path.Path());
	if (dir.InitCheck() != B_OK)
		return;

	BEntry entry;
	while(dir.GetNextEntry(&entry) == B_OK) {
		BDirectory printer(&entry);
		_AddPrinter(printer, false);
	}

	_LayoutPrinterItems();
}


void
PrinterListView::AttachedToWindow()
{
	Inherited::AttachedToWindow();

	SetSelectionMessage(new BMessage(kMsgPrinterSelected));
	SetInvocationMessage(new BMessage(kMsgMakeDefaultPrinter));
	SetTarget(Window());

	BPath path;
	if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK)
		return;

	BDirectory dir(path.Path());
	if (dir.InitCheck() != B_OK) {
		// directory has to exist in order to start watching it
		if (create_directory(path.Path(), 0777) != B_OK)
			return;
		dir.SetTo(path.Path());
	}

	fFolder = new FolderWatcher(Window(), dir, true);
	fFolder->SetListener(this);

	BuildPrinterList();

	// Select active printer
	BString activePrinterName(ActivePrinterName());
	for (int32 i = 0; i < CountItems(); i ++) {
		PrinterItem* item = dynamic_cast<PrinterItem*>(ItemAt(i));
		if (item != NULL && item->Name() == activePrinterName) {
			Select(i);
			fActivePrinter = item;
			break;
		}
	}
}


bool
PrinterListView::QuitRequested()
{
	delete fFolder;
	return true;
}


void
PrinterListView::UpdateItem(PrinterItem* item)
{
	item->UpdatePendingJobs();
	InvalidateItem(IndexOf(item));
}


PrinterItem*
PrinterListView::ActivePrinter() const
{
	return fActivePrinter;
}


void
PrinterListView::SetActivePrinter(PrinterItem* item)
{
	fActivePrinter = item;
}


PrinterItem*
PrinterListView::SelectedItem() const
{
	return dynamic_cast<PrinterItem*>(ItemAt(CurrentSelection()));
}


// FolderListener interface

void
PrinterListView::EntryCreated(node_ref* node, entry_ref* entry)
{
	BDirectory printer(node);
	_AddPrinter(printer, true);
}


void
PrinterListView::EntryRemoved(node_ref* node)
{
	PrinterItem* item = _FindItem(node);
	if (item) {
		if (item == fActivePrinter)
			fActivePrinter = NULL;

		RemoveItem(item);
		delete item;
	}
}


void
PrinterListView::AttributeChanged(node_ref* node)
{
	BDirectory printer(node);
	_AddPrinter(printer, true);
}


// private methods

void
PrinterListView::_AddPrinter(BDirectory& printer, bool calculateLayout)
{
	BString state;
	node_ref node;
		// If the entry is a directory
	if (printer.InitCheck() == B_OK
		&& printer.GetNodeRef(&node) == B_OK
		&& _FindItem(&node) == NULL
		&& printer.ReadAttrString(PSRV_PRINTER_ATTR_STATE, &state) == B_OK
		&& state == "free") {
			// Check it's Mime type for a spool director
		BNodeInfo info(&printer);
		char buffer[256];

		if (info.GetType(buffer) == B_OK
			&& strcmp(buffer, PSRV_PRINTER_FILETYPE) == 0) {
				// Yes, it is a printer definition node
			AddItem(new PrinterItem(static_cast<PrintersWindow*>(Window()),
				printer, fLayoutData));
			if (calculateLayout)
				_LayoutPrinterItems();
		}
	}
}


void
PrinterListView::_LayoutPrinterItems()
{
	float& leftColumnMaximumWidth = fLayoutData.fLeftColumnMaximumWidth;
	float& rightColumnMaximumWidth = fLayoutData.fRightColumnMaximumWidth;

	for (int32 i = 0; i < CountItems(); i ++) {
		PrinterItem* item = static_cast<PrinterItem*>(ItemAt(i));

		float leftColumnWidth = 0;
		float rightColumnWidth = 0;
		item->GetColumnWidth(this, leftColumnWidth, rightColumnWidth);

		leftColumnMaximumWidth = MAX(leftColumnMaximumWidth,
			leftColumnWidth);
		rightColumnMaximumWidth = MAX(rightColumnMaximumWidth,
			rightColumnWidth);
	}

	Invalidate();
}


PrinterItem*
PrinterListView::_FindItem(node_ref* node) const
{
	for (int32 i = CountItems() - 1; i >= 0; i--) {
		PrinterItem* item = dynamic_cast<PrinterItem*>(ItemAt(i));
		node_ref ref;
		if (item && item->Node()->GetNodeRef(&ref) == B_OK && ref == *node)
			return item;
	}
	return NULL;
}



// #pragma mark -- PrinterItem


BBitmap* PrinterItem::sIcon = NULL;
BBitmap* PrinterItem::sSelectedIcon = NULL;


PrinterItem::PrinterItem(PrintersWindow* window, const BDirectory& node,
		PrinterListLayoutData& layoutData)
	: BListItem(0, false),
	fFolder(NULL),
	fNode(node),
	fLayoutData(layoutData)
{
	BRect rect(BPoint(0, 0), be_control_look->ComposeIconSize(B_LARGE_ICON));
	if (sIcon == NULL) {
		sIcon = new BBitmap(rect, B_RGBA32);
		BMimeType type(PSRV_PRINTER_FILETYPE);
		type.GetIcon(sIcon, (icon_size)(rect.IntegerHeight() + 1));
	}

	if (sIcon && sIcon->IsValid() && sSelectedIcon == NULL) {
		const float checkMarkIconSize = be_control_look->ComposeIconSize(20).Height();
		BBitmap *checkMark = _LoadVectorIcon("check_mark_icon",
			checkMarkIconSize);
		if (checkMark && checkMark->IsValid()) {
			sSelectedIcon = new BBitmap(rect, B_RGBA32, true);
			if (sSelectedIcon && sSelectedIcon->IsValid()) {
				// draw check mark at bottom left over printer icon
				BView *view = new BView(rect, "offscreen", B_FOLLOW_ALL,
					B_WILL_DRAW);
				float y = rect.Height() - checkMark->Bounds().Height();
				sSelectedIcon->Lock();
				sSelectedIcon->AddChild(view);
				view->DrawBitmap(sIcon);
				view->SetDrawingMode(B_OP_ALPHA);
				view->DrawBitmap(checkMark, BPoint(0, y));
				view->Sync();
				view->RemoveSelf();
				sSelectedIcon->Unlock();
				delete view;
			}
		}
		delete checkMark;
	}

	// Get Name of printer
	_GetStringProperty(PSRV_PRINTER_ATTR_PRT_NAME, fName);
	_GetStringProperty(PSRV_PRINTER_ATTR_COMMENTS, fComments);
	_GetStringProperty(PSRV_PRINTER_ATTR_TRANSPORT, fTransport);
	_GetStringProperty(PSRV_PRINTER_ATTR_TRANSPORT_ADDR, fTransportAddress);
	_GetStringProperty(PSRV_PRINTER_ATTR_DRV_NAME, fDriverName);

	BPath path;
	if (find_directory(B_USER_PRINTERS_DIRECTORY, &path) != B_OK)
		return;

	// Setup spool folder
	path.Append(fName.String());
	BDirectory dir(path.Path());
	if (dir.InitCheck() == B_OK) {
		fFolder = new SpoolFolder(window, this, dir);
		UpdatePendingJobs();
	}
}


PrinterItem::~PrinterItem()
{
	delete fFolder;
}


void
PrinterItem::GetColumnWidth(BView* view, float& leftColumn, float& rightColumn)
{
	BFont font;
	view->GetFont(&font);

	leftColumn = font.StringWidth(fName.String());
	leftColumn = MAX(leftColumn, font.StringWidth(fDriverName.String()));

	rightColumn = font.StringWidth(fPendingJobs.String());
	rightColumn = MAX(rightColumn, font.StringWidth(fTransport.String()));
	rightColumn = MAX(rightColumn, font.StringWidth(fComments.String()));
}


void
PrinterItem::Update(BView *owner, const BFont *font)
{
	BListItem::Update(owner,font);

	font_height height;
	font->GetHeight(&height);

	SetHeight((height.ascent + height.descent + height.leading) * 3.0 + 8.0);
}


bool PrinterItem::Remove(BListView* view)
{
	BMessenger msgr;
	if (GetPrinterServerMessenger(msgr) == B_OK) {
		BMessage script(B_DELETE_PROPERTY);
		script.AddSpecifier("Printer", view->IndexOf(this));

		BMessage reply;
		if (msgr.SendMessage(&script,&reply) == B_OK)
			return true;
	}
	return false;
}


void
PrinterItem::DrawItem(BView *owner, BRect /*bounds*/, bool complete)
{
	BListView* list = dynamic_cast<BListView*>(owner);
	if (list == NULL)
		return;

	BFont font;
	owner->GetFont(&font);

	font_height height;
	font.GetHeight(&height);

	float fntheight = height.ascent + height.descent + height.leading;

	BRect bounds = list->ItemFrame(list->IndexOf(this));

	rgb_color color = owner->ViewColor();
	rgb_color oldLowColor = owner->LowColor();
	rgb_color oldHighColor = owner->HighColor();

	if (IsSelected())
		color = ui_color(B_LIST_SELECTED_BACKGROUND_COLOR);

	owner->SetLowColor(color);
	owner->SetHighColor(color);

	owner->FillRect(bounds);

	owner->SetLowColor(oldLowColor);
	owner->SetHighColor(oldHighColor);

	float iconColumnWidth = B_LARGE_ICON + 8.0;
	if (sIcon)
		iconColumnWidth = sIcon->Bounds().Height() + 8.0;
	float x = iconColumnWidth;
	BPoint iconPt(bounds.LeftTop() + BPoint(2.0, 2.0));
	BPoint namePt(iconPt + BPoint(x, fntheight));
	BPoint driverPt(iconPt + BPoint(x, fntheight * 2.0));
	BPoint defaultPt(iconPt + BPoint(x, fntheight * 3.0));
	BPoint transportPt(iconPt + BPoint(x, fntheight * 3.0));

	float totalWidth = bounds.Width() - iconColumnWidth;
	float maximumWidth = fLayoutData.fLeftColumnMaximumWidth +
		fLayoutData.fRightColumnMaximumWidth;
	float width;
	if (totalWidth < maximumWidth) {
		width = fLayoutData.fRightColumnMaximumWidth * totalWidth /
			maximumWidth;
	} else {
		width = fLayoutData.fRightColumnMaximumWidth;
	}

	BPoint pendingPt(bounds.right - width - 8.0, namePt.y);
	BPoint commentPt(bounds.right - width - 8.0, driverPt.y);


	drawing_mode mode = owner->DrawingMode();
#ifdef HAIKU_TARGET_PLATFORM_HAIKU
	owner->SetDrawingMode(B_OP_ALPHA);
#else
	owner->SetDrawingMode(B_OP_OVER);
#endif
	if (IsActivePrinter()) {
		if (sSelectedIcon && sSelectedIcon->IsValid())
			owner->DrawBitmap(sSelectedIcon, iconPt);
		else
			owner->DrawString(B_TRANSLATE("Default Printer"), defaultPt);
	} else {
		if (sIcon && sIcon->IsValid())
			owner->DrawBitmap(sIcon, iconPt);
	}

	owner->SetDrawingMode(B_OP_OVER);

	// left of item
	BString s = fName;
	owner->SetFont(be_bold_font);
	owner->TruncateString(&s, B_TRUNCATE_MIDDLE, pendingPt.x - namePt.x);
	owner->DrawString(s.String(), s.Length(), namePt);
	owner->SetFont(&font);

	s = B_TRANSLATE("Driver: %driver%");
	s.ReplaceFirst("%driver%", fDriverName);
	owner->TruncateString(&s, B_TRUNCATE_END, commentPt.x - driverPt.x);
	owner->DrawString(s.String(), s.Length(), driverPt);


	if (fTransport.Length() > 0) {
		s = B_TRANSLATE("Transport: %transport% %transport_address%");
		s.ReplaceFirst("%transport%", fTransport);
		s.ReplaceFirst("%transport_address%", fTransportAddress);
		owner->TruncateString(&s, B_TRUNCATE_BEGINNING, totalWidth);
		owner->DrawString(s.String(), s.Length(), transportPt);
	}

	// right of item
	s = fPendingJobs;
	owner->TruncateString(&s, B_TRUNCATE_END, bounds.Width() - pendingPt.x);
	owner->DrawString(s.String(), s.Length(), pendingPt);

	s = fComments;
	owner->TruncateString(&s, B_TRUNCATE_MIDDLE, bounds.Width() - commentPt.x);
	owner->DrawString(s.String(), s.Length(), commentPt);

	owner->SetDrawingMode(mode);
}


bool
PrinterItem::IsActivePrinter() const
{
	return fName == ActivePrinterName();
}


bool
PrinterItem::HasPendingJobs() const
{
	return fFolder && fFolder->CountJobs() > 0;
}


SpoolFolder*
PrinterItem::Folder() const
{
	return fFolder;
}


BDirectory*
PrinterItem::Node()
{
	return &fNode;
}


void
PrinterItem::UpdatePendingJobs()
{
	uint32 pendingJobs = 0;
	if (fFolder)
		pendingJobs = fFolder->CountJobs();

	static BStringFormat format(B_TRANSLATE("{0, plural,"
		"=0{No pending jobs}"
		"=1{1 pending job}"
		"other{# pending jobs}}"));

	format.Format(fPendingJobs, pendingJobs);
}


void
PrinterItem::_GetStringProperty(const char* propName, BString& outString)
{
	fNode.ReadAttrString(propName, &outString);
}


BBitmap*
PrinterItem::_LoadVectorIcon(const char* resourceName, float iconSize)
{
	size_t dataSize;
	BResources* resources = BApplication::AppResources();
	const void* data = resources->LoadResource(B_VECTOR_ICON_TYPE,
		resourceName, &dataSize);

	if (data != NULL){
		BBitmap *iconBitmap = new BBitmap(BRect(0, 0, iconSize - 1,
			iconSize - 1), 0, B_RGBA32);
		if (BIconUtils::GetVectorIcon(
				reinterpret_cast<const uint8*>(data),
				dataSize, iconBitmap) == B_OK)
			return iconBitmap;
		else
			delete iconBitmap;
	};
	return NULL;
}