1
0

Renamed modded tagfaces module to photoannotation to avoid confusion

Started working on feature to delete annotations directly
Added warning when tagfaces and photoannotation are activated at the same time
This commit is contained in:
hukoeth 2010-08-26 21:46:20 +02:00 committed by Bharat Mediratta
parent d6dc5f6b7a
commit 90ad68fcc6
17 changed files with 2891 additions and 0 deletions

View File

@ -0,0 +1,364 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class photoannotation_Controller extends Controller {
public function save($item_data) {
// Prevent Cross Site Request Forgery
access::verify_csrf();
//Get form data
$id = $_POST["id"]; //Not yet needed since we are only creating new tagfaces will be needed when editing of existing ones is implemented
$str_y1 = $_POST["top"];
$str_x1 = $_POST["left"];
$str_y2 = $_POST["height"] + $str_y1; //Annotation uses area size, tagfaces uses positions
$str_x2 = $_POST["width"] + $str_x1; //Annotation uses area size, tagfaces uses positions
$str_face_title = $_POST["text"];
$tag_data = $_POST["tagsList"];
$str_face_description = $_POST["desc"];
$redir_uri = $_POST["currenturl"];
// Decide if we are saving a face or a note.
if ($tag_data == -1) {
if ($str_face_title == "") {
message::error(t("Please select a Tag or specify a Title."));
url::redirect($redir_uri);
return;
}
//Save note
$newnote = ORM::factory("items_note");
$newnote->item_id = $item_data;
$newnote->x1 = $str_x1;
$newnote->y1 = $str_y1;
$newnote->x2 = $str_x2;
$newnote->y2 = $str_y2;
$newnote->title = $str_face_title;
$newnote->description = $str_face_description;
$newnote->save();
} else {
// Check to see if the tag already has a face associated with it.
$existingFace = ORM::factory("items_face")
->where("tag_id", "=", $tag_data)
->where("item_id", "=", $item_data)
->find_all();
if (count($existingFace) == 0) {
// Save the new face to the database.
$newface = ORM::factory("items_face");
$newface->tag_id = $tag_data;
$newface->item_id = $item_data;
$newface->x1 = $str_x1;
$newface->y1 = $str_y1;
$newface->x2 = $str_x2;
$newface->y2 = $str_y2;
$newface->description = $str_face_description;
$newface->save();
} else {
// Update the coordinates of an existing face.
$updatedFace = ORM::factory("items_face", $existingFace[0]->id);
$updatedFace->x1 = $str_x1;
$updatedFace->y1 = $str_y1;
$updatedFace->x2 = $str_x2;
$updatedFace->y2 = $str_y2;
$updatedFace->description = $str_face_description;
$updatedFace->save();
}
}
message::success(t("Face saved."));
url::redirect($redir_uri);
return;
}
public function drawfaces($id) {
// Generate the page that allows the user to draw boxes over a photo.
// Make sure user has access to view and edit the photo.
$item = ORM::factory("item", $id);
access::required("view", $item);
access::required("edit", $item);
// Create the page.
$template = new Theme_View("page.html", "other", "photoannotation");
$template->set_global("item_id", $id);
$template->set_global("page_title", t("Draw Faces"));
$template->set_global("page_type", "other");
$template->set_global("page_subtype", "photoface");
$template->content = new View("photoannotation.html");
$template->content->title = t("Tag Faces");
$template->content->form = $this->_get_faces_form($id);
$template->content->delete_form = $this->_get_delfaces_form($id);
// Display the page.
print $template;
}
public function delface() {
// Delete the specified face data from the photo.
// Prevent Cross Site Request Forgery
access::verify_csrf();
// Convert submitted data to local variables.
// Figure out which tagged faces and notes to delete.
$tag_data = Input::instance()->post("facesList");
$note_data = Input::instance()->post("notesList");
// Figure out the item id, in order to reload the correct face tagging page.
$item_data = Input::instance()->post("item_id");
// If the user didn't select a tag or note, display and error and abort.
if ((count($tag_data) == 0) && (count($note_data) == 0)) {
message::error(t("Please select a tag or note to delete."));
url::redirect("photoannotation/drawfaces/$item_data");
return;
}
// Delete the face(s) from the database.
foreach ($tag_data as $one_tag) {
db::build()->delete("items_faces")->where("id", "=", $one_tag)->execute();
}
// Delete the notes(s) from the database.
foreach ($note_data as $one_note) {
db::build()->delete("items_notes")->where("id", "=", $one_note)->execute();
}
// Display a success message for deleted faces.
if (count($tag_data) == 1) {
message::success(t("One face deleted."));
} elseif (count($tag_data) > 1) {
message::success(count($tag_data) . t(" faces deleted."));
}
// Display a success message for deleted notes.
if (count($note_data) == 1) {
message::success(t("One note deleted."));
} elseif (count($note_data) > 1) {
message::success(count($note_data) . t(" notes deleted."));
}
// Re-load the face tagging page.
url::redirect("photoannotation/drawfaces/$item_data");
}
public function saveface() {
// Save the face coordinates to the specified tag.
// Prevent Cross Site Request Forgery
access::verify_csrf();
// Convert submitted data to local variables.
$tag_data = Input::instance()->post("tagsList");
$str_face_title = str_replace("'", "\'", Input::instance()->post("face_title"));
$str_face_description = str_replace("'", "\'", Input::instance()->post("face_description"));
$item_data = Input::instance()->post("item_id");
$str_x1 = Input::instance()->post("x1");
$str_y1 = Input::instance()->post("y1");
$str_x2 = Input::instance()->post("x2");
$str_y2 = Input::instance()->post("y2");
// If the user didn't select a face, display an error and abort.
if (($str_x1 == "") || ($str_x2 == "") || ($str_y1 == "") || ($str_y2 == "")) {
message::error(t("Please select a face."));
url::redirect("photoannotation/drawfaces/$item_data");
return;
}
// Decide if we are saving a face or a note.
if ($tag_data == -1) {
// Make sure there's a title.
if ($str_face_title == "") {
message::error(t("Please select a Tag or specify a Title."));
url::redirect("photoannotation/drawfaces/$item_data");
return;
}
// Save a new Note to the database.
$newnote = ORM::factory("items_note");
$newnote->item_id = $item_data;
$newnote->x1 = $str_x1;
$newnote->y1 = $str_y1;
$newnote->x2 = $str_x2;
$newnote->y2 = $str_y2;
$newnote->title = $str_face_title;
$newnote->description = $str_face_description;
$newnote->save();
} else {
// Check to see if the tag already has a face associated with it.
$existingFace = ORM::factory("items_face")
->where("tag_id", "=", $tag_data)
->where("item_id", "=", $item_data)
->find_all();
if (count($existingFace) == 0) {
// Save the new face to the database.
$newface = ORM::factory("items_face");
$newface->tag_id = $tag_data;
$newface->item_id = $item_data;
$newface->x1 = $str_x1;
$newface->y1 = $str_y1;
$newface->x2 = $str_x2;
$newface->y2 = $str_y2;
$newface->description = $str_face_description;
$newface->save();
} else {
// Update the coordinates of an existing face.
$updatedFace = ORM::factory("items_face", $existingFace[0]->id);
$updatedFace->x1 = $str_x1;
$updatedFace->y1 = $str_y1;
$updatedFace->x2 = $str_x2;
$updatedFace->y2 = $str_y2;
$updatedFace->description = $str_face_description;
$updatedFace->save();
}
}
// Redirect back to the main screen and display a "success" message.
message::success(t("Annotation saved."));
url::redirect("photoannotation/drawfaces/$item_data");
}
private function _get_faces_form($id) {
// Generate the form that allows the user to select a tag to
// save the face too. Also displays the coordinates of the face
// and the "Save face" button.
// Make a new Form.
$form = new Forge("photoannotation/saveface", "", "post",
array("id" => "g-tag-faces-form"));
// Create an array of all the tags for the current item.
$all_tags = ORM::factory("tag")
->join("items_tags", "tags.id", "items_tags.tag_id")
->where("items_tags.item_id", "=", $id)
->find_all();
// Generate an array of tags to use as checkboxes.
$array_tags = "";
$array_tags[-1] = t("No Tag");
foreach ($all_tags as $oneTag) {
$array_tags[$oneTag->id] = $oneTag->name;
}
// Make a checklist of tags on the form.
$tags_group = $form->group("FaceTag")
->label(t("Select a tag or enter in a title:"));
$tags_group->dropdown('tagsList')
->label(t("Tag:"))
->id('tagsList')
->options($array_tags);
$tags_group->input("face_title")
->id('face_title')
->label(t("Note Title:"));
$tags_description = $form->group("TagsDescription")
->label(t("Description (optional):"));
$tags_description->input("face_description")
->id('face_description');
// Generate input boxes to hold the coordinates of the face.
$coordinates_group = $form->group("FaceCoordinates")
->label(t("Coordinates:"));
$coordinates_group->input('x1')
->id('x1')
->label(t("X1"));
$coordinates_group->input("y1")
->id('y1')
->label(t("Y1"));
$coordinates_group->input("x2")
->id('x2')
->label(t("X2"));
$coordinates_group->input("y2")
->id('y2')
->label(t("Y2"));
// Add the id# of the photo and a save button to the form.
$coordinates_group->hidden("item_id")->value($id);
$form->submit("SaveFace")->value(t("Save face"));
// Return the newly generated form.
return $form;
}
private function _get_delfaces_form($id) {
// Generate a form to allow the user to remove face data
// from a photo.
// Make a new Form.
$form = new Forge("photoannotation/delface", "", "post",
array("id" => "g-tag-del-faces-form"));
// Create an array of all the tags that already have faces.
$existing_faces = ORM::factory("items_face")
->where("item_id", "=", $id)
->find_all();
// turn the $existing_faces array into an array that can be used
// for a checklist.
$array_faces = "";
foreach ($existing_faces as $oneFace) {
$array_faces[$oneFace->id] = array(ORM::factory("tag",
$oneFace->tag_id)->name, false);
}
if ($array_faces) {
// Add a checklist to the form.
$tags_group = $form->group("ExistingFaces")
->label(t("Tags with faces:"));
// Add the id# of the photo and a delete button to the form.
$tags_group->hidden("item_id")->value($id);
$tags_group->checklist("facesList")
->options($array_faces)
->label(t("Select the tag(s) that correspond(s) to the face(s) you wish to delete:"));
}
// Create an array of all the notes associated with this photo.
$existing_notes = ORM::factory("items_note")
->where("item_id", "=", $id)
->find_all();
// turn the $existing_notes array into an array that can be used
// for a checklist.
$array_notes = "";
foreach ($existing_notes as $oneNote) {
$array_notes[$oneNote->id] = array($oneNote->title, false);
}
if ($array_notes) {
// Add a checklist to the form.
$notes_group = $form->group("ExistingNotes")
->label(t("Notes:"));
// Add the id# of the photo and a delete button to the form.
$notes_group->hidden("item_id")->value($id);
$notes_group->checklist("notesList")
->options($array_notes)
->label(t("Select the notes you wish to delete:"));
}
// Hide the delete button when there's nothing to delete.
if (($array_notes) || ($array_faces)) {
$form->submit("DeleteFace")->value(t("Delete face(s) / note(s)"));
} else {
$form->group("NoFacesNotes")->label(t("There is nothing to delete for this photo."));
}
// Return the newly generated form.
return $form;
}
}

View File

@ -0,0 +1,200 @@
.image-annotate-add {
background: #fff url(../images/asterisk_yellow.png) no-repeat 3px 3px;
color: #000 !important;
cursor: pointer;
display: block;
float: left;
font-family: Verdana, Sans-Serif;
font-size: 12px;
height: 18px;
line-height: 18px;
padding: 2px 0 2px 24px;
margin: 5px 0;
width: 64px;
text-decoration: none;
}
.image-annotate-add:hover {
background-color: #eee;
}
.image-annotate-canvas {
background-position: left top;
background-repeat: no-repeat;
display: block;
margin: 0 auto;
position: relative;
}
.image-annotate-view {
display: none;
position: relative;
}
.image-annotate-area {
border: 1px solid #000000;
position: absolute;
cursor: default;
}
.image-annotate-area div {
border: 1px solid #FFFFFF;
display: block;
}
.image-annotate-area-editable {
cursor: pointer;
}
.image-annotate-area-editable-hover div {
border-color: #00AD00 !important;
}
.image-annotate-note {
background: #000000 none repeat scroll 0 0;
color: #FFFFFF;
display: none;
font-family: Verdana, Sans-Serif;
font-size: 1.4em;
max-width: 200px;
padding: 3px 7px;
position: absolute;
}
.image-annotate-note .actions {
display: block;
font-size: 80%;
}
.image-annotate-edit {
display: none;
}
#image-annotate-edit-form {
background: #FFFFFF none repeat scroll 0 0;
border: 1px solid #000000;
height: 220px;
padding: 7px;
position: absolute;
width: 250px;
}
#image-annotate-edit-form form {
clear: right;
margin: 0 !important;
padding: 0;
z-index: 999;
text-align: left;
color: #000000;
}
#image-annotate-edit-form .box {
margin: 0;
}
#image-annotate-edit-form input.form-text, #image-annotate-edit-form #edit-comment-wrapper textarea {
width: 90%;
}
#image-annotate-edit-form textarea {
height: 50px;
font-family: Verdana, Sans-Serif;
font-size: 12px;
width: 248px;
}
#image-annotate-edit-form fieldset {
background: transparent none repeat scroll 0 0;
}
#image-annotate-edit-form .form-item {
margin: 0 0 5px;
}
#image-annotate-edit-form .form-button, #image-annotate-edit-form .form-submit {
margin: 0;
}
#image-annotate-edit-form a {
cursor: pointer;
display: block;
float: left;
margin: 3px 6px 3px 0;
}
.image-annotate-edit-area {
border: 1px solid black;
cursor: move;
display: block;
height: 60px;
left: 10px;
margin: 0;
padding: 0;
position: absolute;
top: 10px;
width: 60px;
}
.image-annotate-edit-area .ui-resizable-handle {
opacity: 0.8;
}
.image-annotate-edit-ok {
/*background-image: url(../images/accept.png);*/
}
.image-annotate-edit-delete {
background-image: url(../images/delete.png);
}
.image-annotate-edit-close {
/*background-image: url(../images/cross.png);*/
}
.ui-resizable {
position: relative;
}
.ui-resizable-handle {
position: absolute;
font-size: 0.1px;
z-index: 99999;
display: block;
}
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable- autohide .ui-resizable-handle {
display: block;
}
.ui-resizable-n {
cursor: n-resize;
height: 7px;
width: 100%;
top: -5px;
left: 0px;
}
.ui-resizable-s {
cursor: s-resize;
height: 7px;
width: 100%;
bottom: -5px;
left: 0px;
}
.ui-resizable-e {
cursor: e-resize;
width: 7px;
right: -5px;
top: 0px;
height: 100%;
}
.ui-resizable-w {
cursor: w-resize;
width: 7px;
left: -5px;
top: 0px;
height: 100%;
}
.ui-resizable-se {
cursor: se-resize;
width: 12px;
height: 12px;
right: 1px;
bottom: 1px;
}
.ui-resizable-sw {
cursor: sw-resize;
width: 9px;
height: 9px;
left: -5px;
bottom: -5px;
}
.ui-resizable-nw {
cursor: nw-resize;
width: 9px;
height: 9px;
left: -5px;
top: -5px;
}
.ui-resizable-ne {
cursor: ne-resize;
width: 9px;
height: 9px;
right: -5px;
top: -5px;
}
.photoannotation-del-button {
background-image: url('../images/delete.png');
cursor: pointer;
}

View File

@ -0,0 +1,86 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class photoannotation_event_Core {
static function module_change($changes) {
// See if the Tags module is installed,
// tell the user to install it if it isn't.
if (!module::is_active("tag") || in_array("tag", $changes->deactivate)) {
site_status::warning(
t("The Photo Annotation module requires the Tags module. " .
"<a href=\"%url\">Activate the Tags module now</a>",
array("url" => url::site("admin/modules"))),
"photoannotation_needs_tag");
} else {
site_status::clear("photoannotation_needs_tag");
}
if (module::is_active("tagfaces") || in_array("tagfaces", $changes->activate)) {
site_status::warning(
t("The Photo Annotation module cannot be used together with the TagFaces module. " .
"<a href=\"%url\">Dectivate the TagFaces module now</a>",
array("url" => url::site("admin/modules"))),
"photoannotation_incompatibility_tagfaces");
} else {
site_status::clear("photoannotation_incompatibility_tagfaces");
}
}
static function site_menu($menu, $theme) {
// Create a menu option for adding face data.
if (!$theme->item()) {
return;
}
$item = $theme->item();
if ($item->is_photo()) {
if ((access::can("view", $item)) && (access::can("edit", $item))) {
$menu->get("options_menu")
->append(Menu::factory("link")
->id("photoannotation")
->label(t("Add annotation"))
->css_id("g-photoannotation-link")
->url("#"));
$menu->get("options_menu")
->append(Menu::factory("link")
->id("photoannotation_edit")
->label(t("Edit annotations"))
->css_id("g-photoannotation-edit-link")
->url(url::site("photoannotation/drawfaces/" . $item->id)));
}
}
}
static function item_deleted($item) {
// Check for and delete existing Faces and Notes.
$existingFaces = ORM::factory("items_face")
->where("item_id", "=", $item->id)
->find_all();
if (count($existingFaces) > 0) {
db::build()->delete("items_faces")->where("item_id", "=", $item->id)->execute();
}
$existingNotes = ORM::factory("items_note")
->where("item_id", "=", $item->id)
->find_all();
if (count($existingNotes) > 0) {
db::build()->delete("items_notes")->where("item_id", "=", $item->id)->execute();
}
}
}

View File

@ -0,0 +1,86 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class photoannotation_installer {
static function install() {
// Create a table to store face coordinates in.
$db = Database::instance();
$db->query("CREATE TABLE IF NOT EXISTS {items_faces} (
`id` int(9) NOT NULL auto_increment,
`tag_id` int(9) NOT NULL,
`item_id` int(9) NOT NULL,
`x1` int(9) NOT NULL,
`y1` int(9) NOT NULL,
`x2` int(9) NOT NULL,
`y2` int(9) NOT NULL,
`description` varchar(2048) default NULL,
PRIMARY KEY (`id`))
DEFAULT CHARSET=utf8;");
$db->query("CREATE TABLE IF NOT EXISTS {items_notes} (
`id` int(9) NOT NULL auto_increment,
`item_id` int(9) NOT NULL,
`x1` int(9) NOT NULL,
`y1` int(9) NOT NULL,
`x2` int(9) NOT NULL,
`y2` int(9) NOT NULL,
`title` varchar(64) NOT NULL,
`description` varchar(2048) default NULL,
PRIMARY KEY (`id`))
DEFAULT CHARSET=utf8;");
// Set the module's version number.
module::set_version("photoannotation", 1);
}
static function upgrade($version) {
$db = Database::instance();
if ($version == 1) {
$db->query("ALTER TABLE {items_faces} ADD `description` varchar(2048) default NULL");
$db->query("CREATE TABLE IF NOT EXISTS {items_notes} (
`id` int(9) NOT NULL auto_increment,
`item_id` int(9) NOT NULL,
`x1` int(9) NOT NULL,
`y1` int(9) NOT NULL,
`x2` int(9) NOT NULL,
`y2` int(9) NOT NULL,
`title` varchar(64) NOT NULL,
`description` varchar(2048) default NULL,
PRIMARY KEY (`id`))
DEFAULT CHARSET=utf8;");
module::set_version("photoannotation", $version = 1);
}
}
static function deactivate() {
// Clear the require tags message when photoannotation is deactivated.
site_status::clear("photoannotation_needs_tag");
site_status::clear("photoannotation_incompatibility_tagfaces");
}
static function uninstall() {
// Delete the face table before uninstalling.
$db = Database::instance();
$db->query("DROP TABLE IF EXISTS {items_faces};");
$db->query("DROP TABLE IF EXISTS {items_notes};");
module::delete("photoannotation");
}
}

View File

@ -0,0 +1,32 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class photoannotation_theme_Core {
static function head($theme) {
// If it does, add an image map to the page to display them.
$theme->css("photoannotation.css");
//$theme->script("jquery.annotate.js");
Return "<script type=\"text/javascript\" src=\"/modules/photoannotation/js/jquery.annotate.js\"></script>";
}
static function photo_bottom($theme) {
// If it does, add an image map to the page to display them.
return new View("photoannotation_highlight_block.html");
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 329 B

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,163 @@
/**
* Jcrop v.0.9.8 (minimized)
* (c) 2008 Kelly Hallman and DeepLiquid.com
* More information: http://deepliquid.com/content/Jcrop.html
* Released under MIT License - this header must remain with code
*/
(function($){$.Jcrop=function(obj,opt)
{var obj=obj,opt=opt;if(typeof(obj)!=='object')obj=$(obj)[0];if(typeof(opt)!=='object')opt={};if(!('trackDocument'in opt))
{opt.trackDocument=$.browser.msie?false:true;if($.browser.msie&&$.browser.version.split('.')[0]=='8')
opt.trackDocument=true;}
if(!('keySupport'in opt))
opt.keySupport=$.browser.msie?false:true;var defaults={trackDocument:false,baseClass:'jcrop',addClass:null,bgColor:'black',bgOpacity:.6,borderOpacity:.4,handleOpacity:.5,handlePad:5,handleSize:9,handleOffset:5,edgeMargin:14,aspectRatio:0,keySupport:true,cornerHandles:true,sideHandles:true,drawBorders:true,dragEdges:true,boxWidth:0,boxHeight:0,boundary:8,animationDelay:20,swingSpeed:3,allowSelect:true,allowMove:true,allowResize:true,minSelect:[0,0],maxSize:[0,0],minSize:[0,0],onChange:function(){},onSelect:function(){}};var options=defaults;setOptions(opt);var $origimg=$(obj);var $img=$origimg.clone().removeAttr('id').css({position:'absolute'});$img.width($origimg.width());$img.height($origimg.height());$origimg.after($img).hide();presize($img,options.boxWidth,options.boxHeight);var boundx=$img.width(),boundy=$img.height(),$div=$('<div />').width(boundx).height(boundy).addClass(cssClass('holder')).css({position:'relative',backgroundColor:options.bgColor}).insertAfter($origimg).append($img);;if(options.addClass)$div.addClass(options.addClass);var $img2=$('<img />').attr('src',$img.attr('src')).css('position','absolute').width(boundx).height(boundy);var $img_holder=$('<div />').width(pct(100)).height(pct(100)).css({zIndex:310,position:'absolute',overflow:'hidden'}).append($img2);var $hdl_holder=$('<div />').width(pct(100)).height(pct(100)).css('zIndex',320);var $sel=$('<div />').css({position:'absolute',zIndex:300}).insertBefore($img).append($img_holder,$hdl_holder);var bound=options.boundary;var $trk=newTracker().width(boundx+(bound*2)).height(boundy+(bound*2)).css({position:'absolute',top:px(-bound),left:px(-bound),zIndex:290}).mousedown(newSelection);var xlimit,ylimit,xmin,ymin;var xscale,yscale,enabled=true;var docOffset=getPos($img),btndown,lastcurs,dimmed,animating,shift_down;var Coords=function()
{var x1=0,y1=0,x2=0,y2=0,ox,oy;function setPressed(pos)
{var pos=rebound(pos);x2=x1=pos[0];y2=y1=pos[1];};function setCurrent(pos)
{var pos=rebound(pos);ox=pos[0]-x2;oy=pos[1]-y2;x2=pos[0];y2=pos[1];};function getOffset()
{return[ox,oy];};function moveOffset(offset)
{var ox=offset[0],oy=offset[1];if(0>x1+ox)ox-=ox+x1;if(0>y1+oy)oy-=oy+y1;if(boundy<y2+oy)oy+=boundy-(y2+oy);if(boundx<x2+ox)ox+=boundx-(x2+ox);x1+=ox;x2+=ox;y1+=oy;y2+=oy;};function getCorner(ord)
{var c=getFixed();switch(ord)
{case'ne':return[c.x2,c.y];case'nw':return[c.x,c.y];case'se':return[c.x2,c.y2];case'sw':return[c.x,c.y2];}};function getFixed()
{if(!options.aspectRatio)return getRect();var aspect=options.aspectRatio,min_x=options.minSize[0]/xscale,min_y=options.minSize[1]/yscale,max_x=options.maxSize[0]/xscale,max_y=options.maxSize[1]/yscale,rw=x2-x1,rh=y2-y1,rwa=Math.abs(rw),rha=Math.abs(rh),real_ratio=rwa/rha,xx,yy;if(max_x==0){max_x=boundx*10}
if(max_y==0){max_y=boundy*10}
if(real_ratio<aspect)
{yy=y2;w=rha*aspect;xx=rw<0?x1-w:w+x1;if(xx<0)
{xx=0;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}
else if(xx>boundx)
{xx=boundx;h=Math.abs((xx-x1)/aspect);yy=rh<0?y1-h:h+y1;}}
else
{xx=x2;h=rwa/aspect;yy=rh<0?y1-h:y1+h;if(yy<0)
{yy=0;w=Math.abs((yy-y1)*aspect);xx=rw<0?x1-w:w+x1;}
else if(yy>boundy)
{yy=boundy;w=Math.abs(yy-y1)*aspect;xx=rw<0?x1-w:w+x1;}}
if(xx>x1){if(xx-x1<min_x){xx=x1+min_x;}else if(xx-x1>max_x){xx=x1+max_x;}
if(yy>y1){yy=y1+(xx-x1)/aspect;}else{yy=y1-(xx-x1)/aspect;}}else if(xx<x1){if(x1-xx<min_x){xx=x1-min_x}else if(x1-xx>max_x){xx=x1-max_x;}
if(yy>y1){yy=y1+(x1-xx)/aspect;}else{yy=y1-(x1-xx)/aspect;}}
if(xx<0){x1-=xx;xx=0;}else if(xx>boundx){x1-=xx-boundx;xx=boundx;}
if(yy<0){y1-=yy;yy=0;}else if(yy>boundy){y1-=yy-boundy;yy=boundy;}
return last=makeObj(flipCoords(x1,y1,xx,yy));};function rebound(p)
{if(p[0]<0)p[0]=0;if(p[1]<0)p[1]=0;if(p[0]>boundx)p[0]=boundx;if(p[1]>boundy)p[1]=boundy;return[p[0],p[1]];};function flipCoords(x1,y1,x2,y2)
{var xa=x1,xb=x2,ya=y1,yb=y2;if(x2<x1)
{xa=x2;xb=x1;}
if(y2<y1)
{ya=y2;yb=y1;}
return[Math.round(xa),Math.round(ya),Math.round(xb),Math.round(yb)];};function getRect()
{var xsize=x2-x1;var ysize=y2-y1;if(xlimit&&(Math.abs(xsize)>xlimit))
x2=(xsize>0)?(x1+xlimit):(x1-xlimit);if(ylimit&&(Math.abs(ysize)>ylimit))
y2=(ysize>0)?(y1+ylimit):(y1-ylimit);if(ymin&&(Math.abs(ysize)<ymin))
y2=(ysize>0)?(y1+ymin):(y1-ymin);if(xmin&&(Math.abs(xsize)<xmin))
x2=(xsize>0)?(x1+xmin):(x1-xmin);if(x1<0){x2-=x1;x1-=x1;}
if(y1<0){y2-=y1;y1-=y1;}
if(x2<0){x1-=x2;x2-=x2;}
if(y2<0){y1-=y2;y2-=y2;}
if(x2>boundx){var delta=x2-boundx;x1-=delta;x2-=delta;}
if(y2>boundy){var delta=y2-boundy;y1-=delta;y2-=delta;}
if(x1>boundx){var delta=x1-boundy;y2-=delta;y1-=delta;}
if(y1>boundy){var delta=y1-boundy;y2-=delta;y1-=delta;}
return makeObj(flipCoords(x1,y1,x2,y2));};function makeObj(a)
{return{x:a[0],y:a[1],x2:a[2],y2:a[3],w:a[2]-a[0],h:a[3]-a[1]};};return{flipCoords:flipCoords,setPressed:setPressed,setCurrent:setCurrent,getOffset:getOffset,moveOffset:moveOffset,getCorner:getCorner,getFixed:getFixed};}();var Selection=function()
{var start,end,dragmode,awake,hdep=370;var borders={};var handle={};var seehandles=false;var hhs=options.handleOffset;if(options.drawBorders){borders={top:insertBorder('hline').css('top',$.browser.msie?px(-1):px(0)),bottom:insertBorder('hline'),left:insertBorder('vline'),right:insertBorder('vline')};}
if(options.dragEdges){handle.t=insertDragbar('n');handle.b=insertDragbar('s');handle.r=insertDragbar('e');handle.l=insertDragbar('w');}
options.sideHandles&&createHandles(['n','s','e','w']);options.cornerHandles&&createHandles(['sw','nw','ne','se']);function insertBorder(type)
{var jq=$('<div />').css({position:'absolute',opacity:options.borderOpacity}).addClass(cssClass(type));$img_holder.append(jq);return jq;};function dragDiv(ord,zi)
{var jq=$('<div />').mousedown(createDragger(ord)).css({cursor:ord+'-resize',position:'absolute',zIndex:zi});$hdl_holder.append(jq);return jq;};function insertHandle(ord)
{return dragDiv(ord,hdep++).css({top:px(-hhs+1),left:px(-hhs+1),opacity:options.handleOpacity}).addClass(cssClass('handle'));};function insertDragbar(ord)
{var s=options.handleSize,o=hhs,h=s,w=s,t=o,l=o;switch(ord)
{case'n':case's':w=pct(100);break;case'e':case'w':h=pct(100);break;}
return dragDiv(ord,hdep++).width(w).height(h).css({top:px(-t+1),left:px(-l+1)});};function createHandles(li)
{for(i in li)handle[li[i]]=insertHandle(li[i]);};function moveHandles(c)
{var midvert=Math.round((c.h/2)-hhs),midhoriz=Math.round((c.w/2)-hhs),north=west=-hhs+1,east=c.w-hhs,south=c.h-hhs,x,y;'e'in handle&&handle.e.css({top:px(midvert),left:px(east)})&&handle.w.css({top:px(midvert)})&&handle.s.css({top:px(south),left:px(midhoriz)})&&handle.n.css({left:px(midhoriz)});'ne'in handle&&handle.ne.css({left:px(east)})&&handle.se.css({top:px(south),left:px(east)})&&handle.sw.css({top:px(south)});'b'in handle&&handle.b.css({top:px(south)})&&handle.r.css({left:px(east)});};function moveto(x,y)
{$img2.css({top:px(-y),left:px(-x)});$sel.css({top:px(y),left:px(x)});};function resize(w,h)
{$sel.width(w).height(h);};function refresh()
{var c=Coords.getFixed();Coords.setPressed([c.x,c.y]);Coords.setCurrent([c.x2,c.y2]);updateVisible();};function updateVisible()
{if(awake)return update();};function update()
{var c=Coords.getFixed();resize(c.w,c.h);moveto(c.x,c.y);options.drawBorders&&borders['right'].css({left:px(c.w-1)})&&borders['bottom'].css({top:px(c.h-1)});seehandles&&moveHandles(c);awake||show();options.onChange(unscale(c));};function show()
{$sel.show();$img.css('opacity',options.bgOpacity);awake=true;};function release()
{disableHandles();$sel.hide();$img.css('opacity',1);awake=false;};function showHandles()
{if(seehandles)
{moveHandles(Coords.getFixed());$hdl_holder.show();}};function enableHandles()
{seehandles=true;if(options.allowResize)
{moveHandles(Coords.getFixed());$hdl_holder.show();return true;}};function disableHandles()
{seehandles=false;$hdl_holder.hide();};function animMode(v)
{(animating=v)?disableHandles():enableHandles();};function done()
{animMode(false);refresh();};var $track=newTracker().mousedown(createDragger('move')).css({cursor:'move',position:'absolute',zIndex:360})
$img_holder.append($track);disableHandles();return{updateVisible:updateVisible,update:update,release:release,refresh:refresh,setCursor:function(cursor){$track.css('cursor',cursor);},enableHandles:enableHandles,enableOnly:function(){seehandles=true;},showHandles:showHandles,disableHandles:disableHandles,animMode:animMode,done:done};}();var Tracker=function()
{var onMove=function(){},onDone=function(){},trackDoc=options.trackDocument;if(!trackDoc)
{$trk.mousemove(trackMove).mouseup(trackUp).mouseout(trackUp);}
function toFront()
{$trk.css({zIndex:450});if(trackDoc)
{$(document).mousemove(trackMove).mouseup(trackUp);}}
function toBack()
{$trk.css({zIndex:290});if(trackDoc)
{$(document).unbind('mousemove',trackMove).unbind('mouseup',trackUp);}}
function trackMove(e)
{onMove(mouseAbs(e));};function trackUp(e)
{e.preventDefault();e.stopPropagation();if(btndown)
{btndown=false;onDone(mouseAbs(e));options.onSelect(unscale(Coords.getFixed()));toBack();onMove=function(){};onDone=function(){};}
return false;};function activateHandlers(move,done)
{btndown=true;onMove=move;onDone=done;toFront();return false;};function setCursor(t){$trk.css('cursor',t);};$img.before($trk);return{activateHandlers:activateHandlers,setCursor:setCursor};}();var KeyManager=function()
{var $keymgr=$('<input type="radio" />').css({position:'absolute',left:'-30px'}).keypress(parseKey).blur(onBlur),$keywrap=$('<div />').css({position:'absolute',overflow:'hidden'}).append($keymgr);function watchKeys()
{if(options.keySupport)
{$keymgr.show();$keymgr.focus();}};function onBlur(e)
{$keymgr.hide();};function doNudge(e,x,y)
{if(options.allowMove){Coords.moveOffset([x,y]);Selection.updateVisible();};e.preventDefault();e.stopPropagation();};function parseKey(e)
{if(e.ctrlKey)return true;shift_down=e.shiftKey?true:false;var nudge=shift_down?10:1;switch(e.keyCode)
{case 37:doNudge(e,-nudge,0);break;case 39:doNudge(e,nudge,0);break;case 38:doNudge(e,0,-nudge);break;case 40:doNudge(e,0,nudge);break;case 27:Selection.release();break;case 9:return true;}
return nothing(e);};if(options.keySupport)$keywrap.insertBefore($img);return{watchKeys:watchKeys};}();function px(n){return''+parseInt(n)+'px';};function pct(n){return''+parseInt(n)+'%';};function cssClass(cl){return options.baseClass+'-'+cl;};function getPos(obj)
{var pos=$(obj).offset();return[pos.left,pos.top];};function mouseAbs(e)
{return[(e.pageX-docOffset[0]),(e.pageY-docOffset[1])];};function myCursor(type)
{if(type!=lastcurs)
{Tracker.setCursor(type);lastcurs=type;}};function startDragMode(mode,pos)
{docOffset=getPos($img);Tracker.setCursor(mode=='move'?mode:mode+'-resize');if(mode=='move')
return Tracker.activateHandlers(createMover(pos),doneSelect);var fc=Coords.getFixed();var opp=oppLockCorner(mode);var opc=Coords.getCorner(oppLockCorner(opp));Coords.setPressed(Coords.getCorner(opp));Coords.setCurrent(opc);Tracker.activateHandlers(dragmodeHandler(mode,fc),doneSelect);};function dragmodeHandler(mode,f)
{return function(pos){if(!options.aspectRatio)switch(mode)
{case'e':pos[1]=f.y2;break;case'w':pos[1]=f.y2;break;case'n':pos[0]=f.x2;break;case's':pos[0]=f.x2;break;}
else switch(mode)
{case'e':pos[1]=f.y+1;break;case'w':pos[1]=f.y+1;break;case'n':pos[0]=f.x+1;break;case's':pos[0]=f.x+1;break;}
Coords.setCurrent(pos);Selection.update();};};function createMover(pos)
{var lloc=pos;KeyManager.watchKeys();return function(pos)
{Coords.moveOffset([pos[0]-lloc[0],pos[1]-lloc[1]]);lloc=pos;Selection.update();};};function oppLockCorner(ord)
{switch(ord)
{case'n':return'sw';case's':return'nw';case'e':return'nw';case'w':return'ne';case'ne':return'sw';case'nw':return'se';case'se':return'nw';case'sw':return'ne';};};function createDragger(ord)
{return function(e){if(options.disabled)return false;if((ord=='move')&&!options.allowMove)return false;btndown=true;startDragMode(ord,mouseAbs(e));e.stopPropagation();e.preventDefault();return false;};};function presize($obj,w,h)
{var nw=$obj.width(),nh=$obj.height();if((nw>w)&&w>0)
{nw=w;nh=(w/$obj.width())*$obj.height();}
if((nh>h)&&h>0)
{nh=h;nw=(h/$obj.height())*$obj.width();}
xscale=$obj.width()/nw;yscale=$obj.height()/nh;$obj.width(nw).height(nh);};function unscale(c)
{return{x:parseInt(c.x*xscale),y:parseInt(c.y*yscale),x2:parseInt(c.x2*xscale),y2:parseInt(c.y2*yscale),w:parseInt(c.w*xscale),h:parseInt(c.h*yscale)};};function doneSelect(pos)
{var c=Coords.getFixed();if(c.w>options.minSelect[0]&&c.h>options.minSelect[1])
{Selection.enableHandles();Selection.done();}
else
{Selection.release();}
Tracker.setCursor(options.allowSelect?'crosshair':'default');};function newSelection(e)
{if(options.disabled)return false;if(!options.allowSelect)return false;btndown=true;docOffset=getPos($img);Selection.disableHandles();myCursor('crosshair');var pos=mouseAbs(e);Coords.setPressed(pos);Tracker.activateHandlers(selectDrag,doneSelect);KeyManager.watchKeys();Selection.update();e.stopPropagation();e.preventDefault();return false;};function selectDrag(pos)
{Coords.setCurrent(pos);Selection.update();};function newTracker()
{var trk=$('<div></div>').addClass(cssClass('tracker'));$.browser.msie&&trk.css({opacity:0,backgroundColor:'white'});return trk;};function animateTo(a)
{var x1=a[0]/xscale,y1=a[1]/yscale,x2=a[2]/xscale,y2=a[3]/yscale;if(animating)return;var animto=Coords.flipCoords(x1,y1,x2,y2);var c=Coords.getFixed();var animat=initcr=[c.x,c.y,c.x2,c.y2];var interv=options.animationDelay;var x=animat[0];var y=animat[1];var x2=animat[2];var y2=animat[3];var ix1=animto[0]-initcr[0];var iy1=animto[1]-initcr[1];var ix2=animto[2]-initcr[2];var iy2=animto[3]-initcr[3];var pcent=0;var velocity=options.swingSpeed;Selection.animMode(true);var animator=function()
{return function()
{pcent+=(100-pcent)/velocity;animat[0]=x+((pcent/100)*ix1);animat[1]=y+((pcent/100)*iy1);animat[2]=x2+((pcent/100)*ix2);animat[3]=y2+((pcent/100)*iy2);if(pcent<100)animateStart();else Selection.done();if(pcent>=99.8)pcent=100;setSelectRaw(animat);};}();function animateStart()
{window.setTimeout(animator,interv);};animateStart();};function setSelect(rect)
{setSelectRaw([rect[0]/xscale,rect[1]/yscale,rect[2]/xscale,rect[3]/yscale]);};function setSelectRaw(l)
{Coords.setPressed([l[0],l[1]]);Coords.setCurrent([l[2],l[3]]);Selection.update();};function setOptions(opt)
{if(typeof(opt)!='object')opt={};options=$.extend(options,opt);if(typeof(options.onChange)!=='function')
options.onChange=function(){};if(typeof(options.onSelect)!=='function')
options.onSelect=function(){};};function tellSelect()
{return unscale(Coords.getFixed());};function tellScaled()
{return Coords.getFixed();};function setOptionsNew(opt)
{setOptions(opt);interfaceUpdate();};function disableCrop()
{options.disabled=true;Selection.disableHandles();Selection.setCursor('default');Tracker.setCursor('default');};function enableCrop()
{options.disabled=false;interfaceUpdate();};function cancelCrop()
{Selection.done();Tracker.activateHandlers(null,null);};function destroy()
{$div.remove();$origimg.show();};function interfaceUpdate(alt)
{options.allowResize?alt?Selection.enableOnly():Selection.enableHandles():Selection.disableHandles();Tracker.setCursor(options.allowSelect?'crosshair':'default');Selection.setCursor(options.allowMove?'move':'default');$div.css('backgroundColor',options.bgColor);if('setSelect'in options){setSelect(opt.setSelect);Selection.done();delete(options.setSelect);}
if('trueSize'in options){xscale=options.trueSize[0]/boundx;yscale=options.trueSize[1]/boundy;}
xlimit=options.maxSize[0]||0;ylimit=options.maxSize[1]||0;xmin=options.minSize[0]||0;ymin=options.minSize[1]||0;if('outerImage'in options)
{$img.attr('src',options.outerImage);delete(options.outerImage);}
Selection.refresh();};$hdl_holder.hide();interfaceUpdate(true);var api={animateTo:animateTo,setSelect:setSelect,setOptions:setOptionsNew,tellSelect:tellSelect,tellScaled:tellScaled,disable:disableCrop,enable:enableCrop,cancel:cancelCrop,focus:KeyManager.watchKeys,getBounds:function(){return[boundx*xscale,boundy*yscale];},getWidgetSize:function(){return[boundx,boundy];},release:Selection.release,destroy:destroy};$origimg.data('Jcrop',api);return api;};$.fn.Jcrop=function(options)
{function attachWhenDone(from)
{var loadsrc=options.useImg||from.src;var img=new Image();img.onload=function(){$.Jcrop(from,options);};img.src=loadsrc;};if(typeof(options)!=='object')options={};this.each(function()
{if($(this).data('Jcrop'))
{if(options=='api')return $(this).data('Jcrop');else $(this).data('Jcrop').setOptions(options);}
else attachWhenDone(this);});return this;};})(jQuery);

View File

@ -0,0 +1,478 @@
/// <reference path="jquery-1.2.6-vsdoc.js" />
(function($) {
$.fn.annotateImage = function(options) {
/// <summary>
/// Creates annotations on the given image.
/// Images are loaded from the "getUrl" propety passed into the options.
/// </summary>
var opts = $.extend({}, $.fn.annotateImage.defaults, options);
var image = this;
this.image = this;
this.mode = 'view';
// Assign defaults
this.getUrl = opts.getUrl;
this.saveUrl = opts.saveUrl;
this.deleteUrl = opts.deleteUrl;
this.currentUrl = opts.currentUrl;
this.deleteUrl = opts.deleteUrl;
this.editable = opts.editable;
this.useAjax = opts.useAjax;
this.tags = opts.tags;
this.notes = opts.notes;
this.labels = opts.labels;
this.csrf = opts.csrf;
// Add the canvas
this.canvas = $('<div class="image-annotate-canvas g-thumbnail"><div class="image-annotate-view"></div><div class="image-annotate-edit"><div class="image-annotate-edit-area"></div></div></div>');
this.canvas.children('.image-annotate-edit').hide();
this.canvas.children('.image-annotate-view').hide();
this.image.after(this.canvas);
// Give the canvas and the container their size and background
this.canvas.height(this.height());
this.canvas.width(this.width());
this.canvas.css('background-image', 'url("' + this.attr('src') + '")');
this.canvas.children('.image-annotate-view, .image-annotate-edit').height(this.height());
this.canvas.children('.image-annotate-view, .image-annotate-edit').width(this.width());
// Add the behavior: hide/show the notes when hovering the picture
this.canvas.hover(function() {
if ($(this).children('.image-annotate-edit').css('display') == 'none') {
$(this).children('.image-annotate-view').show();
}
}, function() {
$(this).children('.image-annotate-view').hide();
$(this).children('.image-annotate-note').hide();
});
this.canvas.children('.image-annotate-view').hover(function() {
$(this).show();
}, function() {
$(this).hide();
$(this).children('.image-annotate-note').hide();
});
// load the notes
if (this.useAjax) {
$.fn.annotateImage.ajaxLoad(this);
} else {
$.fn.annotateImage.load(this, this.labels, this.editable, this.csrf, this.deleteUrl);
}
// Add the "Add a note" button
if ($('#g-photoannotation-link').length != 0) {
this.button = $('#g-photoannotation-link');
this.button.click(function() {
$.fn.annotateImage.add(image, opts.tags, opts.labels, opts.saveUrl, opts.currentUrl, opts.csrf);
});
//this.canvas.after(this.button);
}
// Hide the original
this.hide();
return this;
};
/**
* Plugin Defaults
**/
$.fn.annotateImage.defaults = {
getUrl: 'your-get.rails',
saveUrl: 'your-save.rails',
deleteUrl: 'your-delete.rails',
editable: true,
useAjax: true,
tags: new Array(),
notes: new Array()
};
$.fn.annotateImage.clear = function(image) {
/// <summary>
/// Clears all existing annotations from the image.
/// </summary>
for (var i = 0; i < image.notes.length; i++) {
image.notes[image.notes[i]].destroy();
}
image.notes = new Array();
};
$.fn.annotateImage.ajaxLoad = function(image) {
/// <summary>
/// Loads the annotations from the "getUrl" property passed in on the
/// options object.
/// </summary>
$.getJSON(image.getUrl + '?ticks=' + $.fn.annotateImage.getTicks(), function(data) {
image.notes = data;
$.fn.annotateImage.load(image);
});
};
$.fn.annotateImage.load = function(image, labels, editable, csrf, deleteUrl) {
/// <summary>
/// Loads the annotations from the notes property passed in on the
/// options object.
/// </summary>
for (var i = 0; i < image.notes.length; i++) {
image.notes[image.notes[i]] = new $.fn.annotateView(image, image.notes[i], labels, editable, csrf, deleteUrl);
}
};
$.fn.annotateImage.getTicks = function() {
/// <summary>
/// Gets a count og the ticks for the current date.
/// This is used to ensure that URLs are always unique and not cached by the browser.
/// </summary>
var now = new Date();
return now.getTime();
};
$.fn.annotateImage.add = function(image, tags, labels, saveUrl, currentUrl, csrf) {
/// <summary>
/// Adds a note to the image.
/// </summary>
if (image.mode == 'view') {
image.mode = 'edit';
// Create/prepare the editable note elements
var editable = new $.fn.annotateEdit(image, null, tags, labels, saveUrl, currentUrl, csrf);
$.fn.annotateImage.createSaveButton(editable, image);
$.fn.annotateImage.createCancelButton(editable, image);
}
};
$.fn.annotateImage.createSaveButton = function(editable, image, note) {
/// <summary>
/// Creates a Save button on the editable note.
/// </summary>
var ok = $('<a class="image-annotate-edit-ok g-button ui-corner-all ui-icon-left ui-state-default">OK</a>');
ok.click(function() {
var form = $('#image-annotate-edit-form form');
var text = $('#image-annotate-text').val();
$.fn.annotateImage.appendPosition(form, editable)
image.mode = 'view';
form.submit();
editable.destroy();
});
editable.form.append(ok);
};
$.fn.annotateImage.createCancelButton = function(editable, image) {
/// <summary>
/// Creates a Cancel button on the editable note.
/// </summary>
var cancel = $('<a class="image-annotate-edit-close g-button ui-corner-all ui-icon-left ui-state-default">Cancel</a>');
cancel.click(function() {
editable.destroy();
image.mode = 'view';
});
editable.form.append(cancel);
};
$.fn.annotateImage.saveAsHtml = function(image, target) {
var element = $(target);
var html = "";
for (var i = 0; i < image.notes.length; i++) {
html += $.fn.annotateImage.createHiddenField("text_" + i, image.notes[i].text);
html += $.fn.annotateImage.createHiddenField("top_" + i, image.notes[i].top);
html += $.fn.annotateImage.createHiddenField("left_" + i, image.notes[i].left);
html += $.fn.annotateImage.createHiddenField("height_" + i, image.notes[i].height);
html += $.fn.annotateImage.createHiddenField("width_" + i, image.notes[i].width);
}
element.html(html);
};
$.fn.annotateImage.createHiddenField = function(name, value) {
return '&lt;input type="hidden" name="' + name + '" value="' + value + '" /&gt;<br />';
};
$.fn.annotateEdit = function(image, note, tags, labels, saveUrl, currentUrl, csrf) {
/// <summary>
/// Defines an editable annotation area.
/// </summary>
this.image = image;
if (note) {
this.note = note;
} else {
var newNote = new Object();
newNote.id = "new";
newNote.top = 30;
newNote.left = 30;
newNote.width = 30;
newNote.height = 30;
newNote.text = "";
this.note = newNote;
}
// Set area
var area = image.canvas.children('.image-annotate-edit').children('.image-annotate-edit-area');
this.area = area;
this.area.css('height', this.note.height + 'px');
this.area.css('width', this.note.width + 'px');
this.area.css('left', this.note.left + 'px');
this.area.css('top', this.note.top + 'px');
// Show the edition canvas and hide the view canvas
image.canvas.children('.image-annotate-view').hide();
image.canvas.children('.image-annotate-edit').show();
// Add the note (which we'll load with the form afterwards)
var tagdropdown = labels[0] + '<select id="tagsList" class="dropdown" name="tagsList"><option value="-1" selected="selected">No Tag</option>';
if (tags)
{
for (var tag in tags)
{
var tagval = tags[tag];
tagdropdown += '<option value="' + tagval.id + '">' + tagval.name + '</option>';
}
}
tagdropdown += '</select>';
var form = $('<div id="image-annotate-edit-form"><form action="' + saveUrl + '" method="post"><input type="hidden" name="csrf" value="' + csrf + '" /><input type="hidden" name="currenturl" value="' + currentUrl + '" />' + tagdropdown + labels[1] + '<textarea id="image-annotate-text" name="text" rows="3" cols="30">' + this.note.text + '</textarea>' + labels[2] + '<textarea id="image-annotate-desc" name="desc" rows="3" cols="30"></textarea></form></div>');
this.form = form;
$('body').append(this.form);
this.form.css('left', this.area.offset().left + 'px');
this.form.css('top', (parseInt(this.area.offset().top) + parseInt(this.area.height()) + 7) + 'px');
// Set the area as a draggable/resizable element contained in the image canvas.
// Would be better to use the containment option for resizable but buggy
area.resizable({
handles: 'all',
stop: function(e, ui) {
form.css('left', area.offset().left + 'px');
form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
}
})
.draggable({
containment: image.canvas,
drag: function(e, ui) {
form.css('left', area.offset().left + 'px');
form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
},
stop: function(e, ui) {
form.css('left', area.offset().left + 'px');
form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 2) + 'px');
}
});
return this;
};
$.fn.annotateEdit.prototype.destroy = function() {
/// <summary>
/// Destroys an editable annotation area.
/// </summary>
this.image.canvas.children('.image-annotate-edit').hide();
this.area.resizable('destroy');
this.area.draggable('destroy');
this.area.css('height', '');
this.area.css('width', '');
this.area.css('left', '');
this.area.css('top', '');
this.form.remove();
}
$.fn.annotateView = function(image, note, labels, editable, csrf, deleteUrl) {
/// <summary>
/// Defines a annotation area.
/// </summary>
this.image = image;
this.note = note;
// Add the area
this.area = $('<div class="image-annotate-area' + (this.note.editable ? ' image-annotate-area-editable' : '') + '"><div></div></div>');
image.canvas.children('.image-annotate-view').prepend(this.area);
if (editable) {
this.delarea = $('<div class="image-annotate-area photoannotation-del-button"><div></div></div>');
image.canvas.children('.image-annotate-view').prepend(this.delarea);
this.delarea.bind('click',function () {
if (confirm(labels[3])) {
var alink = $(".g-fullsize-link");
alink.unbind();
alink.attr ('href', '#');
alink.removeAttr ('rel');
window.location = deleteUrl + "/" + csrf;
}
})
this.delarea.hide();
}
// Add the note
this.form = $('<div class="image-annotate-note">' + note.text + '</div>');
this.form.hide();
image.canvas.children('.image-annotate-view').append(this.form);
this.form.children('span.actions').hide();
// Set the position and size of the note
this.setPosition();
// Add the behavior: hide/display the note when hovering the area
var annotation = this;
this.area.hover(function() {
annotation.show();
if (annotation.delarea != undefined) {
annotation.delarea.show();
}
}, function() {
annotation.hide();
if (annotation.delarea != undefined) {
annotation.delarea.hide();
}
});
if (editable) {
this.delarea.hover(function() {
annotation.delarea.show();
}, function() {
annotation.delarea.hide();
});
}
// Edit a note feature
if (note.url != "" && note.url != null) {
this.area.bind('click',function () {
var alink = $(".g-fullsize-link");
alink.unbind();
alink.attr ('href', '#');
alink.removeAttr ('rel');
window.location = note.url;
})
}
};
$.fn.annotateView.prototype.setPosition = function() {
/// <summary>
/// Sets the position of an annotation.
/// </summary>
this.area.children('div').height((parseInt(this.note.height) - 2) + 'px');
this.area.children('div').width((parseInt(this.note.width) - 2) + 'px');
this.area.css('left', (this.note.left) + 'px');
this.area.css('top', (this.note.top) + 'px');
this.form.css('left', (this.note.left) + 'px');
this.form.css('top', (parseInt(this.note.top) + parseInt(this.note.height) + 7) + 'px');
if (this.delarea != undefined) {
this.delarea.children('div').height('14px');
this.delarea.children('div').width('14px');
this.delarea.css('left', (this.note.left + parseInt(this.note.width)) + 'px');
this.delarea.css('top', (this.note.top) + 'px');
}
};
$.fn.annotateView.prototype.show = function() {
/// <summary>
/// Highlights the annotation
/// </summary>
this.form.fadeIn(250);
if (!this.note.editable) {
this.area.addClass('image-annotate-area-hover');
} else {
this.area.addClass('image-annotate-area-editable-hover');
}
};
$.fn.annotateView.prototype.hide = function() {
/// <summary>
/// Removes the highlight from the annotation.
/// </summary>
this.form.fadeOut(250);
this.area.removeClass('image-annotate-area-hover');
this.area.removeClass('image-annotate-area-editable-hover');
};
$.fn.annotateView.prototype.destroy = function() {
/// <summary>
/// Destroys the annotation.
/// </summary>
this.area.remove();
this.form.remove();
}
$.fn.annotateView.prototype.edit = function() {
/// <summary>
/// Edits the annotation.
/// </summary>
if (this.image.mode == 'view') {
this.image.mode = 'edit';
var annotation = this;
// Create/prepare the editable note elements
var editable = new $.fn.annotateEdit(this.image, this.note);
$.fn.annotateImage.createSaveButton(editable, this.image, annotation);
// Add the delete button
var del = $('<a class="image-annotate-edit-delete">Delete</a>');
del.click(function() {
var form = $('#image-annotate-edit-form form');
$.fn.annotateImage.appendPosition(form, editable)
if (annotation.image.useAjax) {
$.ajax({
url: annotation.image.deleteUrl,
data: form.serialize(),
error: function(e) { alert("An error occured deleting that note.") }
});
}
annotation.image.mode = 'view';
editable.destroy();
annotation.destroy();
});
editable.form.append(del);
$.fn.annotateImage.createCancelButton(editable, this.image);
}
};
$.fn.annotateImage.appendPosition = function(form, editable) {
/// <summary>
/// Appends the annotations coordinates to the given form that is posted to the server.
/// </summary>
var areaFields = $('<input type="hidden" value="' + editable.area.height() + '" name="height"/>' +
'<input type="hidden" value="' + editable.area.width() + '" name="width"/>' +
'<input type="hidden" value="' + editable.area.position().top + '" name="top"/>' +
'<input type="hidden" value="' + editable.area.position().left + '" name="left"/>' +
'<input type="hidden" value="' + editable.note.id + '" name="id"/>');
form.append(areaFields);
}
$.fn.annotateView.prototype.resetPosition = function(editable, text) {
/// <summary>
/// Sets the position of an annotation.
/// </summary>
this.form.html(text);
this.form.hide();
// Resize
this.area.children('div').height(editable.area.height() + 'px');
this.area.children('div').width((editable.area.width() - 2) + 'px');
this.area.css('left', (editable.area.position().left) + 'px');
this.area.css('top', (editable.area.position().top) + 'px');
this.form.css('left', (editable.area.position().left) + 'px');
this.form.css('top', (parseInt(editable.area.position().top) + parseInt(editable.area.height()) + 7) + 'px');
// Save new position to note
this.note.top = editable.area.position().top;
this.note.left = editable.area.position().left;
this.note.height = editable.area.height();
this.note.width = editable.area.width();
this.note.text = text;
this.note.id = editable.note.id;
this.editable = true;
};
})(jQuery);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,21 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Items_Face_Model extends ORM {
}

View File

@ -0,0 +1,21 @@
<?php defined("SYSPATH") or die("No direct script access.");
/**
* Gallery - a web based photo album viewer and editor
* Copyright (C) 2000-2010 Bharat Mediratta
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
*/
class Items_Note_Model extends ORM {
}

View File

@ -0,0 +1,3 @@
name = "Photo Annotation"
description = "Spin-off of rWatcher's fantastic TagFaces module (fully compatible). You cannot run both modules at the same time."
version = 1

View File

@ -0,0 +1,141 @@
<?php defined("SYSPATH") or die("No direct script access.") ?>
<? $item = ORM::factory("item", $item_id); ?>
<style>
.jcrop-holder { text-align: left; }
.jcrop-vline, .jcrop-hline
{
font-size: 0;
position: absolute;
background: white url('<?= url::file("modules/photoannotation/images/jcrop.gif") ?>') top left repeat;
}
.jcrop-vline { height: 100%; width: 1px !important; }
.jcrop-hline { width: 100%; height: 1px !important; }
.jcrop-handle {
font-size: 1px;
width: 7px !important;
height: 7px !important;
border: 1px #eee solid;
background-color: #333;
*width: 9px;
*height: 9px;
}
.jcrop-tracker { width: 100%; height: 100%; }
.custom .jcrop-vline,
.custom .jcrop-hline
{
background: yellow;
}
.custom .jcrop-handle
{
border-color: black;
background-color: #C7BB00;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
</style>
<?= html::script("modules/photoannotation/js/jquery.Jcrop.js") ?>
<script language="Javascript">
// Remember to invoke within jQuery(window).load(...)
// If you don't, Jcrop may not initialize properly
jQuery(document).ready(function(){
jQuery('#g-photo-id-<?=$item->id ?>').Jcrop({
onChange: showCoords,
onSelect: showCoords
});
});
// Our simple event handler, called from onChange and onSelect
// event handlers, as per the Jcrop invocation above
function showCoords(c) {
jQuery('#x1').val(c.x);
jQuery('#y1').val(c.y);
jQuery('#x2').val(c.x2);
jQuery('#y2').val(c.y2);
};
</script>
<?= html::script("modules/photoannotation/js/jquery.Jcrop.js") ?>
<script language="Javascript">
// Remember to invoke within jQuery(window).load(...)
// If you don't, Jcrop may not initialize properly
jQuery(document).ready(function(){
jQuery('#g-select-photo-id-<?=$item->id ?>').Jcrop({
onChange: showCoords,
onSelect: showCoords
});
});
// Our simple event handler, called from onChange and onSelect
// event handlers, as per the Jcrop invocation above
function showCoords(c) {
jQuery('#x1').val(c.x);
jQuery('#y1').val(c.y);
jQuery('#x2').val(c.x2);
jQuery('#y2').val(c.y2);
};
</script>
<div id="g-select-item">
<?= $theme->dynamic_top() ?>
<div id="g-select-photo" align="center">
<h1><?= html::clean($title) ?></h1>
<p><?=t("Use the mouse to select a face on the image below."); ?></p>
<?= $item->resize_img(array("id" => "g-select-photo-id-{$item->id}", "class" => "g-select-resize", "style" => "position: fixed;")) ?>
</div>
</div>
<style>
#face_title {
width: 200px;
}
#face_description {
width: 400px;
}
#x1 {
width: 40px;
}
#y1 {
width: 40px;
}
#x2 {
width: 40px;
}
#y2 {
width: 40px;
}
li {
display: inline;
list-style-type: none;
float:left;
}
</style>
<div id="g-coordinates">
<?=$form ?>
</div>
<br/><br/><br/>
<fieldset>
<div id="g-delete-faces">
<h2><?= t("Delete Existing Faces and Notes") ?></h2>
<?= $delete_form ?>
</div>
</fieldset>
<br/>
<div id="g-exit-faces">
<p><a href="<?= url::abs_site("{$item->type}s/{$item->id}") ?>"><?= t("Return to photo") ?></a></p>
</div>
<?= $theme->dynamic_bottom() ?>

View File

@ -0,0 +1,79 @@
<?php defined("SYSPATH") or die("No direct script access.");
// Check and see if the current photo has any faces or notes associated with it.
$existingFaces = ORM::factory("items_face")
->where("item_id", "=", $item->id)
->find_all();
$existingNotes = ORM::factory("items_note")
->where("item_id", "=", $item->id)
->find_all();
$tags_arraystring = "";
$jscode = "";
// If it does, then insert some javascript and display an image map
// to show where the faces are at.
if ((count($existingFaces) > 0) || (count($existingNotes) > 0)) {
$jscode = "notes: [ ";
foreach ($existingFaces as $oneFace) {
$oneTag = ORM::factory("tag", $oneFace->tag_id);
$jscode .= "{ \"top\": ". $oneFace->y1 .",\n";
$jscode .= "\"left\": ". $oneFace->x1 .",\n";
$jscode .= "\"width\": ". ($oneFace->x2 - $oneFace->x1) .",\n";
$jscode .= "\"height\": ". ($oneFace->y2 - $oneFace->y1) .",\n";
$jscode .= "\"text\": \"". html::clean($oneTag->name) ."\",\n";
$jscode .= "\"noteid\": ". $oneNote->id .",\n";
$jscode .= "\"editable\": true,\n";
$jscode .= "\"url\": \"". $oneTag->url() ."\" },\n";
}
foreach ($existingNotes as $oneNote) {
$tagdesc = "";
if ($oneNote->description) {
$tagdesc = "<br />". html::clean($oneNote->description);
}
$jscode .= "{ \"top\": ". $oneNote->y1 .",\n";
$jscode .= "\"left\": ". $oneNote->x1 .",\n";
$jscode .= "\"width\": ". ($oneNote->x2 - $oneNote->x1) .",\n";
$jscode .= "\"height\": ". ($oneNote->y2 - $oneNote->y1) .",\n";
$jscode .= "\"text\": \"". html::clean($oneNote->title) . $tagdesc ."\",\n";
$jscode .= "\"noteid\": ". $oneNote->id .",\n";
$jscode .= "\"editable\": false,\n";
$jscode .= "\"url\": \"\" },\n";
}
$jscode = trim($jscode, ",\n");
$jscode .= " ],";
}
$item_tags = ORM::factory("tag")
->join("items_tags", "tags.id", "items_tags.tag_id")
->where("items_tags.item_id", "=", $item->id)
->find_all();
$tags_arraystring = "tags: [ ";
foreach ($item_tags as $current_tag) {
$tags_arraystring .= "{'name':'". html::clean($current_tag->name) ."','id':'". $current_tag->id ."'},";
}
$tags_arraystring = trim($tags_arraystring, ",");
$tags_arraystring .= " ],";
$labels_arraystring = "labels: [ '". t("Tag:") ."','". t("Note Title:") ."','". t("Description (optional):") ."','". t("Are you sure you want to delete this annotation?") ."' ],";
?>
<script language="javascript">
$(document).ready(function() {
$("#g-item-id-<?= $item->id ?>").annotateImage({
<? if ((access::can("view", $item)) && (access::can("edit", $item))): ?>
editable: true,
<? else: ?>
editable: false,
<? endif ?>
saveUrl: '<?= url::site("photoannotation/save/". $item->id) ?>',
deleteUrl: '<?= url::site("photoannotation/delete/". $item->id) ?>',
currentUrl: '<?= url::site(Router::$complete_uri, $protocol); ?>',
<?= $tags_arraystring ?>
<?= $labels_arraystring ?>
<?= $jscode ?>
useAjax: false,
csrf: '<?= $csrf ?>'
});
});
</script>
<?php
?>