Modules (188)

ImageViewer

Description

Dependencies

Functions

Private

_createImageView

Creates an image view object and adds it to the specified pane

file non-nullable File
the file to create an image of
pane non-nullable Pane
the pane in which to host the view
Returns: jQuery.Promise
    function _createImageView(file, pane) {
        var view = pane.getViewForPath(file.fullPath);

        if (view) {
            pane.showView(view);
        } else {
            view = new ImageView(file, pane.$content);
            pane.addView(view, true);
        }
        return new $.Deferred().resolve().promise();
    }
Private

_handleFileSystemChange

Handles file system change events so we can refresh image viewers for the files that changed on disk due to external editors

event jQuery.event
event object
file nullable File
file object that changed
added optional Array.<FileSystemEntry>
If entry is a Directory, contains zero or more added children
removed optional Array.<FileSystemEntry>
If entry is a Directory, contains zero or more removed children
    function _handleFileSystemChange(event, entry, added, removed) {
        // this may have been called because files were added
        //  or removed to the file system.  We don't care about those
        if (!entry || entry.isDirectory) {
            return;
        }

        // Look for a viewer for the changed file
        var viewer = _viewers[entry.fullPath];

        // viewer found, call its refresh method
        if (viewer) {
            viewer.refresh();
        }
    }

Classes

Constructor

ImageView

ImageView objects are constructed when an image is opened

file File
The image file object to render
container jQuery
The container to render the image view in
See
Pane for more information about where ImageViews are rendered
    function ImageView(file, $container) {
        this.file = file;
        this.$el = $(Mustache.render(ImageViewTemplate, {fullPath: file.encodedPath || 'file:///' + FileUtils.encodeFilePath(file.fullPath),
                                                         now: new Date().valueOf()}));

        $container.append(this.$el);

        this._naturalWidth = 0;
        this._naturalHeight = 0;
        this._scale = 100;           // 100%
        this._scaleDivInfo = null;   // coordinates of hidden scale sticker

        this.relPath = ProjectManager.makeProjectRelativeIfPossible(this.file.fullPath);

        this.$imagePath = this.$el.find(".image-path");
        this.$imagePreview = this.$el.find(".image-preview");
        this.$imageData = this.$el.find(".image-data");

        this.$image = this.$el.find(".image");
        this.$imageTip = this.$el.find(".image-tip");
        this.$imageGuides = this.$el.find(".image-guide");
        this.$imageScale = this.$el.find(".image-scale");
        this.$x_value = this.$el.find(".x-value");
        this.$y_value = this.$el.find(".y-value");
        this.$horzGuide = this.$el.find(".horz-guide");
        this.$vertGuide = this.$el.find(".vert-guide");

        this.$imagePath.text(this.relPath).attr("title", this.relPath);
        this.$imagePreview.on("load", _.bind(this._onImageLoaded, this));

        _viewers[file.fullPath] = this;
    }

Methods

Private

_handleMouseEnterOrExitScaleSticker

Check mouse entering/exiting the scale sticker. Hide it when entering and show it again when exiting.

offsetX number
mouse offset from the left of the previewing image
offsetY number
mouseoffset from the top of the previewing image
    ImageView.prototype._handleMouseEnterOrExitScaleSticker = function (offsetX, offsetY) {
        var imagePos       = this.$imagePreview.position(),
            scaleDivPos    = this.$imageScale.position(),
            imgWidth       = this.$imagePreview.width(),
            imgHeight      = this.$imagePreview.height(),
            scaleDivLeft,
            scaleDivTop,
            scaleDivRight,
            scaleDivBottom;

        if (this._scaleDivInfo) {
            scaleDivLeft   = this._scaleDivInfo.left;
            scaleDivTop    = this._scaleDivInfo.top;
            scaleDivRight  = this._scaleDivInfo.right;
            scaleDivBottom = this._scaleDivInfo.bottom;

            if ((imgWidth + imagePos.left) < scaleDivRight) {
                scaleDivRight = imgWidth + imagePos.left;
            }

            if ((imgHeight + imagePos.top) < scaleDivBottom) {
                scaleDivBottom = imgHeight + imagePos.top;
            }

        } else {
            scaleDivLeft   = scaleDivPos.left;
            scaleDivTop    = scaleDivPos.top;
            scaleDivRight  = this.$imageScale.width() + scaleDivLeft;
            scaleDivBottom = this.$imageScale.height() + scaleDivTop;
        }

        if (this._scaleDivInfo) {
            // See whether the cursor is no longer inside the hidden scale div.
            // If so, show it again.
            if ((offsetX < scaleDivLeft || offsetX > scaleDivRight) ||
                    (offsetY < scaleDivTop || offsetY > scaleDivBottom)) {
                this._scaleDivInfo = null;
                this.$imageScale.show();
            }
        } else if ((offsetX >= scaleDivLeft && offsetX <= scaleDivRight) &&
                (offsetY >= scaleDivTop && offsetY <= scaleDivBottom)) {
            // Handle mouse inside image scale div.
            // But hide it only if the pixel under mouse is also in the image.
            if (offsetX < (imagePos.left + imgWidth) &&
                    offsetY < (imagePos.top + imgHeight)) {
                // Remember image scale div coordinates before hiding it.
                this._scaleDivInfo = {left: scaleDivPos.left,
                                 top: scaleDivPos.top,
                                 right: scaleDivRight,
                                 bottom: scaleDivBottom};
                this.$imageScale.hide();
            }
        }
    };
Private

_hideGuidesAndTip

Hides both guides and the tip

    ImageView.prototype._hideGuidesAndTip = function () {
        this.$imageTip.hide();
        this.$imageGuides.hide();
    };
Private

_hideImageTip

Hide image coordinates info tip

e Event
event
    ImageView.prototype._hideImageTip = function (e) {
        var $target   = $(e.target),
            targetPos = $target.position(),
            imagePos  = this.$imagePreview.position(),
            right     = imagePos.left + this.$imagePreview.width(),
            bottom    = imagePos.top + this.$imagePreview.height(),
            x         = targetPos.left + e.offsetX,
            y         = targetPos.top + e.offsetY;

        // Hide image tip and guides only if the cursor is outside of the image.
        if (x < imagePos.left || x >= right ||
                y < imagePos.top || y >= bottom) {
            this._hideGuidesAndTip();
            if (this._scaleDivInfo) {
                this._scaleDivInfo = null;
                this.$imageScale.show();
            }
        }
    };
Private

_onFilenameChange

DocumentManger.fileNameChange handler - when an image is renamed, we must update the view

e jQuery.Event
event
oldPath non-nullable string
the name of the file that's changing changing
newPath non-nullable string
the name of the file that's changing changing
    ImageView.prototype._onFilenameChange = function (e, oldPath, newPath) {
Private

_onImageLoaded

<img>.on("load") handler - updates content of the image view initializes computed values installs event handlers

e Event
event
    ImageView.prototype._onImageLoaded = function (e) {
        // add dimensions and size
        this._naturalWidth = e.currentTarget.naturalWidth;
        this._naturalHeight = e.currentTarget.naturalHeight;

        var extension = FileUtils.getFileExtension(this.file.fullPath);
        var dimensionString = this._naturalWidth + " &times; " + this._naturalHeight + " " + Strings.UNIT_PIXELS;

        if (extension === "ico") {
            dimensionString += " (" + Strings.IMAGE_VIEWER_LARGEST_ICON + ")";
        }

        // get image size
        var self = this;

        this.file.stat(function (err, stat) {
            if (err) {
                self.$imageData.html(dimensionString);
            } else {
                var sizeString = "";
                if (stat.size) {
                    sizeString = " &mdash; " + StringUtils.prettyPrintBytes(stat.size, 2);
                }
                var dimensionAndSize = dimensionString + sizeString;
                self.$imageData.html(dimensionAndSize)
                        .attr("title", dimensionAndSize
                                    .replace("&times;", "x")
                                    .replace("&mdash;", "-"));
            }
        });

        // make sure we always show the right file name
        DocumentManager.on("fileNameChange.ImageView", _.bind(this._onFilenameChange, this));

        this.$imageTip.hide();
        this.$imageGuides.hide();

        this.$image.on("mousemove.ImageView", ".image-preview", _.bind(this._showImageTip, this))
                   .on("mouseleave.ImageView", ".image-preview", _.bind(this._hideImageTip, this));

        this._updateScale();
    };
Private

_showImageTip

Show image coordinates under the mouse cursor

e Event
event
    ImageView.prototype._showImageTip = function (e) {
        // Don't show image tip if this._scale is close to zero.
        // since we won't have enough room to show tip anyway.
        if (Math.floor(this._scale) === 0) {
            return;
        }

        var x                   = Math.round(e.offsetX * 100 / this._scale),
            y                   = Math.round(e.offsetY * 100 / this._scale),
            imagePos            = this.$imagePreview.position(),
            left                = e.offsetX + imagePos.left,
            top                 = e.offsetY + imagePos.top,
            width               = this.$imagePreview.width(),
            height              = this.$imagePreview.height(),
            windowWidth         = $(window).width(),
            fourDigitImageWidth = this._naturalWidth.toString().length === 4,

            // @todo -- seems a bit strange that we're computing sizes
            //          using magic numbers

            infoWidth1          = 112,    // info div width 96px + vertical toolbar width 16px
            infoWidth2          = 120,    // info div width 104px (for 4-digit image width) + vertical toolbar width 16px
            tipOffsetX          = 10,     // adjustment for info div left from x coordinate of cursor
            tipOffsetY          = -54,    // adjustment for info div top from y coordinate of cursor
            tipMinusOffsetX1    = -82,    // for less than 4-digit image width
            tipMinusOffsetX2    = -90;    // for 4-digit image width

        // Check whether we're getting mousemove events beyond the image boundaries due to a browser bug
        // or the rounding calculation above for a scaled image. For example, if an image is 120 px wide,
        // we should get mousemove events in the range of 0 <= x < 120, but not 120 or more. If we get
        // a value beyond the range, then simply handle the event as if it were a mouseleave.
        if (x < 0 || x >= this._naturalWidth || y < 0 || y >= this._naturalHeight) {
            this._hideImageTip(e);
            this.$imagePreview.css("cursor", "auto");
            return;
        }

        this.$imagePreview.css("cursor", "none");

        this._handleMouseEnterOrExitScaleSticker(left, top);

        // Check whether to show the image tip on the left.
        if ((e.pageX + infoWidth1) > windowWidth ||
                (fourDigitImageWidth && (e.pageX + infoWidth2) > windowWidth)) {
            tipOffsetX = fourDigitImageWidth ? tipMinusOffsetX2 : tipMinusOffsetX1;
        }

        this.$x_value.text(x + "px");
        this.$y_value.text(y + "px");

        this.$imageTip.css({
            left: left + tipOffsetX,
            top: top + tipOffsetY
        }).show();

        this.$horzGuide.css({
            left: imagePos.left,
            top: top,
            width: width - 1
        }).show();

        this.$vertGuide.css({
            left: left,
            top: imagePos.top,
            height: height - 1
        }).show();
    };
Private

_updateScale

Update the scale element

    ImageView.prototype._updateScale = function () {
        var currentWidth = this.$imagePreview.width();

        if (currentWidth && currentWidth < this._naturalWidth) {
            this._scale = currentWidth / this._naturalWidth * 100;
            this.$imageScale.text(Math.floor(this._scale) + "%")
                // Keep the position of the image scale div relative to the image.
                .css("left", this.$imagePreview.position().left + 5)
                .show();
        } else {
            // Reset everything related to the image scale sticker before hiding it.
            this._scale = 100;
            this._scaleDivInfo = null;
            this.$imageScale.text("").hide();
        }
    };

destroy

Destroys the view

    ImageView.prototype.destroy = function () {
        delete _viewers[this.file.fullPath];
        DocumentManager.off(".ImageView");
        this.$image.off(".ImageView");
        this.$el.remove();
    };

getFile

Retrieves the file object for this view return {!File} the file object for this view

    ImageView.prototype.getFile = function () {
        return this.file;
    };

refresh

Refreshes the image preview with what's on disk

    ImageView.prototype.refresh = function () {
        var noCacheUrl = this.$imagePreview.attr("src"),
            now = new Date().valueOf(),
            index = noCacheUrl.indexOf("?");

        // strip the old param off
        if (index > 0) {
            noCacheUrl = noCacheUrl.slice(0, index);
        }

        // add a new param which will force chrome to
        //  re-read the image from disk
        noCacheUrl = noCacheUrl + "?ver=" + now;


        // Update the DOM node with the src URL
        this.$imagePreview.attr("src", noCacheUrl);
    };

updateLayout

Updates the layout of the view

    ImageView.prototype.updateLayout = function () {
        this._hideGuidesAndTip();

        var $container = this.$el.parent();

        var pos = $container.position(),
            iWidth = $container.innerWidth(),
            iHeight = $container.innerHeight(),
            oWidth = $container.outerWidth(),
            oHeight = $container.outerHeight();

        // $view is "position:absolute" so
        //  we have to update the height, width and position
        this.$el.css({top: pos.top + ((oHeight - iHeight) / 2),
                        left: pos.left + ((oWidth - iWidth) / 2),
                        width: iWidth,
                        height: iHeight});
        this._updateScale();
    };