root/src/preferences/printers/PrintersWindow.cpp
/*
 * Copyright 2001-2011, Haiku.
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Michael Pfeiffer
 *              Philippe Houdoin
 */


#include "PrintersWindow.h"

#include <stdio.h>

#include <Application.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <FindDirectory.h>
#include <GroupLayout.h>
#include <Layout.h>
#include <LayoutBuilder.h>
#include <ListView.h>
#include <Locale.h>
#include <PrintJob.h>
#include <ScrollView.h>

#include "pr_server.h"
#include "AddPrinterDialog.h"
#include "Globals.h"
#include "JobListView.h"
#include "Messages.h"
#include "PrinterListView.h"
#include "TestPageView.h"
#include "ScreenSettings.h"
#include "SpoolFolder.h"


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "PrintersWindow"


class TestPageWindow : public BWindow {
public:
                                                TestPageWindow(BPrintJob* job, PrinterItem* printer);
        virtual                         ~TestPageWindow();

                        void            MessageReceived(BMessage* message);
private:
                        BPrintJob*      fJob;
                        TestPageView*   fTestPage;
};


TestPageWindow::TestPageWindow(BPrintJob* job, PrinterItem* printer)
        : BWindow(job->PaperRect().OffsetByCopy(-20000, -20000),
                B_TRANSLATE("Test page"),
                B_TITLED_WINDOW, 0), fJob(job)
{
        fTestPage = new TestPageView(job->PrintableRect(), printer);

        // SetLayout(new BGroupLayout(B_VERTICAL));
        AddChild(fTestPage);
}


TestPageWindow::~TestPageWindow()
{
        delete fJob;
}


void
TestPageWindow::MessageReceived(BMessage* message)
{
        if (message->what != kMsgPrintTestPage) {
                BWindow::MessageReceived(message);
                return;
        }

        fJob->BeginJob();

        fJob->DrawView(fTestPage, fTestPage->Bounds(), B_ORIGIN);
        fJob->SpoolPage();

        if (!fJob->CanContinue())
                return;

        fJob->CommitJob();

        Quit();
}


// #pragma mark PrintersWindow main class


PrintersWindow::PrintersWindow(ScreenSettings* settings)
        :
        BWindow(settings->WindowFrame(), B_TRANSLATE_SYSTEM_NAME("Printers"),
                B_TITLED_WINDOW, B_AUTO_UPDATE_SIZE_LIMITS),
        fSettings(settings),
        fSelectedPrinter(NULL),
        fAddingPrinter(false)
{
        _BuildGUI();
        MoveOnScreen();
}


PrintersWindow::~PrintersWindow()
{
        delete fSettings;
}


bool
PrintersWindow::QuitRequested()
{
        fSettings->SetWindowFrame(Frame());

        bool result = Inherited::QuitRequested();
        if (result)
                be_app->PostMessage(B_QUIT_REQUESTED);

        return result;
}


void
PrintersWindow::MessageReceived(BMessage* msg)
{
        switch (msg->what) {
                case kMsgPrinterSelected:
                {
                        fSelectedPrinter = fPrinterListView->SelectedItem();
                        if (fSelectedPrinter) {
                                BString text = B_TRANSLATE("Print jobs for %printer_name%");
                                text.ReplaceFirst("%printer_name%", fSelectedPrinter->Name());

                                fJobsBox->SetLabel(text);
                                fMakeDefault->SetEnabled(true);
                                fRemove->SetEnabled(true);
                                fJobListView->SetSpoolFolder(fSelectedPrinter->Folder());
                        } else {
                                fJobsBox->SetLabel(
                                        B_TRANSLATE("Print jobs: No printer selected"));
                                fMakeDefault->SetEnabled(false);
                                fRemove->SetEnabled(false);
                                fSelectedPrinter = NULL;
                                fJobListView->SetSpoolFolder(NULL);
                        }
                        _UpdateJobButtons();
                        _UpdatePrinterButtons();
                        break;
                }

                case kMsgAddPrinter:
                        if (!fAddingPrinter) {
                                fAddingPrinter = true;
                                new AddPrinterDialog(this);
                        }
                        break;

                case kMsgAddPrinterClosed:
                        fAddingPrinter = false;
                        break;

                case kMsgRemovePrinter:
                {
                        fSelectedPrinter = fPrinterListView->SelectedItem();
                        if (fSelectedPrinter)
                                fSelectedPrinter->Remove(fPrinterListView);
                        break;
                }

                case kMsgMakeDefaultPrinter:
                {
                        PrinterItem* printer = fPrinterListView->SelectedItem();
                        if (printer && printer == fPrinterListView->ActivePrinter())
                                break;
                        BMessenger msgr;
                        if (printer && GetPrinterServerMessenger(msgr) == B_OK) {
                                BMessage setActivePrinter(B_SET_PROPERTY);
                                setActivePrinter.AddSpecifier("ActivePrinter");
                                setActivePrinter.AddString("data", printer->Name());
                                msgr.SendMessage(&setActivePrinter);
                                _UpdatePrinterButtons();
                        }
                        break;
                }

                case kMsgPrintTestPage:
                {
                        fSelectedPrinter = fPrinterListView->SelectedItem();
                        if (fSelectedPrinter)
                                PrintTestPage(fSelectedPrinter);
                        break;
                }

                case kMsgCancelJob:
                        fJobListView->CancelJob();
                        break;

                case kMsgRestartJob:
                        fJobListView->RestartJob();
                        break;

                case kMsgJobSelected:
                        _UpdateJobButtons();
                        break;

                case B_PRINTER_CHANGED:
                {
                        // active printer could have been changed, even outside of prefs
                        BString activePrinterName(ActivePrinterName());
                        PrinterItem* item = fPrinterListView->ActivePrinter();
                        if (item && item->Name() != activePrinterName)
                                fPrinterListView->UpdateItem(item);

                        for (int32 i = 0; i < fPrinterListView->CountItems(); ++i) {
                                item = dynamic_cast<PrinterItem*>(fPrinterListView->ItemAt(i));
                                if (item && item->Name() == activePrinterName) {
                                        fPrinterListView->UpdateItem(item);
                                        fPrinterListView->SetActivePrinter(item);
                                        break;
                                }
                        }
                }       break;

                default:
                        Inherited::MessageReceived(msg);
        }
}


void
PrintersWindow::PrintTestPage(PrinterItem* printer)
{
        BPrintJob* job = new BPrintJob(B_TRANSLATE("Test page"));
        job->ConfigPage();

        // job->ConfigJob();

        BMessage* settings = job->Settings();
        if (settings == NULL) {
                delete job;
                return;
        }

        // enforce job config properties
        settings->AddInt32("copies", 1);
        settings->AddInt32("first_page", 1);
        settings->AddInt32("last_page", -1);

        BWindow* win = new TestPageWindow(job, printer);
        win->Show();
        win->PostMessage(kMsgPrintTestPage);
}


void
PrintersWindow::AddJob(SpoolFolder* folder, Job* job)
{
        if (_IsSelected(folder->Item()))
                fJobListView->AddJob(job);
        fPrinterListView->UpdateItem(folder->Item());
        _UpdatePrinterButtons();
}


void
PrintersWindow::RemoveJob(SpoolFolder* folder, Job* job)
{
        if (_IsSelected(folder->Item()))
                fJobListView->RemoveJob(job);
        fPrinterListView->UpdateItem(folder->Item());
        _UpdatePrinterButtons();
}


void
PrintersWindow::UpdateJob(SpoolFolder* folder, Job* job)
{
        if (_IsSelected(folder->Item())) {
                fJobListView->UpdateJob(job);
                _UpdateJobButtons();
        }
        fPrinterListView->UpdateItem(folder->Item());
        _UpdatePrinterButtons();
}


// #pragma mark -


void
PrintersWindow::_BuildGUI()
{
// ------------------------ Next, build the printers overview box
        BBox* printersBox = new BBox("printersBox");
        printersBox->SetFont(be_bold_font);
        printersBox->SetLabel(B_TRANSLATE("Printers"));

                // Add Button
        BButton* addButton = new BButton("add",
                B_TRANSLATE("Add" B_UTF8_ELLIPSIS), new BMessage(kMsgAddPrinter));
        addButton->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Remove button
        fRemove = new BButton("remove",
                B_TRANSLATE("Remove"), new BMessage(kMsgRemovePrinter));
        fRemove->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Make Default button
        fMakeDefault = new BButton("default",
                B_TRANSLATE("Make default"), new BMessage(kMsgMakeDefaultPrinter));
        fMakeDefault->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Print Test Page button
        fPrintTestPage = new BButton("print_test_page",
                B_TRANSLATE("Print test page"), new BMessage(kMsgPrintTestPage));
        fPrintTestPage->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Disable all selection-based buttons
        fRemove->SetEnabled(false);
        fMakeDefault->SetEnabled(false);
        fPrintTestPage->SetEnabled(false);

                // Create listview with scroller
        fPrinterListView = new PrinterListView(BRect());
        BScrollView* printerScrollView = new BScrollView("printer_scroller",
                fPrinterListView, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS,
                false, true, B_FANCY_BORDER);

        printerScrollView->SetExplicitMinSize(
                BSize(be_plain_font->Size() * 30, B_SIZE_UNSET));

        float padding = be_control_look->DefaultItemSpacing();

        BLayoutBuilder::Group<>(printersBox, B_HORIZONTAL, padding)
                .SetInsets(padding, padding * 2, padding, padding)
                .Add(printerScrollView)
                .AddGroup(B_VERTICAL, padding / 2, 0.0f)
                        .SetInsets(0)
                        .Add(addButton)
                        .Add(fRemove)
                        .Add(fMakeDefault)
                        .Add(fPrintTestPage)
                        .AddGlue();

// ------------------------ Lastly, build the jobs overview box
        fJobsBox = new BBox("jobsBox");
        fJobsBox->SetFont(be_bold_font);
        fJobsBox->SetLabel(B_TRANSLATE("Print jobs: No printer selected"));

                // Cancel Job Button
        fCancel = new BButton("cancel",
                B_TRANSLATE("Cancel job"), new BMessage(kMsgCancelJob));
        fCancel->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Restart Job button
        fRestart = new BButton("restart",
                B_TRANSLATE("Restart job"), new BMessage(kMsgRestartJob));
        fRestart->SetExplicitMaxSize(
                BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET));

                // Disable all selection-based buttons
        fCancel->SetEnabled(false);
        fRestart->SetEnabled(false);

                // Create listview with scroller
        fJobListView = new JobListView(BRect());
        BScrollView* jobScrollView = new BScrollView("jobs_scroller",
                fJobListView, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS,
                false, true, B_FANCY_BORDER);

        BLayoutBuilder::Group<>(fJobsBox, B_HORIZONTAL, padding)
                .SetInsets(padding, padding * 2, padding, padding)
                .Add(jobScrollView)
                .AddGroup(B_VERTICAL, padding / 2, 0.0f)
                        .SetInsets(0)
                        .Add(fCancel)
                        .Add(fRestart)
                        .AddGlue();

        BLayoutBuilder::Group<>(this, B_VERTICAL, 0)
                .SetInsets(B_USE_WINDOW_SPACING)
                .Add(printersBox)
                .AddStrut(B_USE_DEFAULT_SPACING)
                .Add(fJobsBox);

                // There is a better solution?
        Layout(true);
        if (fPrintTestPage->Bounds().Width() > fRestart->Bounds().Width())
                fRestart->SetExplicitMinSize(
                        BSize(fPrintTestPage->Bounds().Width(), B_SIZE_UNSET));
        else
                fPrintTestPage->SetExplicitMinSize(
                        BSize(fRestart->Bounds().Width(), B_SIZE_UNSET));
}


bool
PrintersWindow::_IsSelected(PrinterItem* printer)
{
        return fSelectedPrinter && fSelectedPrinter == printer;
}


void
PrintersWindow::_UpdatePrinterButtons()
{
        PrinterItem* item = fPrinterListView->SelectedItem();
        fRemove->SetEnabled(item && !item->HasPendingJobs());
        fMakeDefault->SetEnabled(item && !item->IsActivePrinter());
        fPrintTestPage->SetEnabled(item);
}


void
PrintersWindow::_UpdateJobButtons()
{
        JobItem* item = fJobListView->SelectedItem();
        if (item != NULL) {
                Job* job = item->GetJob();
                fCancel->SetEnabled(job->Status() != kProcessing);
                fRestart->SetEnabled(job->Status() == kFailed);
        } else {
                fCancel->SetEnabled(false);
                fRestart->SetEnabled(false);
        }
}