root/src/tools/translation/inspector/ImageView.cpp
/*****************************************************************************/
// ImageView
// Written by Michael Wilber, Haiku Translation Kit Team
//
// ImageView.cpp
//
// BView class for showing images.  Images can be dropped on this view
// from the tracker and images viewed in this view can be dragged
// to the tracker to be saved.
//
//
// Copyright (c) 2003 Haiku Project
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
/*****************************************************************************/


#include "ImageView.h"
#include "Constants.h"
#include "StatusCheck.h"
#include "InspectorApp.h"
#include "TranslatorItem.h"
#include <Application.h>
#include <Catalog.h>
#include <Message.h>
#include <Locale.h>
#include <List.h>
#include <String.h>
#include <TranslationUtils.h>
#include <TranslatorRoster.h>
#include <BitmapStream.h>
#include <Entry.h>
#include <Path.h>
#include <Directory.h>
#include <File.h>
#include <MenuBar.h>
#include <Screen.h>
#include <ScrollBar.h>
#include <Alert.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#define BORDER_WIDTH 16
#define BORDER_HEIGHT 16
#define PEN_SIZE 1.0f

#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "ImageView"


ImageView::ImageView(BRect rect, const char *name)
        : BView(rect, name, B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS)
{
        fpbitmap = NULL;
        fdocumentIndex = 1;
        fdocumentCount = 1;

        SetViewColor(192, 192, 192);
        SetHighColor(0, 0, 0);
        SetPenSize(PEN_SIZE);
}


ImageView::~ImageView()
{
        delete fpbitmap;
        fpbitmap = NULL;
}


void
ImageView::AttachedToWindow()
{
        AdjustScrollBars();
}


void
ImageView::Draw(BRect rect)
{
        if (HasImage()) {
                // Draw black rectangle around image
                StrokeRect(
                        BRect(BORDER_WIDTH - PEN_SIZE,
                                BORDER_HEIGHT - PEN_SIZE,
                                fpbitmap->Bounds().Width() + BORDER_WIDTH + PEN_SIZE,
                                fpbitmap->Bounds().Height() + BORDER_HEIGHT + PEN_SIZE));

                DrawBitmap(fpbitmap, BPoint(BORDER_WIDTH, BORDER_HEIGHT));
        }
}


void
ImageView::ReDraw()
{
        Draw(Bounds());
}


void
ImageView::FrameResized(float width, float height)
{
        AdjustScrollBars();

        if (!HasImage())
                Invalidate();
}


void
ImageView::MouseDown(BPoint point)
{
        if (!HasImage())
                return;

        // Only accept left button clicks
        BMessage *pmsg = Window()->CurrentMessage();
        int32 button = pmsg->FindInt32("buttons");
        if (button != B_PRIMARY_MOUSE_BUTTON)
                return;

        // Tell BeOS to setup a Drag/Drop operation
        //
        // (When the image is dropped, BeOS sends
        // the following message to ImageWindow,
        // which causes it to call ImageView::SetImage())
        BMessage msg(B_SIMPLE_DATA);
        msg.AddInt32("be:actions", B_COPY_TARGET);
        msg.AddString("be:filetypes", "application/octet-stream");
        msg.AddString("be:types", "application/octet-stream");
        msg.AddString("be:clip_name", "Bitmap");

        DragMessage(&msg, Bounds());
}


void
ImageView::MouseMoved(BPoint point, uint32 state, const BMessage *pmsg)
{
}


void
ImageView::MouseUp(BPoint point)
{
}


void
ImageView::MessageReceived(BMessage *pmsg)
{
        switch (pmsg->what) {
                case B_COPY_TARGET:
                        SaveImageAtDropLocation(pmsg);
                        break;

                default:
                        BView::MessageReceived(pmsg);
                        break;
        }
}


void
ImageView::SaveImageAtDropLocation(BMessage *pmsg)
{
        // Find the location and name of the drop and
        // write the image file there
        BBitmapStream stream(fpbitmap);

        StatusCheck chk;
                // throw an exception if this is assigned
                // anything other than B_OK

        try {
                entry_ref dirref;
                chk = pmsg->FindRef("directory", &dirref);
                const char *filename;
                chk = pmsg->FindString("name", &filename);

                BDirectory dir(&dirref);
                BFile file(&dir, filename, B_WRITE_ONLY | B_CREATE_FILE);
                chk = file.InitCheck();

                BTranslatorRoster *proster = BTranslatorRoster::Default();
                chk = proster->Translate(&stream, NULL, NULL, &file, B_TGA_FORMAT);

        } catch (StatusNotOKException) {
                BAlert *palert = new BAlert(NULL,
                        B_TRANSLATE("Sorry, unable to write the image file."),
                        B_TRANSLATE("OK"));
                palert->Go();
        }

        stream.DetachBitmap(&fpbitmap);
}


void
ImageView::AdjustScrollBars()
{
        BRect rctview = Bounds(), rctbitmap(0, 0, 0, 0);
        if (HasImage())
                rctbitmap = fpbitmap->Bounds();

        float prop, range;
        BScrollBar *psb = ScrollBar(B_HORIZONTAL);
        if (psb) {
                range = rctbitmap.Width() + (BORDER_WIDTH * 2) - rctview.Width();
                if (range < 0) range = 0;
                prop = rctview.Width() / (rctbitmap.Width() + (BORDER_WIDTH * 2));
                if (prop > 1.0f) prop = 1.0f;
                psb->SetRange(0, range);
                psb->SetProportion(prop);
                psb->SetSteps(10, 100);
        }

        psb = ScrollBar(B_VERTICAL);
        if (psb) {
                range = rctbitmap.Height() + (BORDER_HEIGHT * 2) - rctview.Height();
                if (range < 0) range = 0;
                prop = rctview.Height() / (rctbitmap.Height() + (BORDER_HEIGHT * 2));
                if (prop > 1.0f) prop = 1.0f;
                psb->SetRange(0, range);
                psb->SetProportion(prop);
                psb->SetSteps(10, 100);
        }
}


struct ColorSpaceName {
        color_space id;
        const char *name;
};
#define COLORSPACENAME(id) {id, #id}


// convert colorspace numerical value to
// a string value
const char *
get_color_space_name(color_space colors)
{
        // print out colorspace if it matches an item in the list
        const ColorSpaceName kcolorspaces[] = {
                COLORSPACENAME(B_NO_COLOR_SPACE),
                COLORSPACENAME(B_RGB32),
                COLORSPACENAME(B_RGBA32),
                COLORSPACENAME(B_RGB24),
                COLORSPACENAME(B_RGB16),
                COLORSPACENAME(B_RGB15),
                COLORSPACENAME(B_RGBA15),
                COLORSPACENAME(B_CMAP8),
                COLORSPACENAME(B_GRAY8),
                COLORSPACENAME(B_GRAY1),
                COLORSPACENAME(B_RGB32_BIG),
                COLORSPACENAME(B_RGBA32_BIG),
                COLORSPACENAME(B_RGB24_BIG),
                COLORSPACENAME(B_RGB16_BIG),
                COLORSPACENAME(B_RGB15_BIG),
                COLORSPACENAME(B_RGBA15_BIG),
                COLORSPACENAME(B_YCbCr422),
                COLORSPACENAME(B_YCbCr411),
                COLORSPACENAME(B_YCbCr444),
                COLORSPACENAME(B_YCbCr420),
                COLORSPACENAME(B_YUV422),
                COLORSPACENAME(B_YUV411),
                COLORSPACENAME(B_YUV444),
                COLORSPACENAME(B_YUV420),
                COLORSPACENAME(B_YUV9),
                COLORSPACENAME(B_YUV12),
                COLORSPACENAME(B_UVL24),
                COLORSPACENAME(B_UVL32),
                COLORSPACENAME(B_UVLA32),
                COLORSPACENAME(B_LAB24),
                COLORSPACENAME(B_LAB32),
                COLORSPACENAME(B_LABA32),
                COLORSPACENAME(B_HSI24),
                COLORSPACENAME(B_HSI32),
                COLORSPACENAME(B_HSIA32),
                COLORSPACENAME(B_HSV24),
                COLORSPACENAME(B_HSV32),
                COLORSPACENAME(B_HSVA32),
                COLORSPACENAME(B_HLS24),
                COLORSPACENAME(B_HLS32),
                COLORSPACENAME(B_HLSA32),
                COLORSPACENAME(B_CMY24),
                COLORSPACENAME(B_CMY32),
                COLORSPACENAME(B_CMYA32),
                COLORSPACENAME(B_CMYK32)
        };
        const int32 kncolorspaces =  sizeof(kcolorspaces) /
                sizeof(ColorSpaceName);
        for (int32 i = 0; i < kncolorspaces; i++) {
                if (colors == kcolorspaces[i].id)
                        return kcolorspaces[i].name;
        }

        return B_TRANSLATE("Unknown");
}


// return a string of the passed number formated
// as a hexadecimal number in lowercase with a leading "0x"
const char *
hex_format(uint32 num)
{
        static char str[11] = { 0 };
        sprintf(str, "0x%.8lx", num);

        return str;
}


// convert passed number to a string of 4 characters
// and return that string
const char *
char_format(uint32 num)
{
        static char str[5] = { 0 };
        uint32 bnum = B_HOST_TO_BENDIAN_INT32(num);
        memcpy(str, &bnum, 4);

        return str;
}


void
dump_translation_formats(BString &bstr, const translation_format *pfmts,
        int32 nfmts)
{
        BString *str1 = NULL;
        for (int i = 0; i < nfmts; i++) {
                BString string = B_TRANSLATE("\nType: '%1' (%2)\n"
                        "Group: '%3' (%4)\n"
                        "Quality: %5\n"
                        "Capability: %6\n"
                        "MIME Type: %7\n"
                        "Name: %8\n");
                string.ReplaceFirst("%1", char_format(pfmts[i].type));
                string.ReplaceFirst("%2", hex_format(pfmts[i].type));
                string.ReplaceFirst("%3", char_format(pfmts[i].group));
                string.ReplaceFirst("%4", hex_format(pfmts[i].group));
                char str2[127] = { 0 };
                sprintf(str2, "%f", pfmts[i].quality);
                string.ReplaceFirst("%5", str2 );
                str2[0] = '\0';
                sprintf(str2, "%f", pfmts[i].capability);
                string.ReplaceFirst("%6",  str2 );
                string.ReplaceFirst("%7", pfmts[i].MIME);
                string.ReplaceFirst("%8", pfmts[i].name);
                if (i == 0)
                        str1 = new BString(string);
                else
                        str1->Append(string);
        }
        bstr = str1->String();
}


// Send information about the currently open image to the
// BApplication object so it can send it to the InfoWindow
void
ImageView::UpdateInfoWindow(const BPath &path, BMessage &ioExtension,
        const translator_info &tinfo, BTranslatorRoster *proster)
{
        BMessage msg(M_INFO_WINDOW_TEXT);
        BString bstr;

        bstr = B_TRANSLATE("Image: %1\n"
                "Color Space: %2 (%3)\n"
                "Dimensions: %4 x %5\n"
                "Bytes per Row: %6\n"
                "Total Bytes: %7\n"
                "\nIdentify Info:\n"
                "ID String: %8\n"
                "MIME Type: %9\n"
                "Type: '%10' (%11)\n"
                "Translator ID: %12\n"
                "Group: '%13' (%14)\n"
                "Quality: %15\n"
                "Capability: %16\n"
                "\nExtension Info:\n");
        bstr.ReplaceFirst("%1", path.Path());
        color_space cs = fpbitmap->ColorSpace();
        bstr.ReplaceFirst("%2", get_color_space_name(cs));
        bstr.ReplaceFirst("%3", hex_format(static_cast<uint32>(cs)));
        char str2[127] = { 0 };
        sprintf(str2, "%ld", fpbitmap->Bounds().IntegerWidth() + 1);
        bstr.ReplaceFirst("%4", str2);
        str2[0] = '\0';
        sprintf(str2, "%ld", fpbitmap->Bounds().IntegerHeight() + 1);
        bstr.ReplaceFirst("%5", str2);
        str2[0] = '\0';
        sprintf(str2, "%ld", fpbitmap->BytesPerRow());
        bstr.ReplaceFirst("%6", str2);
        str2[0] = '\0';
        sprintf(str2, "%ld", fpbitmap->BitsLength());
        bstr.ReplaceFirst("%7", str2);
        bstr.ReplaceFirst("%8", tinfo.name);
        bstr.ReplaceFirst("%9", tinfo.MIME);
        bstr.ReplaceFirst("%10", char_format(tinfo.type));
        bstr.ReplaceFirst("%11", hex_format(tinfo.type));
        str2[0] = '\0';
        sprintf(str2, "%ld", tinfo.translator);
        bstr.ReplaceFirst("%12", str2);
        bstr.ReplaceFirst("%13", char_format(tinfo.group));
        bstr.ReplaceFirst("%14", hex_format(tinfo.group));
        str2[0] = '\0';
        sprintf(str2, "%f", tinfo.quality);
        bstr.ReplaceFirst("%15", str2);
        str2[0] = '\0';
        sprintf(str2, "%f", tinfo.capability);
        bstr.ReplaceFirst("%16", str2);

        int32 document_count = 0, document_index = 0;
        // Translator Info
        const char *tranname = NULL, *traninfo = NULL;
        int32 tranversion = 0;

        if (ioExtension.FindInt32("/documentCount", &document_count) == B_OK) {
                BString str = B_TRANSLATE("Number of Documents: %1\n"
                        "\nTranslator Used:\n"
                        "Name: %2\n"
                        "Info: %3\n"
                        "Version: %4\n");
                char str2[127] = { 0 };
                sprintf(str2, "%ld", document_count);
                str.ReplaceFirst("%1", str2);
                str.ReplaceFirst("%2", tranname);
                str.ReplaceFirst("%3", traninfo);
                str2[0] = '\0';
                sprintf(str2, "%d", (int)tranversion);
                str.ReplaceFirst("%4", str2);
                bstr.Append(str.String());
        }
        else
                if (ioExtension.FindInt32("/documentIndex", &document_index) == B_OK) {
                        BString str = B_TRANSLATE("Selected Document: %1\n"
                                "\nTranslator Used:\n"
                                "Name: %2\n"
                                "Info: %3\n"
                                "Version: %4\n");
                        char str2[127] = { 0 };
                        sprintf(str2, "%ld", document_index);
                        str.ReplaceFirst("%1", str2);
                        str.ReplaceFirst("%2", tranname);
                        str.ReplaceFirst("%3", traninfo);
                        str2[0] = '\0';
                        sprintf(str2, "%d", (int)tranversion);
                        str.ReplaceFirst("%4", str2);
                        bstr.Append(str.String());
                }
                else
                        if (proster->GetTranslatorInfo(tinfo.translator, &tranname,
                                &traninfo, &tranversion) == B_OK) {
                                        BString str = B_TRANSLATE("\nTranslator Used:\n"
                                                "Name: %1\n"
                                                "Info: %2\n"
                                                "Version: %3\n");
                                        str.ReplaceFirst("%1", tranname);
                                        str.ReplaceFirst("%2", traninfo);
                                        char str2[127] = { 0 };
                                        sprintf(str2, "%d", (int)tranversion);
                                        str.ReplaceFirst("%3", str2);
                                        bstr.Append(str.String());
                        }

        // Translator Input / Output Formats
        int32 nins = 0, nouts = 0;
        const translation_format *pins = NULL, *pouts = NULL;
        if (proster->GetInputFormats(tinfo.translator, &pins, &nins) == B_OK) {
                bstr << B_TRANSLATE("\nInput Formats:");
                dump_translation_formats(bstr, pins, nins);
                pins = NULL;
        }
        if (proster->GetOutputFormats(tinfo.translator, &pouts, &nouts) == B_OK) {
                bstr << B_TRANSLATE("\nOutput Formats:");
                dump_translation_formats(bstr, pouts, nouts);
                pouts = NULL;
        }
        msg.AddString("text", bstr);
        be_app->PostMessage(&msg);
}


BTranslatorRoster *
ImageView::SelectTranslatorRoster(BTranslatorRoster &roster)
{
        bool bNoneSelected = true;

        InspectorApp *papp;
        papp = static_cast<InspectorApp *>(be_app);
        if (papp) {
                BList *plist = papp->GetTranslatorsList();
                if (plist) {
                        for (int32 i = 0; i < plist->CountItems(); i++) {
                                BTranslatorItem *pitem =
                                        static_cast<BTranslatorItem *>(plist->ItemAt(i));
                                if (pitem->IsSelected()) {
                                        bNoneSelected = false;
                                        roster.AddTranslators(pitem->Path());
                                }
                        }
                }
        }

        if (bNoneSelected)
                return BTranslatorRoster::Default();
        else
                return &roster;
}


void
ImageView::SetImage(BMessage *pmsg)
{
        // Replace current image with the image
        // specified in the given BMessage

        entry_ref ref;
        if (!pmsg)
                ref = fcurrentRef;
        else if (pmsg->FindRef("refs", &ref) != B_OK)
                // If refs not found, just ignore the message
                return;

        StatusCheck chk;

        try {
                BFile file(&ref, B_READ_ONLY);
                chk = file.InitCheck();

                BTranslatorRoster roster, *proster;
                proster = SelectTranslatorRoster(roster);
                if (!proster)
                        // throw exception
                        chk = B_ERROR;
                // determine what type the image is
                translator_info tinfo;
                BMessage ioExtension;
                if (ref != fcurrentRef)
                        // if new image, reset to first document
                        fdocumentIndex = 1;
                chk = ioExtension.AddInt32("/documentIndex", fdocumentIndex);
                chk = proster->Identify(&file, &ioExtension, &tinfo, 0, NULL,
                        B_TRANSLATOR_BITMAP);

                // perform the actual translation
                BBitmapStream outstream;
                chk = proster->Translate(&file, &tinfo, &ioExtension, &outstream,
                        B_TRANSLATOR_BITMAP);
                BBitmap *pbitmap = NULL;
                chk = outstream.DetachBitmap(&pbitmap);
                delete fpbitmap;
                fpbitmap = pbitmap;
                pbitmap = NULL;
                fcurrentRef = ref;
                        // need to keep the ref around if user wants to switch pages
                int32 documentCount = 0;
                if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK &&
                        documentCount > 0)
                        fdocumentCount = documentCount;
                else
                        fdocumentCount = 1;

                // Set the name of the Window to reflect the file name
                BWindow *pwin = Window();
                BEntry entry(&ref);
                BPath path;
                if (entry.InitCheck() == B_OK) {
                        if (path.SetTo(&entry) == B_OK)
                                pwin->SetTitle(path.Leaf());
                        else
                                pwin->SetTitle(IMAGEWINDOW_TITLE);
                } else
                        pwin->SetTitle(IMAGEWINDOW_TITLE);
                UpdateInfoWindow(path, ioExtension, tinfo, proster);

                // Resize parent window and set size limits to
                // reflect the size of the new bitmap
                float width, height;
                BMenuBar *pbar = pwin->KeyMenuBar();
                width = fpbitmap->Bounds().Width() + B_V_SCROLL_BAR_WIDTH + (BORDER_WIDTH * 2);
                height = fpbitmap->Bounds().Height() +
                        pbar->Bounds().Height() + B_H_SCROLL_BAR_HEIGHT + (BORDER_HEIGHT * 2) + 1;
                BScreen *pscreen = new BScreen(pwin);
                BRect rctscreen = pscreen->Frame();
                if (width > rctscreen.Width())
                        width = rctscreen.Width();
                if (height > rctscreen.Height())
                        height = rctscreen.Height();
                pwin->SetSizeLimits(B_V_SCROLL_BAR_WIDTH * 4, width,
                        pbar->Bounds().Height() + (B_H_SCROLL_BAR_HEIGHT * 4) + 1, height);
                pwin->SetZoomLimits(width, height);
                AdjustScrollBars();

                //pwin->Zoom();
                        // Perform all of the hard work of resizing the
                        // window while taking into account the size of
                        // the screen, the tab and borders of the window
                        //
                        // HACK: Need to fix case where window un-zooms
                        // when the window is already the correct size
                        // for the current image

                // repaint view
                Invalidate();

        } catch (StatusNotOKException) {
                BAlert *palert = new BAlert(NULL,
                        B_TRANSLATE("Sorry, unable to load the image."),
                        B_TRANSLATE("OK"));
                palert->Go();
        }
}


void
ImageView::FirstPage()
{
        if (fdocumentIndex != 1) {
                fdocumentIndex = 1;
                SetImage(NULL);
        }
}


void
ImageView::LastPage()
{
        if (fdocumentIndex != fdocumentCount) {
                fdocumentIndex = fdocumentCount;
                SetImage(NULL);
        }
}


void
ImageView::NextPage()
{
        if (fdocumentIndex < fdocumentCount) {
                fdocumentIndex++;
                SetImage(NULL);
        }
}


void
ImageView::PrevPage()
{
        if (fdocumentIndex > 1) {
                fdocumentIndex--;
                SetImage(NULL);
        }
}