summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Kinkade <nath@nkinka.de>2011-07-23 17:00:38 +0000
committerNathan Kinkade <nath@nkinka.de>2011-07-23 17:00:38 +0000
commit4ac3be445ebb041a503bbd2999f5d08dbc9a8578 (patch)
tree1df13bcd5f0d435c13b2404cf5909a26b39c499a
parent4fdb50685b9d805cbb4ba1eb3287e4d8191b7838 (diff)
Added a new module for annotating photos.
-rw-r--r--modules/photoannotation/controllers/admin_photoannotation.php312
-rw-r--r--modules/photoannotation/controllers/photoannotation.php251
-rw-r--r--modules/photoannotation/css/colorpicker.css161
-rw-r--r--modules/photoannotation/css/photoannotation.css267
-rw-r--r--modules/photoannotation/helpers/photoannotation.php275
-rw-r--r--modules/photoannotation/helpers/photoannotation_block.php40
-rw-r--r--modules/photoannotation/helpers/photoannotation_event.php248
-rw-r--r--modules/photoannotation/helpers/photoannotation_installer.php119
-rw-r--r--modules/photoannotation/helpers/photoannotation_theme.php81
-rw-r--r--modules/photoannotation/images/blank.gifbin0 -> 49 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_background.pngbin0 -> 1897 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_hex.pngbin0 -> 532 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_hsb_b.pngbin0 -> 970 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_hsb_h.pngbin0 -> 1012 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_hsb_s.pngbin0 -> 1171 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_indic.gifbin0 -> 86 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_overlay.pngbin0 -> 10355 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_rgb_b.pngbin0 -> 970 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_rgb_g.pngbin0 -> 1069 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_rgb_r.pngbin0 -> 1066 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_select.gifbin0 -> 78 bytes
-rw-r--r--modules/photoannotation/images/colorpicker_submit.pngbin0 -> 1016 bytes
-rw-r--r--modules/photoannotation/images/custom_background.pngbin0 -> 1916 bytes
-rw-r--r--modules/photoannotation/images/custom_hex.pngbin0 -> 562 bytes
-rw-r--r--modules/photoannotation/images/custom_hsb_b.pngbin0 -> 1097 bytes
-rw-r--r--modules/photoannotation/images/custom_hsb_h.pngbin0 -> 970 bytes
-rw-r--r--modules/photoannotation/images/custom_hsb_s.pngbin0 -> 1168 bytes
-rw-r--r--modules/photoannotation/images/custom_indic.gifbin0 -> 86 bytes
-rw-r--r--modules/photoannotation/images/custom_rgb_b.pngbin0 -> 1008 bytes
-rw-r--r--modules/photoannotation/images/custom_rgb_g.pngbin0 -> 1069 bytes
-rw-r--r--modules/photoannotation/images/custom_rgb_r.pngbin0 -> 1018 bytes
-rw-r--r--modules/photoannotation/images/custom_submit.pngbin0 -> 997 bytes
-rw-r--r--modules/photoannotation/images/delete.pngbin0 -> 334 bytes
-rw-r--r--modules/photoannotation/images/edit.pngbin0 -> 208 bytes
-rw-r--r--modules/photoannotation/images/select.pngbin0 -> 506 bytes
-rw-r--r--modules/photoannotation/images/select2.pngbin0 -> 518 bytes
-rw-r--r--modules/photoannotation/images/slider.pngbin0 -> 315 bytes
-rw-r--r--modules/photoannotation/js/jquery.annotate.js568
-rw-r--r--modules/photoannotation/js/jquery.annotate.min.js1
-rw-r--r--modules/photoannotation/js/jquery.colorpicker.js484
-rw-r--r--modules/photoannotation/js/jquery.colorpicker.min.js1
-rw-r--r--modules/photoannotation/models/items_face.php21
-rw-r--r--modules/photoannotation/models/items_note.php21
-rw-r--r--modules/photoannotation/models/items_user.php21
-rw-r--r--modules/photoannotation/models/photoannotation_notification.php21
-rw-r--r--modules/photoannotation/module.info3
-rw-r--r--modules/photoannotation/views/admin_photoannotation.html.php30
-rw-r--r--modules/photoannotation/views/admin_photoannotation_converter.html.php10
-rw-r--r--modules/photoannotation/views/admin_photoannotation_tagsmaintanance.html.php27
-rw-r--r--modules/photoannotation/views/photoannotation_block.html.php17
-rw-r--r--modules/photoannotation/views/photoannotation_cloud.html.php9
-rw-r--r--modules/photoannotation/views/photoannotation_highlight_block.html.php135
-rw-r--r--modules/photoannotation/views/photoannotation_user_search.html.php55
53 files changed, 3178 insertions, 0 deletions
diff --git a/modules/photoannotation/controllers/admin_photoannotation.php b/modules/photoannotation/controllers/admin_photoannotation.php
new file mode 100644
index 00000000..dc7436f6
--- /dev/null
+++ b/modules/photoannotation/controllers/admin_photoannotation.php
@@ -0,0 +1,312 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+/**
+ * Gallery - a web based photo album viewer and editor
+ * Copyright (C) 2000-2009 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 Admin_Photoannotation_Controller extends Admin_Controller {
+ public function index() {
+ print $this->_get_view();
+ }
+
+ public function converter() {
+ print $this->_get_converter_view();
+ }
+
+ public function tagsmaintanance($delete) {
+ print $this->_get_tagsmaintanance_view($delete);
+ }
+
+ public function converthandler() {
+ access::verify_csrf();
+ $form = $this->_get_converter_form();
+ if ($form->validate()) {
+ //Load the source tag
+ $sourcetag = ORM::factory("tag", $form->sourcetag->value);
+ if (!$sourcetag->loaded()) {
+ message::error(t("The specified tag could not be found"));
+ url::redirect("admin/photoannotation/converter");
+ }
+ //Load the target user
+ $targetuser = ORM::factory("user", $form->targetuser->value);
+ if (!$targetuser->loaded()) {
+ message::error(t("The specified person could not be found"));
+ url::redirect("admin/photoannotation/converter");
+ }
+ //Load all existing tag annotations
+ $tag_annotations = ORM::factory("items_face")->where("tag_id", "=", $sourcetag->id)->find_all();
+ //Disable user notifications so that users don't get flooded with mails
+ $old_notification_setting = module::get_var("photoannotation", "nonotifications", false);
+ module::set_var("photoannotation", "nonotifications", true, true);
+ foreach ($tag_annotations as $tag_annotation) {
+ photoannotation::saveuser($targetuser->id, $tag_annotation->item_id, $tag_annotation->x1, $tag_annotation->y1, $tag_annotation->x2, $tag_annotation->y2, $tag_annotation->description);
+ //Delete the old annotation
+ $tag_annotation->delete();
+ }
+ //Remove and delete old tag
+ if ($form->deletetag->value) {
+ $this->_remove_tag($sourcetag, true);
+ } elseif ($form->removetag->value) {
+ $this->_remove_tag($sourcetag, false);
+ }
+ module::set_var("photoannotation", "nonotifications", $old_notification_setting, true);
+ message::success(t("%count tag annotations (%tagname) have been converted to user annotations (%username)", array("count" => count($tag_annotations), "tagname" => $sourcetag->name, "username" => $targetuser->display_name())));
+ url::redirect("admin/photoannotation/converter");
+ }
+ print $this->_get_converter_view($form);
+ }
+
+ private function _remove_tag($tag, $delete) {
+ $name = $tag->name;
+ db::build()->delete("items_tags")->where("tag_id", "=", $tag->id)->execute();
+ if ($delete) {
+ $tag->delete();
+ }
+ }
+
+ public function handler() {
+ access::verify_csrf();
+
+ $form = $this->_get_form();
+ if ($form->validate()) {
+ module::set_var(
+ "photoannotation", "noborder", $form->hoverphoto->noborder->value, true);
+ module::set_var(
+ "photoannotation", "bordercolor", $form->hoverphoto->bordercolor->value);
+ module::set_var(
+ "photoannotation", "noclickablehover", $form->hoverclickable->noclickablehover->value, true);
+ module::set_var(
+ "photoannotation", "clickablehovercolor", $form->hoverclickable->clickablehovercolor->value);
+ module::set_var(
+ "photoannotation", "nohover", $form->hovernoclickable->nohover->value, true);
+ module::set_var(
+ "photoannotation", "hovercolor", $form->hovernoclickable->hovercolor->value);
+ module::set_var(
+ "photoannotation", "showusers", $form->legendsettings->showusers->value, true);
+ module::set_var(
+ "photoannotation", "showfaces", $form->legendsettings->showfaces->value, true);
+ module::set_var(
+ "photoannotation", "shownotes", $form->legendsettings->shownotes->value, true);
+ module::set_var(
+ "photoannotation", "fullname", $form->legendsettings->fullname->value, true);
+ module::set_var(
+ "photoannotation", "nonotifications", $form->notifications->nonotifications->value, true);
+ module::set_var(
+ "photoannotation", "notificationoptout", $form->notifications->notificationoptout->value, true);
+ module::set_var(
+ "photoannotation", "allowguestsearch", $form->notifications->allowguestsearch->value, true);
+ module::set_var(
+ "photoannotation", "newtagsubject", strip_tags($form->newtagmail->newtagsubject->value));
+ module::set_var(
+ "photoannotation", "newtagbody", strip_tags($form->newtagmail->newtagbody->value));
+ module::set_var(
+ "photoannotation", "newcommentsubject", strip_tags($form->newcommentmail->newcommentsubject->value));
+ module::set_var(
+ "photoannotation", "newcommentbody", strip_tags($form->newcommentmail->newcommentbody->value));
+ module::set_var(
+ "photoannotation", "updatedcommentsubject", strip_tags($form->updatedcommentmail->updatedcommentsubject->value));
+ module::set_var(
+ "photoannotation", "updatedcommentbody", strip_tags($form->updatedcommentmail->updatedcommentbody->value));
+ module::set_var(
+ "photoannotation", "onuserdelete", $form->onuserdelete->onuserdelete->value);
+ message::success(t("Your settings have been saved."));
+ url::redirect("admin/photoannotation");
+ }
+ print $this->_get_view($form);
+ }
+
+ private function _get_view($form=null) {
+ $v = new Admin_View("admin.html");
+ $v->page_title = t("Photo annotation");
+ $v->content = new View("admin_photoannotation.html");
+ $v->content->form = empty($form) ? $this->_get_form() : $form;
+ return $v;
+ }
+
+ private function _get_converter_view($form=null) {
+ $v = new Admin_View("admin.html");
+ $v->page_title = t("Photo annotation converter");
+ $v->content = new View("admin_photoannotation_converter.html");
+ $v->content->form = empty($form) ? $this->_get_converter_form() : $form;
+ return $v;
+ }
+
+ private function _get_tagsmaintanance_view($delete = false) {
+ $tag_orpanes_count = 0;
+ $user_orphanes_count = 0;
+ $item_orphanes_count = 0;
+ $tag_orpanes_deleted = 0;
+ $user_orphanes_deleted = 0;
+ $item_orphanes_deleted = 0;
+ //check all tag annotations
+ $tag_annotations = ORM::factory("items_face")->find_all();
+ foreach ($tag_annotations as $tag_annotation) {
+ $check_tag = ORM::factory("tag")->where("id", "=", $tag_annotation->tag_id)->find();
+ if (!$check_tag->loaded()) {
+ if ($delete) {
+ $tag_annotation->delete();
+ $tag_orpanes_deleted++;
+ } else {
+ $tag_orpanes_count++;
+ }
+ } else {
+ $check_item = ORM::factory("item")->where("id", "=", $tag_annotation->item_id)->find();
+ if (!$check_item->loaded()) {
+ if ($delete) {
+ $tag_annotation->delete();
+ $item_orpanes_deleted++;
+ } else {
+ $item_orpanes_count++;
+ }
+ }
+ }
+ }
+ //check all user annotations
+ $user_annotations = ORM::factory("items_user")->find_all();
+ foreach ($user_annotations as $user_annotation) {
+ $check_user = ORM::factory("user")->where("id", "=", $user_annotation->user_id)->find();
+ if (!$check_user->loaded()) {
+ if ($delete) {
+ $user_annotation->delete();
+ $user_orpanes_deleted++;
+ } else {
+ $user_orphanes_count++;
+ }
+ } else {
+ $check_item = ORM::factory("item")->where("id", "=", $user_annotation->item_id)->find();
+ if (!$check_item->loaded()) {
+ if ($delete) {
+ $user_annotation->delete();
+ $item_orpanes_deleted++;
+ } else {
+ $item_orpanes_count++;
+ }
+ }
+ }
+ }
+
+ //check all user annotations
+ $note_annotations = ORM::factory("items_note")->find_all();
+ foreach ($note_annotations as $note_annotation) {
+ $check_item = ORM::factory("item")->where("id", "=", $note_annotation->item_id)->find();
+ if (!$check_item->loaded()) {
+ if ($delete) {
+ $note_annotation->delete();
+ $item_orpanes_deleted++;
+ } else {
+ $item_orpanes_count++;
+ }
+ }
+ }
+ $v = new Admin_View("admin.html");
+ $v->page_title = t("Photo annotation tags maintanance");
+ $v->content = new View("admin_photoannotation_tagsmaintanance.html");
+ $v->content->tag_orpanes_count = $tag_orpanes_count;
+ $v->content->user_orphanes_count = $user_orphanes_count;
+ $v->content->item_orphanes_count = $item_orphanes_count;
+ $v->content->tag_orpanes_deleted = $tag_orpanes_deleted;
+ $v->content->user_orphanes_deleted = $user_orphanes_deleted;
+ $v->content->item_orphanes_deleted = $item_orphanes_deleted;
+ $v->content->dodeletion = $delete;
+ return $v;
+ }
+
+ private function _get_converter_form() {
+ //get all tags
+ $tags = ORM::factory("tag")->order_by("name", "ASC")->find_all();
+ foreach ($tags as $tag) {
+ $tag_array[$tag->id] = $tag->name;
+ }
+ //get all users
+ $users = ORM::factory("user")->order_by("name", "ASC")->find_all();
+ foreach ($users as $user) {
+ $user_array[$user->id] = $user->display_name();
+ }
+ $form = new Forge("admin/photoannotation/converthandler", "", "post", array("id" => "g-admin-form"));
+ $form->dropdown("sourcetag")->label(t("Select tag"))
+ ->options($tag_array);
+ $form->dropdown("targetuser")->label(t("Select user"))
+ ->options($user_array);
+ $form->checkbox("deletetag")->label(t("Delete the tag after conversion."));
+ $form->checkbox("removetag")->label(t("Remove the tag from photos after conversion."));
+ $form->submit("submit")->value(t("Convert"));
+ return $form;
+ }
+
+ private function _get_form() {
+ if (module::is_active("comment")) {
+ $comment_required = "";
+ } else {
+ $comment_required = " (comment module required)";
+ }
+ $form = new Forge("admin/photoannotation/handler", "", "post", array("id" => "g-admin-form"));
+ $group = $form->group("hoverphoto")->label(t("Hovering over the photo"));
+ $group->checkbox("noborder")->label(t("Don't show borders."))
+ ->checked(module::get_var("photoannotation", "noborder", false));
+ $group->input("bordercolor")->label(t('Border color'))
+ ->value(module::get_var("photoannotation", "bordercolor", "000000"))
+ ->rules("valid_alpha_numeric|length[6]");
+ $group = $form->group("hoverclickable")->label(t("Hovering over a clickable annotation"));
+ $group->checkbox("noclickablehover")->label(t("Don't show borders."))
+ ->checked(module::get_var("photoannotation", "noclickablehover", false));
+ $group->input("clickablehovercolor")->label(t('Border color'))
+ ->value(module::get_var("photoannotation", "clickablehovercolor", "00AD00"))
+ ->rules("valid_alpha_numeric|length[6]");
+ $group = $form->group("hovernoclickable")->label(t("Hovering over a non-clickable annotation"));
+ $group->checkbox("nohover")->label(t("Don't show borders."))
+ ->checked(module::get_var("photoannotation", "nohover", false));
+ $group->input("hovercolor")->label(t('Border color'))
+ ->value(module::get_var("photoannotation", "hovercolor", "990000"))
+ ->rules("valid_alpha_numeric|length[6]");
+ $group = $form->group("legendsettings")->label(t("Legend settings"));
+ $group->checkbox("showusers")->label(t("Show user annotations below photo."))
+ ->checked(module::get_var("photoannotation", "showusers", false));
+ $group->checkbox("showfaces")->label(t("Show tag annotations below photo."))
+ ->checked(module::get_var("photoannotation", "showfaces", false));
+ $group->checkbox("shownotes")->label(t("Show note annotations below photo."))
+ ->checked(module::get_var("photoannotation", "shownotes", false));
+ $group->checkbox("fullname")->label(t("Show full name of a user instead of the username on annotations (username will be dispayed for users without a full name)."))
+ ->checked(module::get_var("photoannotation", "fullname", false));
+ $group = $form->group("notifications")->label(t("Notification and people cloud settings"));
+ $group->checkbox("nonotifications")->label(t("Disable user notifications."))
+ ->checked(module::get_var("photoannotation", "nonotifications", false));
+ $group->checkbox("notificationoptout")->label(t("Notify users by default (only applies to new users and user who have not saved their profile after installing this module)."))
+ ->checked(module::get_var("photoannotation", "notificationoptout", false));
+ $group->checkbox("allowguestsearch")->label(t("Show people cloud and allow people search for guests."))
+ ->checked(module::get_var("photoannotation", "allowguestsearch", false));
+ $group = $form->group("newtagmail")->label(t("Customize the mail sent to users when a user annotation is created"));
+ $group->input("newtagsubject")->label(t("Subject"))
+ ->value(module::get_var("photoannotation", "newtagsubject", "Someone tagged a photo of you"));
+ $group->textarea("newtagbody")->label(t("Body (allowed placeholders: %name = name of the recipient, %url = link to the item that was tagged)"))
+ ->value(module::get_var("photoannotation", "newtagbody", "Hello %name, please visit %url to view the photo."));
+ $group = $form->group("newcommentmail")->label(t("Customize the mail sent to users when a comment is added". $comment_required));
+ $group->input("newcommentsubject")->label(t("Subject"))
+ ->value(module::get_var("photoannotation", "newcommentsubject", "Someone added a comment to photo of you"));
+ $group->textarea("newcommentbody")->label(t("Body (allowed placeholders: %name = name of the recipient, %url = link to the item that was commented on)"))
+ ->value(module::get_var("photoannotation", "newcommentbody", "Hello %name, please visit %url to read the comment."));
+ $group = $form->group("updatedcommentmail")->label(t("Customize the mail sent to users when a comment is updated". $comment_required));
+ $group->input("updatedcommentsubject")->label(t("Subject"))
+ ->value(module::get_var("photoannotation", "updatedcommentsubject", "Someone updated a comment to photo of you"));
+ $group->textarea("updatedcommentbody")->label(t("Body (allowed placeholders: %name = name of the recipient, %url = link to the item that was commented on)"))
+ ->value(module::get_var("photoannotation", "updatedcommentbody", "Hello %name, please visit %url to read the comment."));
+ $group = $form->group("onuserdelete")->label(t("Auto conversion settings"));
+ $group->dropdown("onuserdelete")->label(t("When deleting a user do the following with all annotations associated with this user"))
+ ->options(array("0" => t("Delete annotation"), "1" => t("Convert to tag annotation"), "2" => t("Convert to note annotation")))
+ ->selected(module::get_var("photoannotation", "onuserdelete", "0"));
+ $form->submit("submit")->value(t("Save"));
+ return $form;
+ }
+}
diff --git a/modules/photoannotation/controllers/photoannotation.php b/modules/photoannotation/controllers/photoannotation.php
new file mode 100644
index 00000000..ed6083e5
--- /dev/null
+++ b/modules/photoannotation/controllers/photoannotation.php
@@ -0,0 +1,251 @@
+<?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 showuser() {
+ if (identity::active_user()->guest && !module::get_var("photoannotation", "allowguestsearch", false)) {
+ message::error(t("You have to log in to perform a people search."));
+ url::redirect(url::site());
+ return;
+ }
+ $form = photoannotation::get_user_search_form("g-user-cloud-form");
+ $user_id = Input::instance()->get("name", "");
+ if ($user_id == "") {
+ $user_id = Input::instance()->post("name", "");
+ }
+ $getuser = photoannotation::getuser($user_id);
+ if ($getuser->found) {
+ url::redirect(user_profile::url($getuser->user->id));
+ return;
+ }
+ $page_size = module::get_var("gallery", "page_size", 9);
+ $page = Input::instance()->get("page", 1);
+ $offset = ($page - 1) * $page_size;
+
+ // Make sure that the page references a valid offset
+ if ($page < 1) {
+ $page = 1;
+ }
+ list ($count, $result) = photoannotation::search_user($user_id, $page_size, $offset);
+ $max_pages = max(ceil($count / $page_size), 1);
+ if ($page > 1) {
+ $previous_page_url = url::site("photoannotation/showuser?name=". $user_id ."&amp;page=". ($page - 1));
+ }
+ if ($page < $max_pages) {
+ $next_page_url = url::site("photoannotation/showuser?name=". $user_id ."&amp;page=". ($page + 1));
+ }
+ if ($user_id == "") {
+ $user_id = "*";
+ }
+ $template = new Theme_View("page.html", "other", "usersearch");
+ $template->set_global("position", $page);
+ $template->set_global("total", $max_pages);
+ $template->content = new View("photoannotation_user_search.html");
+ $template->content->search_form = photoannotation::get_user_search_form(g-user-search-form);
+ $template->content->users = $result;
+ $template->content->q = $user_id;
+ $template->content->count = $count;
+ $template->content->paginator = new View("paginator.html");
+ $template->content->paginator->previous_page_url = $previous_page_url;
+ $template->content->paginator->next_page_url = $next_page_url;
+ print $template;
+ }
+
+ public function save($item_id) {
+ // Prevent Cross Site Request Forgery
+ access::verify_csrf();
+ //Get form data
+ $item = ORM::factory("item", $item_id);
+ $annotate_id = $_POST["noteid"];
+ $notetype = $_POST["notetype"];
+ $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
+ $item_title = $_POST["text"];
+ $tag_data = $_POST["tagsList"];
+ $user_id = "";
+ $user_id = $_POST["userlist"];
+ $description = $_POST["desc"];
+ $redir_uri = url::abs_site("{$item->type}s/{$item->id}");
+ //If this is a user then get the id
+ if ($user_id != "") {
+ $getuser = photoannotation::getuser($user_id);
+ if (!$getuser->found) {
+ message::error(t("Could not find anyone with the name %user.", array("user" => $user_id)));
+ url::redirect($redir_uri);
+ return;
+ }
+ if ($getuser->isguest) {
+ message::error(t("You cannot create an annotation for the guest user."));
+ url::redirect($redir_uri);
+ return;
+ }
+ $user_id = $getuser->user->id;
+ }
+
+ //Add tag to item, create tag if not exists
+ if ($tag_data != "") {
+ $tag = ORM::factory("tag")->where("name", "=", $tag_data)->find();
+ if (!$tag->loaded()) {
+ $tag->name = $tag_data;
+ $tag->count = 0;
+ }
+ $tag->add($item);
+ $tag->count++;
+ $tag->save();
+ $tag_data = $tag->id;
+ } else {
+ $tag_data = "";
+ }
+ //Save annotation
+ if ($annotate_id == "new") { //This is a new annotation
+ if ($user_id != "") { //Save user
+ photoannotation::saveuser($user_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ } elseif ($tag_data != "") { //Save face
+ photoannotation::saveface($tag_data, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ } elseif ($item_title != "") { //Save note
+ photoannotation::savenote($item_title, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ } else { //Something's wrong
+ message::error(t("Please select a person or tag or specify a title."));
+ url::redirect($redir_uri);
+ return;
+ }
+ } else { //This is an update to an existing annotation
+ switch ($notetype) {
+ case "user": //the original annotation is a user
+ $updateduser = ORM::factory("items_user") //load the existing user
+ ->where("id", "=", $annotate_id)
+ ->find();
+ if ($user_id != "") { //Conversion user -> user
+ photoannotation::saveuser($user_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ } elseif ($tag_data != "") { //Conversion user -> face
+ photoannotation::saveface($tag_data, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updateduser->delete(); //delete old user
+ } elseif ($item_title != "") { //Conversion user -> note
+ photoannotation::savenote($item_title, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updateduser->delete(); //delete old user
+ } else { //Somethings wrong
+ message::error(t("Please select a person or tag or specify a title."));
+ url::redirect($redir_uri);
+ return;
+ }
+ break;
+ case "face": //the original annotation is a face
+ $updatedface = ORM::factory("items_face") //load the existing user
+ ->where("id", "=", $annotate_id)
+ ->find();
+ if ($user_id != "") { //Conversion face -> user
+ photoannotation::saveuser($user_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updatedface->delete(); //delete old face
+ } elseif ($tag_data != "") { //Conversion face -> face
+ photoannotation::saveface($tag_data, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description, $annotate_id);
+ } elseif ($item_title != "") { //Conversion face -> note
+ photoannotation::savenote($item_title, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updatedface->delete(); //delete old face
+ } else { //Somethings wrong
+ message::error(t("Please select a person or tag or specify a title."));
+ url::redirect($redir_uri);
+ return;
+ }
+ break;
+ case "note": //the original annotation is a note
+ $updatednote = ORM::factory("items_note") //load the existing user
+ ->where("id", "=", $annotate_id)
+ ->find();
+ if ($user_id != "") { //Conversion note -> user
+ photoannotation::saveuser($user_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updatednote->delete(); //delete old note
+ } elseif ($tag_data != "") { //Conversion note -> face
+ photoannotation::saveface($tag_data, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description);
+ $updatednote->delete(); //delete old note
+ } elseif ($item_title != "") { //Conversion note -> note
+ photoannotation::savenote($item_title, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description, $annotate_id);
+ } else { //Somethings wrong
+ message::error(t("Please select a person or tag or specify a title."));
+ url::redirect($redir_uri);
+ return;
+ }
+ break;
+ default:
+ message::error(t("Please select a person or tag or specify a title."));
+ url::redirect($redir_uri);
+ return;
+ }
+ }
+ message::success(t("Annotation saved."));
+ url::redirect($redir_uri);
+ return;
+ }
+
+ public function delete($item_data) {
+ // Prevent Cross Site Request Forgery
+ access::verify_csrf();
+
+ //Get form data
+ $item = ORM::factory("item", $item_data);
+ $noteid = $_POST["noteid"];
+ $notetype = $_POST["notetype"];
+ $redir_uri = url::abs_site("{$item->type}s/{$item->id}");
+
+ if ($noteid == "") {
+ message::error(t("Please select a tag or note to delete."));
+ url::redirect($redir_uri);
+ return;
+ }
+ switch ($notetype) {
+ case "user":
+ db::build()->delete("items_users")->where("id", "=", $noteid)->execute();
+ break;
+ case "face":
+ db::build()->delete("items_faces")->where("id", "=", $noteid)->execute();
+ break;
+ case "note":
+ db::build()->delete("items_notes")->where("id", "=", $noteid)->execute();
+ break;
+ default:
+ message::error(t("Please select a tag or note to delete."));
+ url::redirect($redir_uri);
+ return;
+ }
+ message::success(t("Annotation deleted."));
+ url::redirect($redir_uri);
+ }
+
+ public function autocomplete() {
+ if (!identity::active_user()->guest || module::get_var("photoannotation", "allowguestsearch", false)) {
+ $users = array();
+ $user_parts = explode(",", Input::instance()->get("q"));
+ $limit = Input::instance()->get("limit");
+ $user_part = ltrim(end($user_parts));
+ $user_list = ORM::factory("user")
+ ->where("name", "LIKE", "{$user_part}%")
+ ->or_where("full_name", "LIKE", "{$user_part}%")
+ ->order_by("full_name", "ASC")
+ ->limit($limit)
+ ->find_all();
+ foreach ($user_list as $user) {
+ if ($user->name != "guest") {
+ $users[] = $user->display_name() ." (". $user->name .")";
+ }
+ }
+ print implode("\n", $users);
+ }
+ }
+}
diff --git a/modules/photoannotation/css/colorpicker.css b/modules/photoannotation/css/colorpicker.css
new file mode 100644
index 00000000..05b02b48
--- /dev/null
+++ b/modules/photoannotation/css/colorpicker.css
@@ -0,0 +1,161 @@
+.colorpicker {
+ width: 356px;
+ height: 176px;
+ overflow: hidden;
+ position: absolute;
+ background: url(../images/colorpicker_background.png);
+ font-family: Arial, Helvetica, sans-serif;
+ display: none;
+}
+.colorpicker_color {
+ width: 150px;
+ height: 150px;
+ left: 14px;
+ top: 13px;
+ position: absolute;
+ background: #f00;
+ overflow: hidden;
+ cursor: crosshair;
+}
+.colorpicker_color div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 150px;
+ height: 150px;
+ background: url(../images/colorpicker_overlay.png);
+}
+.colorpicker_color div div {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 11px;
+ height: 11px;
+ overflow: hidden;
+ background: url(../images/colorpicker_select.gif);
+ margin: -5px 0 0 -5px;
+}
+.colorpicker_hue {
+ position: absolute;
+ top: 13px;
+ left: 171px;
+ width: 35px;
+ height: 150px;
+ cursor: n-resize;
+}
+.colorpicker_hue div {
+ position: absolute;
+ width: 35px;
+ height: 9px;
+ overflow: hidden;
+ background: url(../images/colorpicker_indic.gif) left top;
+ margin: -4px 0 0 0;
+ left: 0px;
+}
+.colorpicker_new_color {
+ position: absolute;
+ width: 60px;
+ height: 30px;
+ left: 213px;
+ top: 13px;
+ background: #f00;
+}
+.colorpicker_current_color {
+ position: absolute;
+ width: 60px;
+ height: 30px;
+ left: 283px;
+ top: 13px;
+ background: #f00;
+}
+.colorpicker input {
+ background-color: transparent;
+ border: 1px solid transparent;
+ position: absolute;
+ font-size: 10px;
+ font-family: Arial, Helvetica, sans-serif;
+ color: #898989;
+ top: 4px;
+ right: 11px;
+ text-align: right;
+ margin: 0;
+ padding: 0;
+ height: 11px;
+}
+.colorpicker_hex {
+ position: absolute;
+ width: 72px;
+ height: 22px;
+ background: url(../images/colorpicker_hex.png) top;
+ left: 212px;
+ top: 142px;
+}
+.colorpicker_hex input {
+ right: 6px;
+}
+.colorpicker_field {
+ height: 22px;
+ width: 62px;
+ background-position: top;
+ position: absolute;
+}
+.colorpicker_field span {
+ position: absolute;
+ width: 12px;
+ height: 22px;
+ overflow: hidden;
+ top: 0;
+ right: 0;
+ cursor: n-resize;
+}
+.colorpicker_rgb_r {
+ background-image: url(../images/colorpicker_rgb_r.png);
+ top: 52px;
+ left: 212px;
+}
+.colorpicker_rgb_g {
+ background-image: url(../images/colorpicker_rgb_g.png);
+ top: 82px;
+ left: 212px;
+}
+.colorpicker_rgb_b {
+ background-image: url(../images/colorpicker_rgb_b.png);
+ top: 112px;
+ left: 212px;
+}
+.colorpicker_hsb_h {
+ background-image: url(../images/colorpicker_hsb_h.png);
+ top: 52px;
+ left: 282px;
+}
+.colorpicker_hsb_s {
+ background-image: url(../images/colorpicker_hsb_s.png);
+ top: 82px;
+ left: 282px;
+}
+.colorpicker_hsb_b {
+ background-image: url(../images/colorpicker_hsb_b.png);
+ top: 112px;
+ left: 282px;
+}
+.colorpicker_submit {
+ position: absolute;
+ width: 22px;
+ height: 22px;
+ background: url(../images/colorpicker_submit.png) top;
+ left: 322px;
+ top: 142px;
+ overflow: hidden;
+}
+.colorpicker_focus {
+ background-position: center;
+}
+.colorpicker_hex.colorpicker_focus {
+ background-position: bottom;
+}
+.colorpicker_submit.colorpicker_focus {
+ background-position: bottom;
+}
+.colorpicker_slider {
+ background-position: bottom;
+}
diff --git a/modules/photoannotation/css/photoannotation.css b/modules/photoannotation/css/photoannotation.css
new file mode 100644
index 00000000..90d7026b
--- /dev/null
+++ b/modules/photoannotation/css/photoannotation.css
@@ -0,0 +1,267 @@
+.photoannotation-user-search {
+ border-bottom-style:solid;
+ border-bottom-width:1px;
+ padding: 0.8em 0.8em !important;
+}
+
+#g-user-cloud ul {
+ font-size: 1.2em;
+ text-align: justify;
+}
+
+#g-user-cloud ul li {
+ display: inline;
+ line-height: 1.5em;
+ text-align: justify;
+}
+
+#g-user-cloud ul li a {
+ text-decoration: none;
+}
+
+#g-user-cloud ul li span {
+ display: none;
+}
+
+#g-user-cloud ul li.size1 a {
+ color: #9cf;
+ font-size: 80%;
+ font-weight: 100;
+}
+
+#g-user-cloud ul li.size2 a {
+ color: #69f;
+ font-size: 90%;
+ font-weight: 300;
+}
+
+#g-user-cloud ul li.size3 a {
+ color: #69c;
+ font-size: 100%;
+ font-weight: 500;
+}
+
+#g-user-cloud ul li.size4 a {
+ color: #369;
+ font-size: 110%;
+ font-weight: 700;
+}
+
+#g-user-cloud ul li.size5 a {
+ color: #0e2b52;
+ font-size: 120%;
+ font-weight: 900;
+}
+
+#g-user-cloud ul li.size6 a {
+ color: #0e2b52;
+ font-size: 130%;
+ font-weight: 900;
+}
+
+#g-user-cloud ul li.size7 a {
+ color: #0e2b52;
+ font-size: 140%;
+ font-weight: 900;
+}
+
+#g-user-cloud ul li a:hover {
+ color: #f30;
+ text-decoration: underline;
+}
+
+.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 {
+ position: absolute;
+ cursor: default;
+}
+.image-annotate-area div {
+ display: block;
+}
+.image-annotate-area-editable {
+ cursor: pointer;
+}
+.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;
+ direction: ltr !important;
+}
+#image-annotate-edit-form {
+ background: #FFFFFF none repeat scroll 0 0;
+ border: 1px solid #000000;
+ padding: 7px;
+ position: absolute;
+ width: 250px;
+}
+.image-annotate-rtl form {
+ text-align: right !important;
+}
+#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;
+ resize: none;
+}
+#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-rtl a {
+ float: right !important;
+ margin: 3px 0 3px 6px !important;
+}
+.inmage-annotate-dialog button{
+ float: left !important;
+}
+.inmage-annotate-dialog-rtl button{
+ float: left !important;
+}
+.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;
+}
+.photoannotation-edit-button {
+ background-image: url('../images/edit.png');
+ cursor: pointer;
+}
diff --git a/modules/photoannotation/helpers/photoannotation.php b/modules/photoannotation/helpers/photoannotation.php
new file mode 100644
index 00000000..749f7778
--- /dev/null
+++ b/modules/photoannotation/helpers/photoannotation.php
@@ -0,0 +1,275 @@
+<?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_Core {
+ static function search_user($q, $page_size, $offset) {
+ $db = Database::instance();
+ $q = trim($q, "*");
+ $q = $db->escape($q) ."*";
+ if ($q == "*") {
+ $users = ORM::factory("user");
+ $count = $users->count_all();
+ $data = $users->order_by("name", "ASC")->find_all($page_size, $offset);
+ return array($count, $data);
+ } else {
+ $query =
+ "SELECT SQL_CALC_FOUND_ROWS {users}.*, " .
+ " MATCH({users}.`name`) AGAINST ('$q' IN BOOLEAN MODE) AS `score` " .
+ "FROM {users} " .
+ "WHERE MATCH({users}.`name`, {users}.`full_name`) AGAINST ('$q' IN BOOLEAN MODE) " .
+ "ORDER BY `score` DESC " .
+ "LIMIT $page_size OFFSET $offset";
+ $data = $db->query($query);
+ $count = $db->query("SELECT FOUND_ROWS() as c")->current()->c;
+ return array($count, new ORM_Iterator(ORM::factory("user"), $data));
+ }
+ }
+
+ static function get_user_search_form($form_id) {
+ $form = new Forge("photoannotation/showuser", "", "post", array("id" => $form_id, "class" => "g-short-form"));
+ $label = t("Search for a person");
+
+ $group = $form->group("showuser")->label("Search for a person");
+ $group->input("name")->label($label)->id("name");
+ $group->submit("")->value(t("Search"));
+ return $form;
+ }
+
+ public static function getuser($user_string) {
+ $user_parts = explode("(", $user_string);
+ $user_part = rtrim(ltrim(end($user_parts)), ")");
+ $user = ORM::factory("user")->where("name", "=", $user_part)->find();
+ $user_firstpart = trim(implode(array_slice($user_parts, 0, count($user_parts)-1)));
+ if (!$user->loaded() || strcasecmp($user_firstpart, $user->display_name()) <> 0) {
+ $result->found = false;
+ $result->isguest = false;
+ $result->user = "";
+ return $result;
+ }
+ if (identity::guest()->id == $user->id) {
+ $result->found = true;
+ $result->isguest = true;
+ $result->user = "";
+ return $result;
+ }
+ $result->found = true;
+ $result->isguest = false;
+ $result->user = $user;
+ return $result;
+ }
+
+ public static function saveuser($user_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description) {
+ //Since we are associating a user we will remove any old annotation of this user on this photo
+ $item_old_users = ORM::factory("items_user")
+ ->where("user_id", "=", $user_id)
+ ->where("item_id", "=", $item_id)
+ ->find_all();
+ if (count($item_old_users) > 1) {
+ foreach ($item_old_users as $item_old_user) {
+ $item_old_user->delete();
+ }
+ $item_user = ORM::factory("items_user");
+ } elseif (count($item_old_users) == 1) {
+ $item_user = ORM::factory("items_user", $item_old_users[0]->id);
+ } else {
+ $item_user = ORM::factory("items_user");
+ photoannotation::send_notifications($user_id, $item_id, "newtag");
+ }
+ $item_user->user_id = $user_id;
+ $item_user->item_id = $item_id;
+ $item_user->x1 = $str_x1;
+ $item_user->y1 = $str_y1;
+ $item_user->x2 = $str_x2;
+ $item_user->y2 = $str_y2;
+ $item_user->description = $description;
+ $item_user->save();
+ }
+
+ public static function saveface($tag_id, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description, $annotate_id = "") {
+ if ($annotate_id == "") {
+ $item_face = ORM::factory("items_face");
+ } else {
+ $item_face = ORM::factory("items_face")
+ ->where("id", "=", $annotate_id)
+ ->find();
+ }
+ $item_face->tag_id = $tag_id;
+ $item_face->item_id = $item_id;
+ $item_face->x1 = $str_x1;
+ $item_face->y1 = $str_y1;
+ $item_face->x2 = $str_x2;
+ $item_face->y2 = $str_y2;
+ $item_face->description = $description;
+ $item_face->save();
+ }
+
+ public static function savenote($item_title, $item_id, $str_x1, $str_y1, $str_x2, $str_y2, $description, $annotate_id = "") {
+ if ($annotate_id == "") {
+ $item_note = ORM::factory("items_note");
+ } else {
+ $item_note = ORM::factory("items_note")
+ ->where("id", "=", $annotate_id)
+ ->find();
+ }
+ $item_note->item_id = $item_id;
+ $item_note->x1 = $str_x1;
+ $item_note->y1 = $str_y1;
+ $item_note->x2 = $str_x2;
+ $item_note->y2 = $str_y2;
+ $item_note->title = $item_title;
+ $item_note->description = $description;
+ $item_note->save();
+ }
+
+ public static function send_notifications($recipient_id, $item_id, $mailtype) {
+ //Load the item
+ $item = ORM::factory("item")->where("id", "=", $item_id)->find();
+ if (!$item->loaded()) {
+ return false;
+ }
+ //Load the user
+ $recipient = ORM::factory("user")->where("id", "=", $recipient_id)->find();
+ if (!$recipient->loaded()) {
+ return false;
+ }
+ //Only send mail if the notifications are switched on globally
+ if (!module::get_var("photoannotation", "nonotifications", false)) {
+ //Check if the use has a valid e-mail
+ if (!valid::email($recipient->email)) {
+ return false;
+ }
+ //Get the users settings
+ $notification_settings = self::get_user_notification_settings($recipient);
+ //Check which type of mail to send
+ switch ($mailtype) {
+ case "newtag":
+ //Only send if user has this option enabled
+ if ($notification_settings->newtag) {
+ //Get subject and body and send the mail
+ $subject = module::get_var("photoannotation", "newtagsubject", "Someone tagged a photo of you");
+ $body = module::get_var("photoannotation", "newtagbody", "Hello %name, please visit %url to view the photo.");
+ $body = str_ireplace(array("%url", "%name"), array($item->abs_url(), $recipient->display_name()), $body);
+ return self::_send_mail($recipient->email, $subject, $body);
+ }
+ break;
+ case "newcomment":
+ //Only send if user has this option enabled
+ if ($notification_settings->comment) {
+ //Don't send if the notification module is active and the user is watching this item
+ if (module::is_active("notification")) {
+ if (notification::is_watching($item, $recipient)) {
+ return false;
+ }
+ }
+ //Get subject and body and send the mail
+ $subject = module::get_var("photoannotation", "newcommentsubject", "Someone added a comment to photo of you");
+ $body = module::get_var("photoannotation", "newcommentbody", "Hello %name, please visit %url to read the comment.");
+ $body = str_ireplace(array("%url", "%name"), array($item->abs_url(), $recipient->display_name()), $body);
+ return self::_send_mail($recipient->email, $subject, $body);
+ }
+ break;
+ case "updatecomment":
+ //Only send if user has this option enabled
+ if ($notification_settings->comment) {
+ //Don't send if the notification module is active and the user is watching this item
+ if (module::is_active("notification")) {
+ if (notification::is_watching($item, $recipient)) {
+ return false;
+ }
+ }
+ //Get subject and body and send the mail
+ $subject = module::get_var("photoannotation", "updatedcommentsubject", "Someone updated a comment to photo of you");
+ $body = module::get_var("photoannotation", "updatedcommentbody", "Hello %name, please visit %url to read the comment.");
+ $body = str_ireplace(array("%url", "%name"), array($item->abs_url(), $recipient->display_name()), $body);
+ return self::_send_mail($recipient->email, $subject, $body);
+ }
+ }
+ }
+ return false;
+ }
+
+ private static function _send_mail($mailto, $subject, $message) {
+ //Send the notification mail
+ $message = nl2br($message);
+ return Sendmail::factory()
+ ->to($mailto)
+ ->subject($subject)
+ ->header("Mime-Version", "1.0")
+ ->header("Content-type", "text/html; charset=utf-8")
+ ->message($message)
+ ->send();
+ }
+
+ public static function get_user_notification_settings($user) {
+ //Try loading the notification settings of user
+ $notification_settings = ORM::factory("photoannotation_notification")->where("user_id", "=", $user->id)->find();
+ if (!$notification_settings->loaded()) {
+ //If the user did not save his settings use the website default
+ $notify = module::get_var("photoannotation", "notificationoptout", false);
+ $notification_settings = ORM::factory("photoannotation_notification");
+ $notification_settings->user_id = $user->id;
+ $notification_settings->newtag = $notify;
+ $notification_settings->comment = $notify;
+ $notification_settings->save();
+ }
+ return $notification_settings;
+ }
+
+ static function cloud($count) {
+ $users = ORM::factory("user")->order_by("name", "ASC")->find_all();
+ if ($users) {
+ $cloud = new View("photoannotation_cloud.html");
+ $fullname = module::get_var("photoannotation", "fullname", false);
+ foreach ($users as $user) {
+ $annotations = ORM::factory("items_user")->where("user_id", "=", $user->id)->count_all();
+ if ($annotations > 0) {
+ if ($annotations > $maxcount) {
+ $maxcount = $annotations;
+ }
+ if ($fullname) {
+ $user_array[$user->name]->name = $user->display_name();
+ } else {
+ $user_array[$user->name]->name = $user->name;
+ }
+ $user_array[$user->name]->size = $annotations;
+ $user_array[$user->name]->url = user_profile::url($user->id);
+ }
+ }
+ if (isset($user_array)) {
+ $cloud->users = array_slice($user_array, 0, $count);
+ $cloud->max_count = $maxcount;
+ return $cloud;
+ } else {
+ return "";
+ }
+ }
+ }
+
+ static function comment_count($user_id) {
+ if (module::is_active("comment")) {
+ return ORM::factory("comment")->where("author_id", "=", $user_id)->count_all();
+ } else {
+ return false;
+ }
+ }
+
+ static function annotation_count($user_id) {
+ return ORM::factory("items_user")->where("user_id", "=", $user_id)->count_all();
+ }
+}
diff --git a/modules/photoannotation/helpers/photoannotation_block.php b/modules/photoannotation/helpers/photoannotation_block.php
new file mode 100644
index 00000000..d0e89e0a
--- /dev/null
+++ b/modules/photoannotation/helpers/photoannotation_block.php
@@ -0,0 +1,40 @@
+<?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_block_Core {
+ static function get_site_list() {
+ return array("photoannotation" => t("Users"));
+ }
+
+ static function get($block_id, $theme) {
+ $block = "";
+ if (!identity::active_user()->guest || module::get_var("photoannotation", "allowguestsearch", false)) {
+ switch ($block_id) {
+ case "photoannotation":
+ $block = new Block();
+ $block->css_id = "g-photoannotation";
+ $block->title = t("People");
+ $block->content = new View("photoannotation_block.html");
+ $block->content->cloud = photoannotation::cloud(30);
+ $block->content->form = photoannotation::get_user_search_form("g-user-cloud-form");
+ }
+ }
+ return $block;
+ }
+}
diff --git a/modules/photoannotation/helpers/photoannotation_event.php b/modules/photoannotation/helpers/photoannotation_event.php
new file mode 100644
index 00000000..a7836052
--- /dev/null
+++ b/modules/photoannotation/helpers/photoannotation_event.php
@@ -0,0 +1,248 @@
+<?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("#"));
+ }
+ }
+ }
+
+ 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();
+ }
+
+ $existingUsers = ORM::factory("items_user")
+ ->where("item_id", "=", $item->id)
+ ->find_all();
+ if (count($existingUsers) > 0) {
+ db::build()->delete("items_users")->where("item_id", "=", $item->id)->execute();
+ }
+ }
+
+ static function user_deleted($old) {
+ // Check for and delete existing Annotations linked to that user.
+ $existingFaces = ORM::factory("items_user")
+ ->where("user_id", "=", $old->id)
+ ->find_all();
+ if (count($existingFaces) > 0) {
+ $onuserdelete = module::get_var("photoannotation", "onuserdelete", "0");
+ if (module::get_var("photoannotation", "fullname", false)) {
+ $new_tag_name = $old->display_name();
+ } else {
+ $new_tag_name = $old->name;
+ }
+ switch ($onuserdelete) {
+ case "1":
+ //convert to tag
+ $tag = ORM::factory("tag")->where("name", "=", $new_tag_name)->find();
+ if (!$tag->loaded()) {
+ $tag->name = $new_tag_name;
+ $tag->count = 0;
+ }
+ foreach ($existingFaces as $existingFace) {
+ $item = ORM::factory("item")->where("id", "=", $existingFace->item_id)->find();
+ $tag->add($item);
+ $tag->count++;
+ $tag->save();
+ photoannotation::saveface($tag->id, $existingFace->item_id, $existingFace->x1, $existingFace->y1, $existingFace->x2, $existingFace->y2, $existingFace->description);
+ }
+ break;
+ case "2":
+ //convert to note
+ foreach ($existingFaces as $existingFace) {
+ photoannotation::savenote($new_tag_name, $existingFace->item_id, $existingFace->x1, $existingFace->y1, $existingFace->x2, $existingFace->y2, $existingFace->description);
+ }
+ }
+ db::build()->delete("items_users")->where("user_id", "=", $old->id)->execute();
+ }
+ // Delete notification settings
+ $notification_settings = ORM::factory("photoannotation_notification")
+ ->where("user_id", "=", $old->id)
+ ->find();
+ if ($notification_settings->loaded()) {
+ $notification_settings->delete();
+ }
+ }
+
+ static function user_created($user) {
+ // Write notification settings
+ $notify = module::get_var("photoannotation", "notificationoptout", false);
+ $notification_settings = ORM::factory("photoannotation_notification");
+ $notification_settings->user_id = $user->id;
+ $notification_settings->newtag = $notify;
+ $notification_settings->comment = $notify;
+ $notification_settings->save();
+ }
+
+ static function admin_menu($menu, $theme) {
+ $menu->get("settings_menu")
+ ->append(Menu::factory("link")
+ ->id("photoannotation_menu")
+ ->label(t("Photo Annotation"))
+ ->url(url::site("admin/photoannotation")));
+ }
+
+ static function show_user_profile($data) {
+ $view = new Theme_View("dynamic.html", "collection", "userprofiles");
+ //load thumbs
+ $item_users = ORM::factory("items_user")->where("user_id", "=", $data->user->id)->find_all();
+ foreach ($item_users as $item_user) {
+ $item_thumb = ORM::factory("item")
+ ->viewable()
+ ->where("type", "!=", "album")
+ ->where("id", "=", $item_user->item_id)
+ ->find();
+ if ($item_thumb->loaded()) {
+ $item_thumbs[] = $item_thumb;
+ }
+ }
+ $children_count = count($item_thumbs);
+ $page_size = module::get_var("gallery", "page_size", 9);
+ $page = (int) Input::instance()->get("page", "1");
+ $offset = ($page-1) * $page_size;
+ $max_pages = max(ceil($children_count / $page_size), 1);
+
+ // Make sure that the page references a valid offset
+ if ($page < 1) {
+ url::redirect($album->abs_url());
+ } else if ($page > $max_pages) {
+ url::redirect($album->abs_url("page=$max_pages"));
+ }
+ if ($page < $max_pages) {
+ $next_page_url = url::site("user_profile/show/". $data->user->id ."?page=". ($page + 1));
+ $view->set_global("next_page_url", $next_page_url);
+ $view->set_global("first_page_url", url::site("user_profile/show/". $data->user->id ."?page=". $max_pages));
+ }
+
+ if ($page > 1) {
+ $view->set_global("previous_page_url", url::site("user_profile/show/". $data->user->id ."?page=". ($page - 1)));
+ $view->set_global("first_page_url", url::site("user_profile/show/". $data->user->id ."?page=1"));
+ }
+ $view->set_global("page", $page);
+ $view->set_global("max_pages", $max_pages);
+ $view->set_global("page_size", $page_size);
+ $view->set_global("children", array_slice($item_thumbs, $offset, $page_size));;
+ $view->set_global("children_count", $children_count);
+ $view->set_global("total", $max_pages);
+ $view->set_global("position", t("Page") ." ". $page);
+ if ($children_count > 0) {
+ $data->content[] = (object)array("title" => t("Photos"), "view" => $view);
+ }
+ }
+
+ static function user_edit_form($user, $form) {
+ // Allow users to modify notification settings.
+ if (!module::get_var("photoannotation", "nonotifications", false)) {
+ $notification_settings = photoannotation::get_user_notification_settings($user);
+ $user_notification = $form->edit_user->group("edit_notification")->label("Tag notifications");
+ $user_notification->checkbox("photoannotation_newtag")->label(t("Notify me when a tag is added to a photo with me"))
+ ->checked($notification_settings->newtag);
+ $user_notification->checkbox("photoannotation_comment")->label(t("Notify me if someone comments on a photo with me on it"))
+ ->checked($notification_settings->comment);
+ }
+ }
+
+ static function user_edit_form_completed($user, $form) {
+ // Save notification settings.
+ if (!module::get_var("photoannotation", "nonotifications", false)) {
+ $notification_settings = ORM::factory("photoannotation_notification")->where("user_id", "=", $user->id)->find();
+ if (!$notification_settings->loaded()) {
+ $notification_settings = ORM::factory("photoannotation_notification");
+ $notification_settings->user_id = $user->id;
+ }
+ $notification_settings->newtag = $form->edit_user->edit_notification->photoannotation_newtag->value;
+ $notification_settings->comment = $form->edit_user->edit_notification->photoannotation_comment->value;
+ $notification_settings->save();
+ }
+ }
+
+ static function comment_created($comment) {
+ //Check if there are any user annotations on the photo and send notification if applicable
+ $item_users = ORM::factory("items_user")->where("item_id", "=", $comment->item_id)->find_all();
+ if (count($item_users) > 0) {
+ foreach ($item_users as $item_user) {
+ //Don't send if the commenter is the user to be notified
+ if ($comment->author_id != $item_user->user_id && module::is_active("notification")) {
+ photoannotation::send_notifications($item_user->user_id, $comment->item_id, "newcomment");
+ }
+ }
+ }
+ }
+
+ static function comment_updated($comment) {
+ //Check if there are any user annotations on the photo and send notification if applicable
+ $item_users = ORM::factory("items_user")->where("item_id", "=", $comment->item_id)->find_all();
+ if (count($item_users) > 0) {
+ foreach ($item_users as $item_user) {
+ //Don't send if the commenter is the user to be notified
+ if ($comment->author_id != $item_user->user_id && module::is_active("notification")) {
+ photoannotation::send_notifications($item_user->user_id, $comment->item_id, "updatedcomment");
+ }
+ }
+ }
+ }
+}
diff --git a/modules/photoannotation/helpers/photoannotation_installer.php b/modules/photoannotation/helpers/photoannotation_installer.php
new file mode 100644
index 00000000..a0a881a9
--- /dev/null
+++ b/modules/photoannotation/helpers/photoannotation_installer.php
@@ -0,0 +1,119 @@
+<?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;");
+
+ $db->query("CREATE TABLE IF NOT EXISTS {items_users} (
+ `id` int(9) NOT NULL auto_increment,
+ `user_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 {photoannotation_notifications} (
+ `id` int(9) NOT NULL auto_increment,
+ `user_id` int(9) NOT NULL unique,
+ `newtag` int(2) default NULL,
+ `comment` int(2) default NULL,
+ PRIMARY KEY (`id`))
+ DEFAULT CHARSET=utf8;");
+
+ // Set the module's version number.
+ module::set_version("photoannotation", 4);
+ }
+
+ static function upgrade($version) {
+ if ($version == 1) {
+ module::set_version("photoannotation", $version = 2);
+ }
+ if ($version == 2) {
+ $db = Database::instance();
+ $db->query("CREATE TABLE IF NOT EXISTS {items_users} (
+ `id` int(9) NOT NULL auto_increment,
+ `user_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;");
+ module::set_version("photoannotation", $version = 3);
+ }
+ if ($version == 3) {
+ $db = Database::instance();
+ $db->query("CREATE TABLE IF NOT EXISTS {photoannotation_notifications} (
+ `id` int(9) NOT NULL auto_increment,
+ `user_id` int(9) NOT NULL unique,
+ `newtag` int(2) default NULL,
+ `comment` int(2) default NULL,
+ PRIMARY KEY (`id`))
+ DEFAULT CHARSET=utf8;");
+ module::set_version("photoannotation", $version = 4);
+ }
+ }
+
+ 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};");
+ $db->query("DROP TABLE IF EXISTS {items_users};");
+ $db->query("DROP TABLE IF EXISTS {photoannotation_notifications};");
+ module::delete("photoannotation");
+ }
+}
diff --git a/modules/photoannotation/helpers/photoannotation_theme.php b/modules/photoannotation/helpers/photoannotation_theme.php
new file mode 100644
index 00000000..15f584f8
--- /dev/null
+++ b/modules/photoannotation/helpers/photoannotation_theme.php
@@ -0,0 +1,81 @@
+<?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) {
+ $theme->css("photoannotation.css");
+ if ($theme->page_subtype == "photo") {
+ $theme->script("jquery.annotate.min.js");
+ $noborder = module::get_var("photoannotation", "noborder", false);
+ $noclickablehover = module::get_var("photoannotation", "noclickablehover", false);
+ $nohover = module::get_var("photoannotation", "nohover", false);
+ $bordercolor = "#". module::get_var("photoannotation", "bordercolor", "000000");
+ $v = "<style type=\"text/css\">\n";
+ $v .= ".photoannotation-del-button {\n
+ border:1px solid ". $bordercolor ." !important;\n
+ }\n";
+ $v .= ".photoannotation-edit-button {\n
+ border:1px solid ". $bordercolor ." !important;\n
+ }";
+ if ($noborder) {
+ $border_thickness = "2px";
+ } else {
+ $border_thickness = "1px";
+ }
+ if (!$noborder || !$noclickablehover || !$nohover) {
+ if (!$noborder) {
+ $v .= ".image-annotate-area {\n
+ border: 1px solid ". $bordercolor .";\n
+ }\n";
+ $v .= ".image-annotate-area div {\n
+ border: 1px solid #FFFFFF;\n
+ }\n";
+ }
+ if (!$noclickablehover) {
+ $clickablehovercolor = "#". module::get_var("photoannotation", "clickablehovercolor", "00AD00");
+ $v .= ".image-annotate-area-editable-hover div {\n
+ border: ". $border_thickness ." solid ". $clickablehovercolor ." !important;\n
+ }\n";
+ }
+ if (!$nohover) {
+ $hovercolor = "#". module::get_var("photoannotation", "hovercolor", "990000");
+ $v .= ".image-annotate-area-hover div {\n
+ border: ". $border_thickness ." solid ". $hovercolor ." !important;\n
+ }\n";
+ }
+ }
+ $v .= "</style>\n";
+ return $v;
+ }
+ }
+
+ static function resize_bottom($theme) {
+ if ($theme->page_subtype == "photo") {
+ return new View("photoannotation_highlight_block.html");
+ }
+ }
+
+ static function admin_head($theme) {
+ if (strpos($theme->content->kohana_filename, "admin_photoannotation.html.php")) {
+ $theme->css("colorpicker.css");
+ $theme->script("jquery.colorpicker.min.js");
+ }
+ }
+
+}
diff --git a/modules/photoannotation/images/blank.gif b/modules/photoannotation/images/blank.gif
new file mode 100644
index 00000000..75b945d2
--- /dev/null
+++ b/modules/photoannotation/images/blank.gif
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_background.png b/modules/photoannotation/images/colorpicker_background.png
new file mode 100644
index 00000000..8401572f
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_background.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_hex.png b/modules/photoannotation/images/colorpicker_hex.png
new file mode 100644
index 00000000..4e532d7c
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_hex.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_hsb_b.png b/modules/photoannotation/images/colorpicker_hsb_b.png
new file mode 100644
index 00000000..dfac595d
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_hsb_b.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_hsb_h.png b/modules/photoannotation/images/colorpicker_hsb_h.png
new file mode 100644
index 00000000..3977ed9f
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_hsb_h.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_hsb_s.png b/modules/photoannotation/images/colorpicker_hsb_s.png
new file mode 100644
index 00000000..a2a69973
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_hsb_s.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_indic.gif b/modules/photoannotation/images/colorpicker_indic.gif
new file mode 100644
index 00000000..f9fa95e2
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_indic.gif
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_overlay.png b/modules/photoannotation/images/colorpicker_overlay.png
new file mode 100644
index 00000000..561cdd9c
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_overlay.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_rgb_b.png b/modules/photoannotation/images/colorpicker_rgb_b.png
new file mode 100644
index 00000000..dfac595d
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_rgb_b.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_rgb_g.png b/modules/photoannotation/images/colorpicker_rgb_g.png
new file mode 100644
index 00000000..72b32760
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_rgb_g.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_rgb_r.png b/modules/photoannotation/images/colorpicker_rgb_r.png
new file mode 100644
index 00000000..4855fe03
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_rgb_r.png
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_select.gif b/modules/photoannotation/images/colorpicker_select.gif
new file mode 100644
index 00000000..599f7f13
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_select.gif
Binary files differ
diff --git a/modules/photoannotation/images/colorpicker_submit.png b/modules/photoannotation/images/colorpicker_submit.png
new file mode 100644
index 00000000..566fe2dd
--- /dev/null
+++ b/modules/photoannotation/images/colorpicker_submit.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_background.png b/modules/photoannotation/images/custom_background.png
new file mode 100644
index 00000000..cf55ffdd
--- /dev/null
+++ b/modules/photoannotation/images/custom_background.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_hex.png b/modules/photoannotation/images/custom_hex.png
new file mode 100644
index 00000000..888f4444
--- /dev/null
+++ b/modules/photoannotation/images/custom_hex.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_hsb_b.png b/modules/photoannotation/images/custom_hsb_b.png
new file mode 100644
index 00000000..2f99dae8
--- /dev/null
+++ b/modules/photoannotation/images/custom_hsb_b.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_hsb_h.png b/modules/photoannotation/images/custom_hsb_h.png
new file mode 100644
index 00000000..a217e921
--- /dev/null
+++ b/modules/photoannotation/images/custom_hsb_h.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_hsb_s.png b/modules/photoannotation/images/custom_hsb_s.png
new file mode 100644
index 00000000..7826b415
--- /dev/null
+++ b/modules/photoannotation/images/custom_hsb_s.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_indic.gif b/modules/photoannotation/images/custom_indic.gif
new file mode 100644
index 00000000..222fb94c
--- /dev/null
+++ b/modules/photoannotation/images/custom_indic.gif
Binary files differ
diff --git a/modules/photoannotation/images/custom_rgb_b.png b/modules/photoannotation/images/custom_rgb_b.png
new file mode 100644
index 00000000..80764e5d
--- /dev/null
+++ b/modules/photoannotation/images/custom_rgb_b.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_rgb_g.png b/modules/photoannotation/images/custom_rgb_g.png
new file mode 100644
index 00000000..fc9778be
--- /dev/null
+++ b/modules/photoannotation/images/custom_rgb_g.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_rgb_r.png b/modules/photoannotation/images/custom_rgb_r.png
new file mode 100644
index 00000000..91b0cd4c
--- /dev/null
+++ b/modules/photoannotation/images/custom_rgb_r.png
Binary files differ
diff --git a/modules/photoannotation/images/custom_submit.png b/modules/photoannotation/images/custom_submit.png
new file mode 100644
index 00000000..cd202cd9
--- /dev/null
+++ b/modules/photoannotation/images/custom_submit.png
Binary files differ
diff --git a/modules/photoannotation/images/delete.png b/modules/photoannotation/images/delete.png
new file mode 100644
index 00000000..13eccb1c
--- /dev/null
+++ b/modules/photoannotation/images/delete.png
Binary files differ
diff --git a/modules/photoannotation/images/edit.png b/modules/photoannotation/images/edit.png
new file mode 100644
index 00000000..6dbcb679
--- /dev/null
+++ b/modules/photoannotation/images/edit.png
Binary files differ
diff --git a/modules/photoannotation/images/select.png b/modules/photoannotation/images/select.png
new file mode 100644
index 00000000..21213bfd
--- /dev/null
+++ b/modules/photoannotation/images/select.png
Binary files differ
diff --git a/modules/photoannotation/images/select2.png b/modules/photoannotation/images/select2.png
new file mode 100644
index 00000000..2cd2cabe
--- /dev/null
+++ b/modules/photoannotation/images/select2.png
Binary files differ
diff --git a/modules/photoannotation/images/slider.png b/modules/photoannotation/images/slider.png
new file mode 100644
index 00000000..8b03da96
--- /dev/null
+++ b/modules/photoannotation/images/slider.png
Binary files differ
diff --git a/modules/photoannotation/js/jquery.annotate.js b/modules/photoannotation/js/jquery.annotate.js
new file mode 100644
index 00000000..8310aad8
--- /dev/null
+++ b/modules/photoannotation/js/jquery.annotate.js
@@ -0,0 +1,568 @@
+/// <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.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;
+ this.cssaclass = opts.cssaclass;
+ this.rtlsupport = opts.rtlsupport;
+ this.users = opts.users;
+
+ // 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, this.tags, this.saveUrl, this.cssaclass, this.rtlsupport, this.users);
+ }
+
+ // 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.csrf, opts.rtlsupport, opts.users);
+ });
+ //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, tags, saveUrl, cssaclass, rtlsupport, users) {
+ /// <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], tags, labels, editable, csrf, deleteUrl, saveUrl, cssaclass, rtlsupport, users);
+ }
+ };
+
+ $.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, csrf, rtlsupport, users) {
+ /// <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, csrf, rtlsupport, users);
+
+ $.fn.annotateImage.createSaveButton(editable, image, null, rtlsupport, labels);
+ $.fn.annotateImage.createCancelButton(editable, image, rtlsupport, labels);
+ }
+ };
+
+ $.fn.annotateImage.createSaveButton = function(editable, image, note, rtlsupport, labels) {
+ /// <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 ' + rtlsupport + '">' + labels[8] + '</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, rtlsupport, labels) {
+ /// <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 ' + rtlsupport + '">' + labels[9] + '</a>');
+ cancel.click(function() {
+ editable.destroy();
+ image.mode = 'view';
+ location.reload();
+ });
+ 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, csrf, rtlsupport, users) {
+ /// <summary>
+ /// Defines an editable annotation area.
+ /// </summary>
+ this.image = image;
+
+ if (note) {
+ this.note = note;
+ } else {
+ var newNote = new Object();
+ newNote.noteid = "new";
+ newNote.top = 30;
+ newNote.left = 30;
+ newNote.width = 60;
+ newNote.height = 60;
+ newNote.text = "";
+ newNote.description = "";
+ newNote.notetype = "";
+ newNote.internaltext = "";
+ 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 selectedtag = "";
+ var notetitle = "";
+ var username = "";
+ var selecteduser = "";
+ if (this.note.notetype == "face") {
+ selectedtag = this.note.text;
+ } else if (this.note.notetype == "user") {
+ username = this.note.internaltext;
+ } else {
+ notetitle = this.note.text;
+ }
+ var form = $('<div id="image-annotate-edit-form" class="ui-dialog-content ui-widget-content ' + rtlsupport + '"><form id="photoannotation-form" action="' + saveUrl + '" method="post"><input type="hidden" name="csrf" value="' + csrf + '" /><input type="hidden" name="noteid" value="' + this.note.noteid + '" /><input type="hidden" name="notetype" value="' + this.note.notetype + '" /><fieldset><legend>' + labels[12] + '</legend><label for="photoannotation-user-list">' + labels[10] + '</label><input id="photoannotation-user-list" class="textbox ui-corner-left ui-corner-right" type="text" name="userlist" style="width: 210px;" value="' + username + '" /><div style="text-align: center"><strong>' + labels[4] + '</strong></div><label for="image-annotate-tag-text">' + labels[0] + '</label><input id="image-annotate-tag-text" class="textbox ui-corner-left ui-corner-right" type="text" name="tagsList" style="width: 210px;" value="' + selectedtag + '" /><div style="text-align: center"><strong>' + labels[4] + '</strong></div><label for="image-annotate-text">' + labels[1] + '</label><input id="image-annotate-text" class="textbox ui-corner-left ui-corner-right" type="text" name="text" style="width: 210px;" value="' + notetitle + '" /></fieldset><fieldset><legend>' + labels[2] + '</legend><textarea id="image-annotate-desc" name="desc" rows="3" style="width: 210px;">' + this.note.description + '</textarea></fieldset></form></div>');
+ this.form = form;
+ $('body').append(this.form);
+ $("#photoannotation-form").ready(function() {
+ var url = tags;
+ $("input#image-annotate-tag-text").autocomplete(
+ url, {
+ max: 30,
+ multiple: false,
+ cacheLength: 1
+ }
+ );
+ var urlusers = users;
+ $("input#photoannotation-user-list").autocomplete(
+ urlusers, {
+ max: 30,
+ multiple: false,
+ cacheLength: 1
+ }
+ );
+ });
+ $("input#image-annotate-tag-text").keyup(function() {
+ if ($("input#image-annotate-tag-text").val() != "") {
+ $("input#image-annotate-text").html("");
+ $("input#image-annotate-text").val("");
+ $("input#photoannotation-user-list").html("");
+ $("input#photoannotation-user-list").val("");
+ }
+ });
+ $("input#image-annotate-text").keyup(function() {
+ if ($("input#image-annotate-text").val() != "") {
+ $("input#image-annotate-tag-text").html("");
+ $("input#image-annotate-tag-text").val("");
+ $("input#photoannotation-user-list").html("");
+ $("input#photoannotation-user-list").val("");
+ }
+ });
+ $("input#photoannotation-user-list").keyup(function() {
+ if ($("select#photoannotation-user-list").val() != "-1") {
+ $("input#image-annotate-tag-text").html("");
+ $("input#image-annotate-tag-text").val("");
+ $("input#image-annotate-text").html("");
+ $("input#image-annotate-text").val("");
+ }
+ });
+ 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',
+
+ start: function(e, ui) {
+ form.hide();
+ },
+ stop: function(e, ui) {
+ form.css('left', area.offset().left + 'px');
+ form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 7) + 'px');
+ form.show();
+ }
+ })
+ .draggable({
+ containment: image.canvas,
+ drag: function(e, ui) {
+ form.hide();
+ },
+ stop: function(e, ui) {
+ form.css('left', area.offset().left + 'px');
+ form.css('top', (parseInt(area.offset().top) + parseInt(area.height()) + 7) + 'px');
+ form.show();
+ }
+ });
+ 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, tags, labels, editable, csrf, deleteUrl, saveUrl, cssaclass, rtlsupport, users) {
+ /// <summary>
+ /// Defines a annotation area.
+ /// </summary>
+ this.image = image;
+
+ this.note = note;
+
+ // Add the area
+ this.area = $('<div id="photoannotation-area-' + this.note.notetype + "-" + this.note.noteid + '" 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><form id="photoannotation-del-' + this.note.noteid + '" class="photoannotation-del-form" method="post" action="' + deleteUrl + '"><input type="hidden" name="notetype" value="' + this.note.notetype + '" /><input type="hidden" name="noteid" value="' + this.note.noteid + '" /><input type="hidden" name="csrf" value="' + csrf + '" /></form></div></div>');
+ this.editarea = $('<div id="photoannotation-edit-' + this.note.noteid + '" class="image-annotate-area photoannotation-edit-button"><div></div></div>');
+ image.canvas.children('.image-annotate-view').prepend(this.delarea);
+ image.canvas.children('.image-annotate-view').prepend(this.editarea);
+ this.delarea.bind('click',function () {
+ var alink = $(cssaclass);
+ alink.unbind();
+ alink.attr ('href', '#');
+ alink.removeAttr ('rel');
+ var confdialog = '<div id="image-annotate-conf-dialog" rel="' + $(this).find('form.photoannotation-del-form').attr('id') + '">' + labels[3] + '<div />';
+ $('body').append(confdialog);
+ var btns = {};
+ if (rtlsupport == "") {
+ diagclass = "inmage-annotate-dialog";
+ } else {
+ diagclass = "inmage-annotate-dialog-rtl";
+ }
+ btns[labels[5]] = function(){ var delform = $(this).attr("rel"); $("form#" + delform).submit(); };
+ btns[labels[6]] = function(){ location.reload(); };
+ $('#image-annotate-conf-dialog').dialog({
+ modal: true,
+ resizable: false,
+ dialogClass: diagclass,
+ title: labels[7],
+ close: function(event, ui) { location.reload(); },
+ buttons: btns
+ });
+ });
+ var form = this;
+ this.editarea.bind('click',function () {
+ var alink = $(cssaclass);
+ alink.unbind();
+ alink.attr ('href', '#');
+ alink.removeAttr ('rel');
+ form.edit(tags, labels, saveUrl, csrf, rtlsupport, users);
+ });
+ this.delarea.hide();
+ this.editarea.hide();
+ }
+
+ // Add the note
+ var notedescription = "";
+ if (note.description != "") {
+ notedescription = "<br />" + note.description;
+ }
+ this.form = $('<div class="image-annotate-note">' + note.text + notedescription + '</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(rtlsupport);
+
+ // 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();
+ annotation.editarea.show();
+ }
+ }, function() {
+ annotation.hide();
+ if (annotation.delarea != undefined) {
+ annotation.delarea.hide();
+ annotation.editarea.hide();
+ }
+ });
+ var legendspan = "#photoannotation-legend-" + this.note.notetype + "-" + this.note.noteid;
+ if ($(legendspan).length > 0) {
+ $(legendspan).hover(function() {
+ var legendsarea = "#photoannotation-area-" + note.notetype + "-" + note.noteid;
+ $(legendsarea).children('.image-annotate-view').show();
+ $(".image-annotate-view").show();
+ $(legendsarea).show();
+ annotation.show();
+ }, function() {
+ var legendsarea = "#photoannotation-area-" + note.notetype + "-" + note.noteid;
+ annotation.hide();
+ $(legendsarea).children('.image-annotate-view').hide();
+ $(".image-annotate-view").hide();
+ });
+ }
+ if (editable) {
+ this.delarea.hover(function() {
+ annotation.delarea.show();
+ annotation.editarea.show();
+ }, function() {
+ annotation.delarea.hide();
+ annotation.editarea.hide();
+ });
+ this.editarea.hover(function() {
+ annotation.delarea.show();
+ annotation.editarea.show();
+ }, function() {
+ annotation.delarea.hide();
+ annotation.editarea.hide();
+ });
+ }
+ // Edit a note feature
+ if (note.url != "" && note.url != null) {
+ this.area.bind('click',function () {
+ var alink = $(cssaclass);
+ alink.unbind();
+ alink.attr ('href', '#');
+ alink.removeAttr ('rel');
+ window.location = note.url;
+ });
+ }
+ };
+
+ $.fn.annotateView.prototype.setPosition = function(rtlsupport) {
+ /// <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('top', (this.note.top) + 'px');
+ this.editarea.children('div').height('14px');
+ this.editarea.children('div').width('14px');
+ if (rtlsupport == '') {
+ this.delarea.css('left', (this.note.left + parseInt(this.note.width)) + 'px');
+ this.editarea.css('left', (this.note.left + parseInt(this.note.width)) + 'px');
+ } else {
+ this.delarea.css('left', (this.note.left - 16) + 'px');
+ this.editarea.css('left', (this.note.left - 16) + 'px');
+ }
+ this.editarea.css('top', (this.note.top + 16) + '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(tags, labels, saveUrl, csrf, rtlsupport, users) {
+ /// <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, tags, labels, saveUrl, csrf, rtlsupport, users);
+ $.fn.annotateImage.createSaveButton(editable, this.image, annotation, rtlsupport, labels);
+ $.fn.annotateImage.createCancelButton(editable, this.image, rtlsupport, labels);
+ }
+ };
+
+ $.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);
diff --git a/modules/photoannotation/js/jquery.annotate.min.js b/modules/photoannotation/js/jquery.annotate.min.js
new file mode 100644
index 00000000..200962f4
--- /dev/null
+++ b/modules/photoannotation/js/jquery.annotate.min.js
@@ -0,0 +1 @@
+(function($){$.fn.annotateImage=function(options){var opts=$.extend({},$.fn.annotateImage.defaults,options);var image=this;this.image=this;this.mode='view';this.getUrl=opts.getUrl;this.saveUrl=opts.saveUrl;this.deleteUrl=opts.deleteUrl;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;this.cssaclass=opts.cssaclass;this.rtlsupport=opts.rtlsupport;this.users=opts.users;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);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());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()});if(this.useAjax){$.fn.annotateImage.ajaxLoad(this)}else{$.fn.annotateImage.load(this,this.labels,this.editable,this.csrf,this.deleteUrl,this.tags,this.saveUrl,this.cssaclass,this.rtlsupport,this.users)}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.csrf,opts.rtlsupport,opts.users)})}this.hide();return this};$.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){for(var i=0;i<image.notes.length;i++){image.notes[image.notes[i]].destroy()}image.notes=new Array()};$.fn.annotateImage.ajaxLoad=function(image){$.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,tags,saveUrl,cssaclass,rtlsupport,users){for(var i=0;i<image.notes.length;i++){image.notes[image.notes[i]]=new $.fn.annotateView(image,image.notes[i],tags,labels,editable,csrf,deleteUrl,saveUrl,cssaclass,rtlsupport,users)}};$.fn.annotateImage.getTicks=function(){var now=new Date();return now.getTime()};$.fn.annotateImage.add=function(image,tags,labels,saveUrl,csrf,rtlsupport,users){if(image.mode=='view'){image.mode='edit';var editable=new $.fn.annotateEdit(image,null,tags,labels,saveUrl,csrf,rtlsupport,users);$.fn.annotateImage.createSaveButton(editable,image,null,rtlsupport,labels);$.fn.annotateImage.createCancelButton(editable,image,rtlsupport,labels)}};$.fn.annotateImage.createSaveButton=function(editable,image,note,rtlsupport,labels){var ok=$('<a class="image-annotate-edit-ok g-button ui-corner-all ui-icon-left ui-state-default '+rtlsupport+'">'+labels[8]+'</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,rtlsupport,labels){var cancel=$('<a class="image-annotate-edit-close g-button ui-corner-all ui-icon-left ui-state-default '+rtlsupport+'">'+labels[9]+'</a>');cancel.click(function(){editable.destroy();image.mode='view';location.reload()});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,csrf,rtlsupport,users){this.image=image;if(note){this.note=note}else{var newNote=new Object();newNote.noteid="new";newNote.top=30;newNote.left=30;newNote.width=60;newNote.height=60;newNote.text="";newNote.description="";newNote.notetype="";newNote.internaltext="";this.note=newNote}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');image.canvas.children('.image-annotate-view').hide();image.canvas.children('.image-annotate-edit').show();var selectedtag="";var notetitle="";var username="";var selecteduser="";if(this.note.notetype=="face"){selectedtag=this.note.text}else if(this.note.notetype=="user"){username=this.note.internaltext}else{notetitle=this.note.text}var form=$('<div id="image-annotate-edit-form" class="ui-dialog-content ui-widget-content '+rtlsupport+'"><form id="photoannotation-form" action="'+saveUrl+'" method="post"><input type="hidden" name="csrf" value="'+csrf+'" /><input type="hidden" name="noteid" value="'+this.note.noteid+'" /><input type="hidden" name="notetype" value="'+this.note.notetype+'" /><fieldset><legend>'+labels[12]+'</legend><label for="photoannotation-user-list">'+labels[10]+'</label><input id="photoannotation-user-list" class="textbox ui-corner-left ui-corner-right" type="text" name="userlist" style="width: 210px;" value="'+username+'" /><div style="text-align: center"><strong>'+labels[4]+'</strong></div><label for="image-annotate-tag-text">'+labels[0]+'</label><input id="image-annotate-tag-text" class="textbox ui-corner-left ui-corner-right" type="text" name="tagsList" style="width: 210px;" value="'+selectedtag+'" /><div style="text-align: center"><strong>'+labels[4]+'</strong></div><label for="image-annotate-text">'+labels[1]+'</label><input id="image-annotate-text" class="textbox ui-corner-left ui-corner-right" type="text" name="text" style="width: 210px;" value="'+notetitle+'" /></fieldset><fieldset><legend>'+labels[2]+'</legend><textarea id="image-annotate-desc" name="desc" rows="3" style="width: 210px;">'+this.note.description+'</textarea></fieldset></form></div>');this.form=form;$('body').append(this.form);$("#photoannotation-form").ready(function(){var url=tags;$("input#image-annotate-tag-text").autocomplete(url,{max:30,multiple:false,cacheLength:1});var urlusers=users;$("input#photoannotation-user-list").autocomplete(urlusers,{max:30,multiple:false,cacheLength:1})});$("input#image-annotate-tag-text").keyup(function(){if($("input#image-annotate-tag-text").val()!=""){$("input#image-annotate-text").html("");$("input#image-annotate-text").val("");$("input#photoannotation-user-list").html("");$("input#photoannotation-user-list").val("")}});$("input#image-annotate-text").keyup(function(){if($("input#image-annotate-text").val()!=""){$("input#image-annotate-tag-text").html("");$("input#image-annotate-tag-text").val("");$("input#photoannotation-user-list").html("");$("input#photoannotation-user-list").val("")}});$("input#photoannotation-user-list").keyup(function(){if($("select#photoannotation-user-list").val()!="-1"){$("input#image-annotate-tag-text").html("");$("input#image-annotate-tag-text").val("");$("input#image-annotate-text").html("");$("input#image-annotate-text").val("")}});this.form.css('left',this.area.offset().left+'px');this.form.css('top',(parseInt(this.area.offset().top)+parseInt(this.area.height())+7)+'px');area.resizable({handles:'all',start:function(e,ui){form.hide()},stop:function(e,ui){form.css('left',area.offset().left+'px');form.css('top',(parseInt(area.offset().top)+parseInt(area.height())+7)+'px');form.show()}}).draggable({containment:image.canvas,drag:function(e,ui){form.hide()},stop:function(e,ui){form.css('left',area.offset().left+'px');form.css('top',(parseInt(area.offset().top)+parseInt(area.height())+7)+'px');form.show()}});return this};$.fn.annotateEdit.prototype.destroy=function(){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,tags,labels,editable,csrf,deleteUrl,saveUrl,cssaclass,rtlsupport,users){this.image=image;this.note=note;this.area=$('<div id="photoannotation-area-'+this.note.notetype+"-"+this.note.noteid+'" 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><form id="photoannotation-del-'+this.note.noteid+'" class="photoannotation-del-form" method="post" action="'+deleteUrl+'"><input type="hidden" name="notetype" value="'+this.note.notetype+'" /><input type="hidden" name="noteid" value="'+this.note.noteid+'" /><input type="hidden" name="csrf" value="'+csrf+'" /></form></div></div>');this.editarea=$('<div id="photoannotation-edit-'+this.note.noteid+'" class="image-annotate-area photoannotation-edit-button"><div></div></div>');image.canvas.children('.image-annotate-view').prepend(this.delarea);image.canvas.children('.image-annotate-view').prepend(this.editarea);this.delarea.bind('click',function(){var alink=$(cssaclass);alink.unbind();alink.attr('href','#');alink.removeAttr('rel');var confdialog='<div id="image-annotate-conf-dialog" rel="'+$(this).find('form.photoannotation-del-form').attr('id')+'">'+labels[3]+'<div />';$('body').append(confdialog);var btns={};if(rtlsupport==""){diagclass="inmage-annotate-dialog"}else{diagclass="inmage-annotate-dialog-rtl"}btns[labels[5]]=function(){var delform=$(this).attr("rel");$("form#"+delform).submit()};btns[labels[6]]=function(){location.reload()};$('#image-annotate-conf-dialog').dialog({modal:true,resizable:false,dialogClass:diagclass,title:labels[7],close:function(event,ui){location.reload()},buttons:btns})});var form=this;this.editarea.bind('click',function(){var alink=$(cssaclass);alink.unbind();alink.attr('href','#');alink.removeAttr('rel');form.edit(tags,labels,saveUrl,csrf,rtlsupport,users)});this.delarea.hide();this.editarea.hide()}var notedescription="";if(note.description!=""){notedescription="<br />"+note.description}this.form=$('<div class="image-annotate-note">'+note.text+notedescription+'</div>');this.form.hide();image.canvas.children('.image-annotate-view').append(this.form);this.form.children('span.actions').hide();this.setPosition(rtlsupport);var annotation=this;this.area.hover(function(){annotation.show();if(annotation.delarea!=undefined){annotation.delarea.show();annotation.editarea.show()}},function(){annotation.hide();if(annotation.delarea!=undefined){annotation.delarea.hide();annotation.editarea.hide()}});var legendspan="#photoannotation-legend-"+this.note.notetype+"-"+this.note.noteid;if($(legendspan).length>0){$(legendspan).hover(function(){var legendsarea="#photoannotation-area-"+note.notetype+"-"+note.noteid;$(legendsarea).children('.image-annotate-view').show();$(".image-annotate-view").show();$(legendsarea).show();annotation.show()},function(){var legendsarea="#photoannotation-area-"+note.notetype+"-"+note.noteid;annotation.hide();$(legendsarea).children('.image-annotate-view').hide();$(".image-annotate-view").hide()})}if(editable){this.delarea.hover(function(){annotation.delarea.show();annotation.editarea.show()},function(){annotation.delarea.hide();annotation.editarea.hide()});this.editarea.hover(function(){annotation.delarea.show();annotation.editarea.show()},function(){annotation.delarea.hide();annotation.editarea.hide()})}if(note.url!=""&&note.url!=null){this.area.bind('click',function(){var alink=$(cssaclass);alink.unbind();alink.attr('href','#');alink.removeAttr('rel');window.location=note.url})}};$.fn.annotateView.prototype.setPosition=function(rtlsupport){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('top',(this.note.top)+'px');this.editarea.children('div').height('14px');this.editarea.children('div').width('14px');if(rtlsupport==''){this.delarea.css('left',(this.note.left+parseInt(this.note.width))+'px');this.editarea.css('left',(this.note.left+parseInt(this.note.width))+'px')}else{this.delarea.css('left',(this.note.left-16)+'px');this.editarea.css('left',(this.note.left-16)+'px')}this.editarea.css('top',(this.note.top+16)+'px')}};$.fn.annotateView.prototype.show=function(){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(){this.form.fadeOut(250);this.area.removeClass('image-annotate-area-hover');this.area.removeClass('image-annotate-area-editable-hover')};$.fn.annotateView.prototype.destroy=function(){this.area.remove();this.form.remove()};$.fn.annotateView.prototype.edit=function(tags,labels,saveUrl,csrf,rtlsupport,users){if(this.image.mode=='view'){this.image.mode='edit';var annotation=this;var editable=new $.fn.annotateEdit(this.image,this.note,tags,labels,saveUrl,csrf,rtlsupport,users);$.fn.annotateImage.createSaveButton(editable,this.image,annotation,rtlsupport,labels);$.fn.annotateImage.createCancelButton(editable,this.image,rtlsupport,labels)}};$.fn.annotateImage.appendPosition=function(form,editable){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){this.form.html(text);this.form.hide();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');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);
diff --git a/modules/photoannotation/js/jquery.colorpicker.js b/modules/photoannotation/js/jquery.colorpicker.js
new file mode 100644
index 00000000..802e7c18
--- /dev/null
+++ b/modules/photoannotation/js/jquery.colorpicker.js
@@ -0,0 +1,484 @@
+/**
+ *
+ * Color picker
+ * Author: Stefan Petre www.eyecon.ro
+ *
+ * Dual licensed under the MIT and GPL licenses
+ *
+ */
+(function ($) {
+ var ColorPicker = function () {
+ var
+ ids = {},
+ inAction,
+ charMin = 65,
+ visible,
+ tpl = '<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div></div>',
+ defaults = {
+ eventName: 'click',
+ onShow: function () {},
+ onBeforeShow: function(){},
+ onHide: function () {},
+ onChange: function () {},
+ onSubmit: function () {},
+ color: 'ff0000',
+ livePreview: true,
+ flat: false
+ },
+ fillRGBFields = function (hsb, cal) {
+ var rgb = HSBToRGB(hsb);
+ $(cal).data('colorpicker').fields
+ .eq(1).val(rgb.r).end()
+ .eq(2).val(rgb.g).end()
+ .eq(3).val(rgb.b).end();
+ },
+ fillHSBFields = function (hsb, cal) {
+ $(cal).data('colorpicker').fields
+ .eq(4).val(hsb.h).end()
+ .eq(5).val(hsb.s).end()
+ .eq(6).val(hsb.b).end();
+ },
+ fillHexFields = function (hsb, cal) {
+ $(cal).data('colorpicker').fields
+ .eq(0).val(HSBToHex(hsb)).end();
+ },
+ setSelector = function (hsb, cal) {
+ $(cal).data('colorpicker').selector.css('backgroundColor', '#' + HSBToHex({h: hsb.h, s: 100, b: 100}));
+ $(cal).data('colorpicker').selectorIndic.css({
+ left: parseInt(150 * hsb.s/100, 10),
+ top: parseInt(150 * (100-hsb.b)/100, 10)
+ });
+ },
+ setHue = function (hsb, cal) {
+ $(cal).data('colorpicker').hue.css('top', parseInt(150 - 150 * hsb.h/360, 10));
+ },
+ setCurrentColor = function (hsb, cal) {
+ $(cal).data('colorpicker').currentColor.css('backgroundColor', '#' + HSBToHex(hsb));
+ },
+ setNewColor = function (hsb, cal) {
+ $(cal).data('colorpicker').newColor.css('backgroundColor', '#' + HSBToHex(hsb));
+ },
+ keyDown = function (ev) {
+ var pressedKey = ev.charCode || ev.keyCode || -1;
+ if ((pressedKey > charMin && pressedKey <= 90) || pressedKey == 32) {
+ return false;
+ }
+ var cal = $(this).parent().parent();
+ if (cal.data('colorpicker').livePreview === true) {
+ change.apply(this);
+ }
+ },
+ change = function (ev) {
+ var cal = $(this).parent().parent(), col;
+ if (this.parentNode.className.indexOf('_hex') > 0) {
+ cal.data('colorpicker').color = col = HexToHSB(fixHex(this.value));
+ } else if (this.parentNode.className.indexOf('_hsb') > 0) {
+ cal.data('colorpicker').color = col = fixHSB({
+ h: parseInt(cal.data('colorpicker').fields.eq(4).val(), 10),
+ s: parseInt(cal.data('colorpicker').fields.eq(5).val(), 10),
+ b: parseInt(cal.data('colorpicker').fields.eq(6).val(), 10)
+ });
+ } else {
+ cal.data('colorpicker').color = col = RGBToHSB(fixRGB({
+ r: parseInt(cal.data('colorpicker').fields.eq(1).val(), 10),
+ g: parseInt(cal.data('colorpicker').fields.eq(2).val(), 10),
+ b: parseInt(cal.data('colorpicker').fields.eq(3).val(), 10)
+ }));
+ }
+ if (ev) {
+ fillRGBFields(col, cal.get(0));
+ fillHexFields(col, cal.get(0));
+ fillHSBFields(col, cal.get(0));
+ }
+ setSelector(col, cal.get(0));
+ setHue(col, cal.get(0));
+ setNewColor(col, cal.get(0));
+ cal.data('colorpicker').onChange.apply(cal, [col, HSBToHex(col), HSBToRGB(col)]);
+ },
+ blur = function (ev) {
+ var cal = $(this).parent().parent();
+ cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus');
+ },
+ focus = function () {
+ charMin = this.parentNode.className.indexOf('_hex') > 0 ? 70 : 65;
+ $(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');
+ $(this).parent().addClass('colorpicker_focus');
+ },
+ downIncrement = function (ev) {
+ var field = $(this).parent().find('input').focus();
+ var current = {
+ el: $(this).parent().addClass('colorpicker_slider'),
+ max: this.parentNode.className.indexOf('_hsb_h') > 0 ? 360 : (this.parentNode.className.indexOf('_hsb') > 0 ? 100 : 255),
+ y: ev.pageY,
+ field: field,
+ val: parseInt(field.val(), 10),
+ preview: $(this).parent().parent().data('colorpicker').livePreview
+ };
+ $(document).bind('mouseup', current, upIncrement);
+ $(document).bind('mousemove', current, moveIncrement);
+ },
+ moveIncrement = function (ev) {
+ ev.data.field.val(Math.max(0, Math.min(ev.data.max, parseInt(ev.data.val + ev.pageY - ev.data.y, 10))));
+ if (ev.data.preview) {
+ change.apply(ev.data.field.get(0), [true]);
+ }
+ return false;
+ },
+ upIncrement = function (ev) {
+ change.apply(ev.data.field.get(0), [true]);
+ ev.data.el.removeClass('colorpicker_slider').find('input').focus();
+ $(document).unbind('mouseup', upIncrement);
+ $(document).unbind('mousemove', moveIncrement);
+ return false;
+ },
+ downHue = function (ev) {
+ var current = {
+ cal: $(this).parent(),
+ y: $(this).offset().top
+ };
+ current.preview = current.cal.data('colorpicker').livePreview;
+ $(document).bind('mouseup', current, upHue);
+ $(document).bind('mousemove', current, moveHue);
+ },
+ moveHue = function (ev) {
+ change.apply(
+ ev.data.cal.data('colorpicker')
+ .fields
+ .eq(4)
+ .val(parseInt(360*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.y))))/150, 10))
+ .get(0),
+ [ev.data.preview]
+ );
+ return false;
+ },
+ upHue = function (ev) {
+ fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+ fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+ $(document).unbind('mouseup', upHue);
+ $(document).unbind('mousemove', moveHue);
+ return false;
+ },
+ downSelector = function (ev) {
+ var current = {
+ cal: $(this).parent(),
+ pos: $(this).offset()
+ };
+ current.preview = current.cal.data('colorpicker').livePreview;
+ $(document).bind('mouseup', current, upSelector);
+ $(document).bind('mousemove', current, moveSelector);
+ },
+ moveSelector = function (ev) {
+ change.apply(
+ ev.data.cal.data('colorpicker')
+ .fields
+ .eq(6)
+ .val(parseInt(100*(150 - Math.max(0,Math.min(150,(ev.pageY - ev.data.pos.top))))/150, 10))
+ .end()
+ .eq(5)
+ .val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX - ev.data.pos.left))))/150, 10))
+ .get(0),
+ [ev.data.preview]
+ );
+ return false;
+ },
+ upSelector = function (ev) {
+ fillRGBFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+ fillHexFields(ev.data.cal.data('colorpicker').color, ev.data.cal.get(0));
+ $(document).unbind('mouseup', upSelector);
+ $(document).unbind('mousemove', moveSelector);
+ return false;
+ },
+ enterSubmit = function (ev) {
+ $(this).addClass('colorpicker_focus');
+ },
+ leaveSubmit = function (ev) {
+ $(this).removeClass('colorpicker_focus');
+ },
+ clickSubmit = function (ev) {
+ var cal = $(this).parent();
+ var col = cal.data('colorpicker').color;
+ cal.data('colorpicker').origColor = col;
+ setCurrentColor(col, cal.get(0));
+ cal.data('colorpicker').onSubmit(col, HSBToHex(col), HSBToRGB(col), cal.data('colorpicker').el);
+ },
+ show = function (ev) {
+ var cal = $('#' + $(this).data('colorpickerId'));
+ cal.data('colorpicker').onBeforeShow.apply(this, [cal.get(0)]);
+ var pos = $(this).offset();
+ var viewPort = getViewport();
+ var top = pos.top + this.offsetHeight;
+ var left = pos.left;
+ if (top + 176 > viewPort.t + viewPort.h) {
+ top -= this.offsetHeight + 176;
+ }
+ if (left + 356 > viewPort.l + viewPort.w) {
+ left -= 356;
+ }
+ cal.css({left: left + 'px', top: top + 'px'});
+ if (cal.data('colorpicker').onShow.apply(this, [cal.get(0)]) != false) {
+ cal.show();
+ }
+ $(document).bind('mousedown', {cal: cal}, hide);
+ return false;
+ },
+ hide = function (ev) {
+ if (!isChildOf(ev.data.cal.get(0), ev.target, ev.data.cal.get(0))) {
+ if (ev.data.cal.data('colorpicker').onHide.apply(this, [ev.data.cal.get(0)]) != false) {
+ ev.data.cal.hide();
+ }
+ $(document).unbind('mousedown', hide);
+ }
+ },
+ isChildOf = function(parentEl, el, container) {
+ if (parentEl == el) {
+ return true;
+ }
+ if (parentEl.contains) {
+ return parentEl.contains(el);
+ }
+ if ( parentEl.compareDocumentPosition ) {
+ return !!(parentEl.compareDocumentPosition(el) & 16);
+ }
+ var prEl = el.parentNode;
+ while(prEl && prEl != container) {
+ if (prEl == parentEl)
+ return true;
+ prEl = prEl.parentNode;
+ }
+ return false;
+ },
+ getViewport = function () {
+ var m = document.compatMode == 'CSS1Compat';
+ return {
+ l : window.pageXOffset || (m ? document.documentElement.scrollLeft : document.body.scrollLeft),
+ t : window.pageYOffset || (m ? document.documentElement.scrollTop : document.body.scrollTop),
+ w : window.innerWidth || (m ? document.documentElement.clientWidth : document.body.clientWidth),
+ h : window.innerHeight || (m ? document.documentElement.clientHeight : document.body.clientHeight)
+ };
+ },
+ fixHSB = function (hsb) {
+ return {
+ h: Math.min(360, Math.max(0, hsb.h)),
+ s: Math.min(100, Math.max(0, hsb.s)),
+ b: Math.min(100, Math.max(0, hsb.b))
+ };
+ },
+ fixRGB = function (rgb) {
+ return {
+ r: Math.min(255, Math.max(0, rgb.r)),
+ g: Math.min(255, Math.max(0, rgb.g)),
+ b: Math.min(255, Math.max(0, rgb.b))
+ };
+ },
+ fixHex = function (hex) {
+ var len = 6 - hex.length;
+ if (len > 0) {
+ var o = [];
+ for (var i=0; i<len; i++) {
+ o.push('0');
+ }
+ o.push(hex);
+ hex = o.join('');
+ }
+ return hex;
+ },
+ HexToRGB = function (hex) {
+ var hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16);
+ return {r: hex >> 16, g: (hex & 0x00FF00) >> 8, b: (hex & 0x0000FF)};
+ },
+ HexToHSB = function (hex) {
+ return RGBToHSB(HexToRGB(hex));
+ },
+ RGBToHSB = function (rgb) {
+ var hsb = {
+ h: 0,
+ s: 0,
+ b: 0
+ };
+ var min = Math.min(rgb.r, rgb.g, rgb.b);
+ var max = Math.max(rgb.r, rgb.g, rgb.b);
+ var delta = max - min;
+ hsb.b = max;
+ if (max != 0) {
+
+ }
+ hsb.s = max != 0 ? 255 * delta / max : 0;
+ if (hsb.s != 0) {
+ if (rgb.r == max) {
+ hsb.h = (rgb.g - rgb.b) / delta;
+ } else if (rgb.g == max) {
+ hsb.h = 2 + (rgb.b - rgb.r) / delta;
+ } else {
+ hsb.h = 4 + (rgb.r - rgb.g) / delta;
+ }
+ } else {
+ hsb.h = -1;
+ }
+ hsb.h *= 60;
+ if (hsb.h < 0) {
+ hsb.h += 360;
+ }
+ hsb.s *= 100/255;
+ hsb.b *= 100/255;
+ return hsb;
+ },
+ HSBToRGB = function (hsb) {
+ var rgb = {};
+ var h = Math.round(hsb.h);
+ var s = Math.round(hsb.s*255/100);
+ var v = Math.round(hsb.b*255/100);
+ if(s == 0) {
+ rgb.r = rgb.g = rgb.b = v;
+ } else {
+ var t1 = v;
+ var t2 = (255-s)*v/255;
+ var t3 = (t1-t2)*(h%60)/60;
+ if(h==360) h = 0;
+ if(h<60) {rgb.r=t1; rgb.b=t2; rgb.g=t2+t3}
+ else if(h<120) {rgb.g=t1; rgb.b=t2; rgb.r=t1-t3}
+ else if(h<180) {rgb.g=t1; rgb.r=t2; rgb.b=t2+t3}
+ else if(h<240) {rgb.b=t1; rgb.r=t2; rgb.g=t1-t3}
+ else if(h<300) {rgb.b=t1; rgb.g=t2; rgb.r=t2+t3}
+ else if(h<360) {rgb.r=t1; rgb.g=t2; rgb.b=t1-t3}
+ else {rgb.r=0; rgb.g=0; rgb.b=0}
+ }
+ return {r:Math.round(rgb.r), g:Math.round(rgb.g), b:Math.round(rgb.b)};
+ },
+ RGBToHex = function (rgb) {
+ var hex = [
+ rgb.r.toString(16),
+ rgb.g.toString(16),
+ rgb.b.toString(16)
+ ];
+ $.each(hex, function (nr, val) {
+ if (val.length == 1) {
+ hex[nr] = '0' + val;
+ }
+ });
+ return hex.join('');
+ },
+ HSBToHex = function (hsb) {
+ return RGBToHex(HSBToRGB(hsb));
+ },
+ restoreOriginal = function () {
+ var cal = $(this).parent();
+ var col = cal.data('colorpicker').origColor;
+ cal.data('colorpicker').color = col;
+ fillRGBFields(col, cal.get(0));
+ fillHexFields(col, cal.get(0));
+ fillHSBFields(col, cal.get(0));
+ setSelector(col, cal.get(0));
+ setHue(col, cal.get(0));
+ setNewColor(col, cal.get(0));
+ };
+ return {
+ init: function (opt) {
+ opt = $.extend({}, defaults, opt||{});
+ if (typeof opt.color == 'string') {
+ opt.color = HexToHSB(opt.color);
+ } else if (opt.color.r != undefined && opt.color.g != undefined && opt.color.b != undefined) {
+ opt.color = RGBToHSB(opt.color);
+ } else if (opt.color.h != undefined && opt.color.s != undefined && opt.color.b != undefined) {
+ opt.color = fixHSB(opt.color);
+ } else {
+ return this;
+ }
+ return this.each(function () {
+ if (!$(this).data('colorpickerId')) {
+ var options = $.extend({}, opt);
+ options.origColor = opt.color;
+ var id = 'collorpicker_' + parseInt(Math.random() * 1000);
+ $(this).data('colorpickerId', id);
+ var cal = $(tpl).attr('id', id);
+ if (options.flat) {
+ cal.appendTo(this).show();
+ } else {
+ cal.appendTo(document.body);
+ }
+ options.fields = cal
+ .find('input')
+ .bind('keyup', keyDown)
+ .bind('change', change)
+ .bind('blur', blur)
+ .bind('focus', focus);
+ cal
+ .find('span').bind('mousedown', downIncrement).end()
+ .find('>div.colorpicker_current_color').bind('click', restoreOriginal);
+ options.selector = cal.find('div.colorpicker_color').bind('mousedown', downSelector);
+ options.selectorIndic = options.selector.find('div div');
+ options.el = this;
+ options.hue = cal.find('div.colorpicker_hue div');
+ cal.find('div.colorpicker_hue').bind('mousedown', downHue);
+ options.newColor = cal.find('div.colorpicker_new_color');
+ options.currentColor = cal.find('div.colorpicker_current_color');
+ cal.data('colorpicker', options);
+ cal.find('div.colorpicker_submit')
+ .bind('mouseenter', enterSubmit)
+ .bind('mouseleave', leaveSubmit)
+ .bind('click', clickSubmit);
+ fillRGBFields(options.color, cal.get(0));
+ fillHSBFields(options.color, cal.get(0));
+ fillHexFields(options.color, cal.get(0));
+ setHue(options.color, cal.get(0));
+ setSelector(options.color, cal.get(0));
+ setCurrentColor(options.color, cal.get(0));
+ setNewColor(options.color, cal.get(0));
+ if (options.flat) {
+ cal.css({
+ position: 'relative',
+ display: 'block'
+ });
+ } else {
+ $(this).bind(options.eventName, show);
+ }
+ }
+ });
+ },
+ showPicker: function() {
+ return this.each( function () {
+ if ($(this).data('colorpickerId')) {
+ show.apply(this);
+ }
+ });
+ },
+ hidePicker: function() {
+ return this.each( function () {
+ if ($(this).data('colorpickerId')) {
+ $('#' + $(this).data('colorpickerId')).hide();
+ }
+ });
+ },
+ setColor: function(col) {
+ if (typeof col == 'string') {
+ col = HexToHSB(col);
+ } else if (col.r != undefined && col.g != undefined && col.b != undefined) {
+ col = RGBToHSB(col);
+ } else if (col.h != undefined && col.s != undefined && col.b != undefined) {
+ col = fixHSB(col);
+ } else {
+ return this;
+ }
+ return this.each(function(){
+ if ($(this).data('colorpickerId')) {
+ var cal = $('#' + $(this).data('colorpickerId'));
+ cal.data('colorpicker').color = col;
+ cal.data('colorpicker').origColor = col;
+ fillRGBFields(col, cal.get(0));
+ fillHSBFields(col, cal.get(0));
+ fillHexFields(col, cal.get(0));
+ setHue(col, cal.get(0));
+ setSelector(col, cal.get(0));
+ setCurrentColor(col, cal.get(0));
+ setNewColor(col, cal.get(0));
+ }
+ });
+ }
+ };
+ }();
+ $.fn.extend({
+ ColorPicker: ColorPicker.init,
+ ColorPickerHide: ColorPicker.hidePicker,
+ ColorPickerShow: ColorPicker.showPicker,
+ ColorPickerSetColor: ColorPicker.setColor
+ });
+})(jQuery);
diff --git a/modules/photoannotation/js/jquery.colorpicker.min.js b/modules/photoannotation/js/jquery.colorpicker.min.js
new file mode 100644
index 00000000..36da9f98
--- /dev/null
+++ b/modules/photoannotation/js/jquery.colorpicker.min.js
@@ -0,0 +1 @@
+(function($){var ColorPicker=function(){var ids={},inAction,charMin=65,visible,tpl='<div class="colorpicker"><div class="colorpicker_color"><div><div></div></div></div><div class="colorpicker_hue"><div></div></div><div class="colorpicker_new_color"></div><div class="colorpicker_current_color"></div><div class="colorpicker_hex"><input type="text" maxlength="6" size="6" /></div><div class="colorpicker_rgb_r colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_g colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_rgb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_h colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_s colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_hsb_b colorpicker_field"><input type="text" maxlength="3" size="3" /><span></span></div><div class="colorpicker_submit"></div></div>',defaults={eventName:'click',onShow:function(){},onBeforeShow:function(){},onHide:function(){},onChange:function(){},onSubmit:function(){},color:'ff0000',livePreview:true,flat:false},fillRGBFields=function(hsb,cal){var rgb=HSBToRGB(hsb);$(cal).data('colorpicker').fields.eq(1).val(rgb.r).end().eq(2).val(rgb.g).end().eq(3).val(rgb.b).end()},fillHSBFields=function(hsb,cal){$(cal).data('colorpicker').fields.eq(4).val(hsb.h).end().eq(5).val(hsb.s).end().eq(6).val(hsb.b).end()},fillHexFields=function(hsb,cal){$(cal).data('colorpicker').fields.eq(0).val(HSBToHex(hsb)).end()},setSelector=function(hsb,cal){$(cal).data('colorpicker').selector.css('backgroundColor','#'+HSBToHex({h:hsb.h,s:100,b:100}));$(cal).data('colorpicker').selectorIndic.css({left:parseInt(150*hsb.s/100,10),top:parseInt(150*(100-hsb.b)/100,10)})},setHue=function(hsb,cal){$(cal).data('colorpicker').hue.css('top',parseInt(150-150*hsb.h/360,10))},setCurrentColor=function(hsb,cal){$(cal).data('colorpicker').currentColor.css('backgroundColor','#'+HSBToHex(hsb))},setNewColor=function(hsb,cal){$(cal).data('colorpicker').newColor.css('backgroundColor','#'+HSBToHex(hsb))},keyDown=function(ev){var pressedKey=ev.charCode||ev.keyCode||-1;if((pressedKey>charMin&&pressedKey<=90)||pressedKey==32){return false}var cal=$(this).parent().parent();if(cal.data('colorpicker').livePreview===true){change.apply(this)}},change=function(ev){var cal=$(this).parent().parent(),col;if(this.parentNode.className.indexOf('_hex')>0){cal.data('colorpicker').color=col=HexToHSB(fixHex(this.value))}else if(this.parentNode.className.indexOf('_hsb')>0){cal.data('colorpicker').color=col=fixHSB({h:parseInt(cal.data('colorpicker').fields.eq(4).val(),10),s:parseInt(cal.data('colorpicker').fields.eq(5).val(),10),b:parseInt(cal.data('colorpicker').fields.eq(6).val(),10)})}else{cal.data('colorpicker').color=col=RGBToHSB(fixRGB({r:parseInt(cal.data('colorpicker').fields.eq(1).val(),10),g:parseInt(cal.data('colorpicker').fields.eq(2).val(),10),b:parseInt(cal.data('colorpicker').fields.eq(3).val(),10)}))}if(ev){fillRGBFields(col,cal.get(0));fillHexFields(col,cal.get(0));fillHSBFields(col,cal.get(0))}setSelector(col,cal.get(0));setHue(col,cal.get(0));setNewColor(col,cal.get(0));cal.data('colorpicker').onChange.apply(cal,[col,HSBToHex(col),HSBToRGB(col)])},blur=function(ev){var cal=$(this).parent().parent();cal.data('colorpicker').fields.parent().removeClass('colorpicker_focus')},focus=function(){charMin=this.parentNode.className.indexOf('_hex')>0?70:65;$(this).parent().parent().data('colorpicker').fields.parent().removeClass('colorpicker_focus');$(this).parent().addClass('colorpicker_focus')},downIncrement=function(ev){var field=$(this).parent().find('input').focus();var current={el:$(this).parent().addClass('colorpicker_slider'),max:this.parentNode.className.indexOf('_hsb_h')>0?360:(this.parentNode.className.indexOf('_hsb')>0?100:255),y:ev.pageY,field:field,val:parseInt(field.val(),10),preview:$(this).parent().parent().data('colorpicker').livePreview};$(document).bind('mouseup',current,upIncrement);$(document).bind('mousemove',current,moveIncrement)},moveIncrement=function(ev){ev.data.field.val(Math.max(0,Math.min(ev.data.max,parseInt(ev.data.val+ev.pageY-ev.data.y,10))));if(ev.data.preview){change.apply(ev.data.field.get(0),[true])}return false},upIncrement=function(ev){change.apply(ev.data.field.get(0),[true]);ev.data.el.removeClass('colorpicker_slider').find('input').focus();$(document).unbind('mouseup',upIncrement);$(document).unbind('mousemove',moveIncrement);return false},downHue=function(ev){var current={cal:$(this).parent(),y:$(this).offset().top};current.preview=current.cal.data('colorpicker').livePreview;$(document).bind('mouseup',current,upHue);$(document).bind('mousemove',current,moveHue)},moveHue=function(ev){change.apply(ev.data.cal.data('colorpicker').fields.eq(4).val(parseInt(360*(150-Math.max(0,Math.min(150,(ev.pageY-ev.data.y))))/150,10)).get(0),[ev.data.preview]);return false},upHue=function(ev){fillRGBFields(ev.data.cal.data('colorpicker').color,ev.data.cal.get(0));fillHexFields(ev.data.cal.data('colorpicker').color,ev.data.cal.get(0));$(document).unbind('mouseup',upHue);$(document).unbind('mousemove',moveHue);return false},downSelector=function(ev){var current={cal:$(this).parent(),pos:$(this).offset()};current.preview=current.cal.data('colorpicker').livePreview;$(document).bind('mouseup',current,upSelector);$(document).bind('mousemove',current,moveSelector)},moveSelector=function(ev){change.apply(ev.data.cal.data('colorpicker').fields.eq(6).val(parseInt(100*(150-Math.max(0,Math.min(150,(ev.pageY-ev.data.pos.top))))/150,10)).end().eq(5).val(parseInt(100*(Math.max(0,Math.min(150,(ev.pageX-ev.data.pos.left))))/150,10)).get(0),[ev.data.preview]);return false},upSelector=function(ev){fillRGBFields(ev.data.cal.data('colorpicker').color,ev.data.cal.get(0));fillHexFields(ev.data.cal.data('colorpicker').color,ev.data.cal.get(0));$(document).unbind('mouseup',upSelector);$(document).unbind('mousemove',moveSelector);return false},enterSubmit=function(ev){$(this).addClass('colorpicker_focus')},leaveSubmit=function(ev){$(this).removeClass('colorpicker_focus')},clickSubmit=function(ev){var cal=$(this).parent();var col=cal.data('colorpicker').color;cal.data('colorpicker').origColor=col;setCurrentColor(col,cal.get(0));cal.data('colorpicker').onSubmit(col,HSBToHex(col),HSBToRGB(col),cal.data('colorpicker').el)},show=function(ev){var cal=$('#'+$(this).data('colorpickerId'));cal.data('colorpicker').onBeforeShow.apply(this,[cal.get(0)]);var pos=$(this).offset();var viewPort=getViewport();var top=pos.top+this.offsetHeight;var left=pos.left;if(top+176>viewPort.t+viewPort.h){top-=this.offsetHeight+176}if(left+356>viewPort.l+viewPort.w){left-=356}cal.css({left:left+'px',top:top+'px'});if(cal.data('colorpicker').onShow.apply(this,[cal.get(0)])!=false){cal.show()}$(document).bind('mousedown',{cal:cal},hide);return false},hide=function(ev){if(!isChildOf(ev.data.cal.get(0),ev.target,ev.data.cal.get(0))){if(ev.data.cal.data('colorpicker').onHide.apply(this,[ev.data.cal.get(0)])!=false){ev.data.cal.hide()}$(document).unbind('mousedown',hide)}},isChildOf=function(parentEl,el,container){if(parentEl==el){return true}if(parentEl.contains){return parentEl.contains(el)}if(parentEl.compareDocumentPosition){return!!(parentEl.compareDocumentPosition(el)&16)}var prEl=el.parentNode;while(prEl&&prEl!=container){if(prEl==parentEl)return true;prEl=prEl.parentNode}return false},getViewport=function(){var m=document.compatMode=='CSS1Compat';return{l:window.pageXOffset||(m?document.documentElement.scrollLeft:document.body.scrollLeft),t:window.pageYOffset||(m?document.documentElement.scrollTop:document.body.scrollTop),w:window.innerWidth||(m?document.documentElement.clientWidth:document.body.clientWidth),h:window.innerHeight||(m?document.documentElement.clientHeight:document.body.clientHeight)}},fixHSB=function(hsb){return{h:Math.min(360,Math.max(0,hsb.h)),s:Math.min(100,Math.max(0,hsb.s)),b:Math.min(100,Math.max(0,hsb.b))}},fixRGB=function(rgb){return{r:Math.min(255,Math.max(0,rgb.r)),g:Math.min(255,Math.max(0,rgb.g)),b:Math.min(255,Math.max(0,rgb.b))}},fixHex=function(hex){var len=6-hex.length;if(len>0){var o=[];for(var i=0;i<len;i++){o.push('0')}o.push(hex);hex=o.join('')}return hex},HexToRGB=function(hex){var hex=parseInt(((hex.indexOf('#')>-1)?hex.substring(1):hex),16);return{r:hex>>16,g:(hex&0x00FF00)>>8,b:(hex&0x0000FF)}},HexToHSB=function(hex){return RGBToHSB(HexToRGB(hex))},RGBToHSB=function(rgb){var hsb={h:0,s:0,b:0};var min=Math.min(rgb.r,rgb.g,rgb.b);var max=Math.max(rgb.r,rgb.g,rgb.b);var delta=max-min;hsb.b=max;if(max!=0){}hsb.s=max!=0?255*delta/max:0;if(hsb.s!=0){if(rgb.r==max){hsb.h=(rgb.g-rgb.b)/delta}else if(rgb.g==max){hsb.h=2+(rgb.b-rgb.r)/delta}else{hsb.h=4+(rgb.r-rgb.g)/delta}}else{hsb.h=-1}hsb.h*=60;if(hsb.h<0){hsb.h+=360}hsb.s*=100/255;hsb.b*=100/255;return hsb},HSBToRGB=function(hsb){var rgb={};var h=Math.round(hsb.h);var s=Math.round(hsb.s*255/100);var v=Math.round(hsb.b*255/100);if(s==0){rgb.r=rgb.g=rgb.b=v}else{var t1=v;var t2=(255-s)*v/255;var t3=(t1-t2)*(h%60)/60;if(h==360)h=0;if(h<60){rgb.r=t1;rgb.b=t2;rgb.g=t2+t3}else if(h<120){rgb.g=t1;rgb.b=t2;rgb.r=t1-t3}else if(h<180){rgb.g=t1;rgb.r=t2;rgb.b=t2+t3}else if(h<240){rgb.b=t1;rgb.r=t2;rgb.g=t1-t3}else if(h<300){rgb.b=t1;rgb.g=t2;rgb.r=t2+t3}else if(h<360){rgb.r=t1;rgb.g=t2;rgb.b=t1-t3}else{rgb.r=0;rgb.g=0;rgb.b=0}}return{r:Math.round(rgb.r),g:Math.round(rgb.g),b:Math.round(rgb.b)}},RGBToHex=function(rgb){var hex=[rgb.r.toString(16),rgb.g.toString(16),rgb.b.toString(16)];$.each(hex,function(nr,val){if(val.length==1){hex[nr]='0'+val}});return hex.join('')},HSBToHex=function(hsb){return RGBToHex(HSBToRGB(hsb))},restoreOriginal=function(){var cal=$(this).parent();var col=cal.data('colorpicker').origColor;cal.data('colorpicker').color=col;fillRGBFields(col,cal.get(0));fillHexFields(col,cal.get(0));fillHSBFields(col,cal.get(0));setSelector(col,cal.get(0));setHue(col,cal.get(0));setNewColor(col,cal.get(0))};return{init:function(opt){opt=$.extend({},defaults,opt||{});if(typeof opt.color=='string'){opt.color=HexToHSB(opt.color)}else if(opt.color.r!=undefined&&opt.color.g!=undefined&&opt.color.b!=undefined){opt.color=RGBToHSB(opt.color)}else if(opt.color.h!=undefined&&opt.color.s!=undefined&&opt.color.b!=undefined){opt.color=fixHSB(opt.color)}else{return this}return this.each(function(){if(!$(this).data('colorpickerId')){var options=$.extend({},opt);options.origColor=opt.color;var id='collorpicker_'+parseInt(Math.random()*1000);$(this).data('colorpickerId',id);var cal=$(tpl).attr('id',id);if(options.flat){cal.appendTo(this).show()}else{cal.appendTo(document.body)}options.fields=cal.find('input').bind('keyup',keyDown).bind('change',change).bind('blur',blur).bind('focus',focus);cal.find('span').bind('mousedown',downIncrement).end().find('>div.colorpicker_current_color').bind('click',restoreOriginal);options.selector=cal.find('div.colorpicker_color').bind('mousedown',downSelector);options.selectorIndic=options.selector.find('div div');options.el=this;options.hue=cal.find('div.colorpicker_hue div');cal.find('div.colorpicker_hue').bind('mousedown',downHue);options.newColor=cal.find('div.colorpicker_new_color');options.currentColor=cal.find('div.colorpicker_current_color');cal.data('colorpicker',options);cal.find('div.colorpicker_submit').bind('mouseenter',enterSubmit).bind('mouseleave',leaveSubmit).bind('click',clickSubmit);fillRGBFields(options.color,cal.get(0));fillHSBFields(options.color,cal.get(0));fillHexFields(options.color,cal.get(0));setHue(options.color,cal.get(0));setSelector(options.color,cal.get(0));setCurrentColor(options.color,cal.get(0));setNewColor(options.color,cal.get(0));if(options.flat){cal.css({position:'relative',display:'block'})}else{$(this).bind(options.eventName,show)}}})},showPicker:function(){return this.each(function(){if($(this).data('colorpickerId')){show.apply(this)}})},hidePicker:function(){return this.each(function(){if($(this).data('colorpickerId')){$('#'+$(this).data('colorpickerId')).hide()}})},setColor:function(col){if(typeof col=='string'){col=HexToHSB(col)}else if(col.r!=undefined&&col.g!=undefined&&col.b!=undefined){col=RGBToHSB(col)}else if(col.h!=undefined&&col.s!=undefined&&col.b!=undefined){col=fixHSB(col)}else{return this}return this.each(function(){if($(this).data('colorpickerId')){var cal=$('#'+$(this).data('colorpickerId'));cal.data('colorpicker').color=col;cal.data('colorpicker').origColor=col;fillRGBFields(col,cal.get(0));fillHSBFields(col,cal.get(0));fillHexFields(col,cal.get(0));setHue(col,cal.get(0));setSelector(col,cal.get(0));setCurrentColor(col,cal.get(0));setNewColor(col,cal.get(0))}})}}}();$.fn.extend({ColorPicker:ColorPicker.init,ColorPickerHide:ColorPicker.hidePicker,ColorPickerShow:ColorPicker.showPicker,ColorPickerSetColor:ColorPicker.setColor})})(jQuery);
diff --git a/modules/photoannotation/models/items_face.php b/modules/photoannotation/models/items_face.php
new file mode 100644
index 00000000..8f3a4072
--- /dev/null
+++ b/modules/photoannotation/models/items_face.php
@@ -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 {
+} \ No newline at end of file
diff --git a/modules/photoannotation/models/items_note.php b/modules/photoannotation/models/items_note.php
new file mode 100644
index 00000000..9202f0f9
--- /dev/null
+++ b/modules/photoannotation/models/items_note.php
@@ -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 {
+} \ No newline at end of file
diff --git a/modules/photoannotation/models/items_user.php b/modules/photoannotation/models/items_user.php
new file mode 100644
index 00000000..22d39a58
--- /dev/null
+++ b/modules/photoannotation/models/items_user.php
@@ -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_User_Model extends ORM {
+} \ No newline at end of file
diff --git a/modules/photoannotation/models/photoannotation_notification.php b/modules/photoannotation/models/photoannotation_notification.php
new file mode 100644
index 00000000..6cb6912a
--- /dev/null
+++ b/modules/photoannotation/models/photoannotation_notification.php
@@ -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 Photoannotation_Notification_Model extends ORM {
+}
diff --git a/modules/photoannotation/module.info b/modules/photoannotation/module.info
new file mode 100644
index 00000000..133c2456
--- /dev/null
+++ b/modules/photoannotation/module.info
@@ -0,0 +1,3 @@
+name = "Photo Annotation"
+description = "Allows you to assign tags and notes to areas on your photos. This module is partially compatible with the TagFaces module by rWatcher. This means that notes and faces that you create in either one will be shown and are editable by the other module as well. If you added users to an annotation area though they will only be displayed with the Photo Annotation module. You cannot run both modules at the same time."
+version = 4
diff --git a/modules/photoannotation/views/admin_photoannotation.html.php b/modules/photoannotation/views/admin_photoannotation.html.php
new file mode 100644
index 00000000..8c059194
--- /dev/null
+++ b/modules/photoannotation/views/admin_photoannotation.html.php
@@ -0,0 +1,30 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-admin-photoannotation">
+ <h1><?= t("Photo annotation administration") ?></h1>
+ <h3><?= t("Notes:") ?></h3>
+ <p><?= t("This module is partially compatible with the <a href=\"%url\">TagFaces module</a> by rWatcher.<br />
+ This means that notes and faces that you create in either one will be shown and are editable by the other module as well. If you added users to an annotation area though they will only be displayed with the Photo Annotation module.<br />
+ You cannot have both active at the same time.", array("tagfaces" => "http://codex.gallery2.org/Gallery3:Modules:tagfaces")) ?>
+ <br /><br /><?= t("<a href=\"%url\">Convert existing tag annotations to user annotations</a>", array("url" => url::site("admin/photoannotation/converter/"))) ?>
+ <br /><?= t("<a href=\"%url\">Check for orphaned annotations</a>", array("url" => url::site("admin/photoannotation/tagsmaintanance/"))) ?></p>
+ <?= $form ?>
+</div>
+<script type="text/javascript">
+ $("input[name='bordercolor'], input[name='clickablehovercolor'], input[name='hovercolor']").ColorPicker({
+ onSubmit: function(hsb, hex, rgb, el) {
+ $(el).val(hex);
+ $(el).ColorPickerHide();
+ },
+ onBeforeShow: function () {
+ $(this).ColorPickerSetColor(this.value);
+ }
+ })
+ .bind('keyup', function(){
+ $(this).ColorPickerSetColor(this.value);
+ });
+ <? if (!module::is_active("comment")): ?>
+ $(document).ready(function(){
+ $("input[name='newcommentsubject'], input[name='updatedcommentsubject'], textarea[name='newcommentbody'], textarea[name='updatedcommentbody']").attr("disabled", true);
+ });
+ <? endif ?>
+</script>
diff --git a/modules/photoannotation/views/admin_photoannotation_converter.html.php b/modules/photoannotation/views/admin_photoannotation_converter.html.php
new file mode 100644
index 00000000..4b8dfa1d
--- /dev/null
+++ b/modules/photoannotation/views/admin_photoannotation_converter.html.php
@@ -0,0 +1,10 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-admin-photoannotation">
+ <h1><?= t("Photo annotation converter") ?></h1>
+ <h3><?= t("Notes:") ?></h3>
+ <p><?= t("Here you can convert existing annotations with tags associated with them to annotations with users.") ?><br /><br />
+ <?= t("Please be aware that if a photo has already the same user associated with an annotation this annotation will be updated instead of a new one being created. If a photo has more than one annotation associated with the specified tag only one area will be converted and all other annotations with this tag will be removed.") ?>
+ <br /><?= t("<a href=\"%url\">Back to photo annotation settings</a>", array("url" => url::site("admin/photoannotation/"))) ?>
+ <br /><?= t("<a href=\"%url\">Check for orphaned annotations</a>", array("url" => url::site("admin/photoannotation/tagsmaintanance/"))) ?></p>
+ <?= $form ?>
+</div>
diff --git a/modules/photoannotation/views/admin_photoannotation_tagsmaintanance.html.php b/modules/photoannotation/views/admin_photoannotation_tagsmaintanance.html.php
new file mode 100644
index 00000000..9e373780
--- /dev/null
+++ b/modules/photoannotation/views/admin_photoannotation_tagsmaintanance.html.php
@@ -0,0 +1,27 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<div id="g-admin-photoannotation">
+ <h1><?= t("Photo annotation tags maintanance") ?></h1>
+ <h3><?= t("Notes:") ?></h3>
+ <p><?= t("When deleting a tag this might leave orphaned tag annotations.") ?>
+ <br /><br /><?= t("<a href=\"%url\">Back to photo annotation settings</a>", array("url" => url::site("admin/photoannotation/"))) ?>
+ <br /><?= t("<a href=\"%url\">Convert existing tag annotations to user annotations</a>", array("url" => url::site("admin/photoannotation/converter/"))) ?></p>
+ <? if ($dodeletion): ?>
+ <h3><?= t("Maintanance results") ?></h3>
+ <p><?= t("%user_orphanes_deleted annotations without a user have been deleted.", array("user_orphanes_deleted" => $user_orphanes_deleted)) ?>
+ <br><?= t("%tag_orpanes_deleted annotations without a tag have been deleted.", array("tag_orpanes_deleted" => $tag_orpanes_deleted)) ?>
+ <br><?= t("%item_orphanes_deleted annotations without a photo have been deleted.", array("item_orphanes_deleted" => $item_orphanes_deleted)) ?>
+ </p>
+ <?= t("<a href=\"%url\" class=\"submit ui-state-default ui-corner-all\" style=\"padding: 5px;\">Back</a>", array("url" => url::site("admin/photoannotation/"))) ?>
+ <? else: ?>
+ <h3><?= t("Orphaned annotations") ?></h3>
+ <p><?= t("%user_orphanes_count annotations without a user found.", array("user_orphanes_count" => $user_orphanes_count)) ?>
+ <br><?= t("%tag_orpanes_count annotations without a tag found.", array("tag_orpanes_count" => $tag_orpanes_count)) ?>
+ <br><?= t("%item_orphanes_count annotations without a photo found.", array("item_orphanes_count" => $item_orphanes_count)) ?>
+ </p>
+ <? if ($user_orphanes_count == 0 && $tag_orpanes_count == 0 && $item_orphanes_count == 0): ?>
+ <?= t("<a href=\"%url\" class=\"submit ui-state-default ui-corner-all\" style=\"padding: 5px;\">Back</a>", array("url" => url::site("admin/photoannotation/"))) ?>
+ <? else: ?>
+ <?= t("<a href=\"%url\" class=\"submit ui-state-default ui-corner-all\" style=\"padding: 5px;\">Remove all</a>", array("url" => url::site("admin/photoannotation/tagsmaintanance/true"))) ?>
+ <? endif ?>
+ <? endif ?>
+</div>
diff --git a/modules/photoannotation/views/photoannotation_block.html.php b/modules/photoannotation/views/photoannotation_block.html.php
new file mode 100644
index 00000000..8861699a
--- /dev/null
+++ b/modules/photoannotation/views/photoannotation_block.html.php
@@ -0,0 +1,17 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $("#g-user-cloud-form").ready(function() {
+ var url = $("#g-user-cloud").attr("ref") + "/autocomplete";
+ $("#g-user-cloud-form input:text").autocomplete(
+ url, {
+ max: 30,
+ multiple: false,
+ cacheLength: 1
+ }
+ );
+ });
+</script>
+<div id="g-user-cloud" ref="<?= url::site("photoannotation") ?>">
+ <?= $cloud ?>
+</div>
+<?= $form ?> \ No newline at end of file
diff --git a/modules/photoannotation/views/photoannotation_cloud.html.php b/modules/photoannotation/views/photoannotation_cloud.html.php
new file mode 100644
index 00000000..6f47b272
--- /dev/null
+++ b/modules/photoannotation/views/photoannotation_cloud.html.php
@@ -0,0 +1,9 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<ul>
+ <? foreach ($users as $user): ?>
+ <li class="size<?=(int)(($user->size / $max_count) * 7) ?>">
+ <span><?= $user->size ?> photos are tagged with </span>
+ <a href="<?= $user->url ?>"><?= html::clean($user->name) ?></a>
+ </li>
+ <? endforeach ?>
+</ul>
diff --git a/modules/photoannotation/views/photoannotation_highlight_block.html.php b/modules/photoannotation/views/photoannotation_highlight_block.html.php
new file mode 100644
index 00000000..814bc600
--- /dev/null
+++ b/modules/photoannotation/views/photoannotation_highlight_block.html.php
@@ -0,0 +1,135 @@
+<?php defined("SYSPATH") or die("No direct script access.");
+ // Check and see if the current photo has any faces or notes associated with it.
+ $existingUsers = ORM::factory("items_user")
+ ->where("item_id", "=", $item->id)
+ ->find_all();
+ $existingFaces = ORM::factory("items_face")
+ ->where("item_id", "=", $item->id)
+ ->find_all();
+ $existingNotes = ORM::factory("items_note")
+ ->where("item_id", "=", $item->id)
+ ->find_all();
+ $fullname = module::get_var("photoannotation", "fullname", false);
+ $showusers = module::get_var("photoannotation", "showusers", false);
+ $showfaces = module::get_var("photoannotation", "showfaces", false);
+ $shownotes = module::get_var("photoannotation", "shownotes", false);
+ if (locales::is_rtl()) {
+ $rtl_support = "image-annotate-rtl";
+ } else {
+ $rtl_support = "";
+ }
+ $tags_arraystring = "";
+ $jscode = "";
+ $legend_faces = "";
+ $legend_notes = "";
+ $legend_users = "";
+ if (module::get_var("gallery", "active_site_theme") == "greydragon") {
+ $css_item_id = "#g-photo-id-". $item->id;
+ $css_a_class = ".g-sb-preview";
+ } else {
+ $css_item_id = "#g-item-id-". $item->id;
+ $css_a_class = ".g-fullsize-link";
+ }
+ // 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) || (count($existingUsers) > 0)) {
+ $jscode = "notes: [ ";
+ foreach ($existingUsers as $oneUser) {
+ $oneTag = ORM::factory("user", $oneUser->user_id);
+ if ($oneTag->loaded()) {
+ if ($fullname) {
+ $user_text = $oneTag->display_name();
+ } else {
+ $user_text = $oneTag->name;
+ }
+ if ($showusers) {
+ $legend_users .= "<span id=\"photoannotation-legend-user-". $oneUser->id . "\"><a href=\"". user_profile::url($oneUser->user_id) ."\">". html::clean($user_text) ."</a></span>, ";
+ }
+ $jscode .= "{ \"top\": ". $oneUser->y1 .",\n";
+ $jscode .= "\"left\": ". $oneUser->x1 .",\n";
+ $jscode .= "\"width\": ". ($oneUser->x2 - $oneUser->x1) .",\n";
+ $jscode .= "\"height\": ". ($oneUser->y2 - $oneUser->y1) .",\n";
+ $jscode .= "\"text\": \"". html::clean($user_text) ."\",\n";
+ $jscode .= "\"internaltext\": \"". $oneTag->display_name() ." (". $oneTag->name .")\",\n";
+ $jscode .= "\"description\": \"". html::clean($oneUser->description) ."\",\n";
+ $jscode .= "\"noteid\": ". $oneUser->id .",\n";
+ $jscode .= "\"notetype\": \"user\",\n";
+ $jscode .= "\"editable\": true,\n";
+ $jscode .= "\"url\": \"". user_profile::url($oneUser->user_id) ."\" },\n";
+ }
+ }
+ if ($legend_users != "") {
+ $legend_users = trim($legend_users, ", ");
+ $legend_users = t("People on this photo: ") . $legend_users ."<br />";
+ }
+ foreach ($existingFaces as $oneFace) {
+ $oneTag = ORM::factory("tag", $oneFace->tag_id);
+ if ($oneTag->loaded()) {
+ if ($showfaces) {
+ $legend_faces .= "<span id=\"photoannotation-legend-face-". $oneFace->id . "\"><a href=\"". $oneTag->url() ."\">". html::clean($oneTag->name) ."</a></span>, ";
+ }
+ $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 .= "\"description\": \"". html::clean($oneFace->description) ."\",\n";
+ $jscode .= "\"noteid\": ". $oneFace->id .",\n";
+ $jscode .= "\"notetype\": \"face\",\n";
+ $jscode .= "\"editable\": true,\n";
+ $jscode .= "\"url\": \"". $oneTag->url() ."\" },\n";
+ }
+ }
+ if ($legend_faces != "") {
+ $legend_faces = trim($legend_faces, ", ");
+ $legend_faces = t("Faces on this photo: ") . $legend_faces ."<br />";
+ }
+ foreach ($existingNotes as $oneNote) {
+ if ($shownotes) {
+ $legend_notes .= "<span id=\"photoannotation-legend-note-". $oneNote->id . "\">". html::clean($oneNote->title) ."</span>, ";
+ }
+ $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) ."\",\n";
+ $jscode .= "\"description\": \"". html::clean($oneNote->description) ."\",\n";
+ $jscode .= "\"noteid\": ". $oneNote->id .",\n";
+ $jscode .= "\"notetype\": \"note\",\n";
+ $jscode .= "\"editable\": false,\n";
+ $jscode .= "\"url\": \"\" },\n";
+ }
+ $jscode = trim($jscode, ",\n");
+ $jscode .= " ],";
+ if ($legend_notes != "") {
+ $legend_notes = trim($legend_notes, ", ");
+ $legend_notes = t("Notes on this photo: ") . $legend_notes ."<br />";
+ }
+ }
+ $legend_display = $legend_users . $legend_faces . $legend_notes;
+ $labels_arraystring = "labels: [ '". t("Tag:") ."','". t("Note Title:") ."','". t("Description (optional)") ."','". t("Are you sure you want to delete this annotation?") ."','". t("or") ."','". t("Yes") ."','". t("No") ."','". t("Confirm deletion") ."','". t("Save") ."','". t("Cancel") ."','". t("Person:") ."','". t("No user selected") ."','". t("Select one of the following") ."' ],";
+?>
+<script type="text/javascript">
+ $(document).ready(function() {
+ $("<?= $css_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) ?>',
+ tags: '<?= url::site("tags/autocomplete") ?>',
+ <?= $labels_arraystring ?>
+ <?= $jscode ?>
+ users: '<?= url::site("photoannotation/autocomplete") ?>',
+ rtlsupport: '<?= $rtl_support ?>',
+ useAjax: false,
+ cssaclass: '<?= $css_a_class ?>',
+ csrf: '<?= $csrf ?>'
+ });
+ });
+ </script>
+ <? if ($legend_display != ""): ?>
+ <?= "<div style=\"text-align: center\">". $legend_display ."</div>" ?>
+ <? endif ?>
diff --git a/modules/photoannotation/views/photoannotation_user_search.html.php b/modules/photoannotation/views/photoannotation_user_search.html.php
new file mode 100644
index 00000000..2182a324
--- /dev/null
+++ b/modules/photoannotation/views/photoannotation_user_search.html.php
@@ -0,0 +1,55 @@
+<?php defined("SYSPATH") or die("No direct script access.") ?>
+<script type="text/javascript">
+ $("#g-user-search-form").ready(function() {
+ var url = $("#g-user-search-results").attr("ref") + "/autocomplete";
+ $("#g-user-search-form input:text").autocomplete(
+ url, {
+ max: 30,
+ multiple: false,
+ cacheLength: 1
+ }
+ );
+ });
+</script>
+<div id="g-user-search-results" ref="<?= url::site("photoannotation") ?>">
+ <h1><?= t("Search results") ?></h1>
+ <?= $search_form ?>
+ <? if (count($users)): ?>
+ <div class="g-message photoannotation-user-search">
+ <?= t("%count people found for <b>%term</b>", array("count" => $count, "term" => $q)) ?>
+ </div>
+ <? foreach ($users as $user): ?>
+ <? $profile_link = "<a href=\"". user_profile::url($user->id) ."\">" ?>
+ <div class="g-block">
+ <h2><img src="<?= $user->avatar_url(40, $theme->url("images/avatar.jpg", true)) ?>"
+ alt="<?= html::clean_attribute($user->display_name()) ?>"
+ class="g-avatar" width="40" height="40" />
+ <?= $profile_link . $user->name ?></a></h2>
+ <div>
+ <table class="g-message">
+ <tbody>
+ <tr>
+ <th style="width: 20%"><?= t("Full name") ?></th>
+ <td><?= $user->display_name() ?></td>
+ </tr>
+ <tr>
+ <th style="width: 20%"><?= t("Tagged photos") ?></th>
+ <td colspan="2"><?= photoannotation::annotation_count($user->id) ?></td>
+ </tr>
+ <? if (module::is_active("comment")): ?>
+ <tr>
+ <th style="width: 20%"><?= t("Comments") ?></th>
+ <td colspan="2"><?= photoannotation::comment_count($user->id) ?></td>
+ </tr>
+ <? endif ?>
+ </tbody></table>
+ </div>
+ </div>
+ <? endforeach ?>
+ <?= $paginator ?>
+ <? else: ?>
+ <div class="photoannotation-user-search">
+ <?= t("No users found for <b>%term</b>", array("term" => $q)) ?>
+ </div>
+ <? endif; ?>
+</div>