Очень долго я искал для себя маленькую записную книжку для хранения важной информации на компьютере, которая была бы на моем рабочем столе на работе и дома. В Windows 7 есть даже специальная программка, которая называется Sticky Notes, она всем хороша, но имеет три существенных (для меня) минуса:- Она не имеет встроенной синхронизации с несколькими компьютерами (точнее сделать то ее можно, через тот же DropBox, но с бубном);
- Она постоянно висит в панели задач в списке открытых окон;
- При нажатии на Ctrl+D (свернуть все окна), она сворачивается, как и любое другое окно. Что не очень удобно.
Также в сети была найдена программа Evernote Sticky Notes. Она также имеет из недостатков два последних пункта, но позволяет через учетную запись Evernote производить синхронизацию. После не продолжительного использования, я от нее решил отказаться.
Итак, что же делать?
Сначала покажу, что у меня получилось в итоге. И опишу основные возможности, затем как все это было сделано.
| Внешний вид гаджета: | А вот страница настроек: |
![]() |
![]() |
Основные возможности:
- Заметки сделаны в виде гаджета для боковой панели Windows 7;
- Синхронизация хранимых данных через DropBox между несколькими компьютерами;
- Возможность изменения цвета заметки, шрифта, размера шрифта;
- Изменение размеров заметки с помощью мышки;
Реализация гаджета
Я решил сделать гаджет для Windows, который полностью бы удовлетворял меня во всех отношениях.
Механизм гаджетов в Windows позволяет не мешаться в панели задач, на них не распространяется сворачивание всех окон, они все равно будут занимать гордое место на рабочем столе. А если хранить файл с текстом заметки в папке DropBox, то можно добиться синхронизации записок на нескольких компьютерах.
Дабы избежать вопросов почему выбран именно DropBox, отвечу сразу: он мне нравится, и я его постоянно использую, а также определить папку, куда DropBox устанавливается по умолчанию достаточно просто: эта папка обычно находится здесь (на JavaScript):
System.Environment.getEnvironmentVariable("USERPROFILE") + "\\DropBox\\"
Алгоритм работы гаджета
Алгоритм работы гаджета очень простой.
Гаджет хранит путь к файлу, в котором содержится текст с отображаемой информацией. Этот файл находится в DropBox. При изменении текста в гаджете, измененная информация записывается в файл. DropBox после изменения файла сам синхронизирует данные между компьютерами, это уже головная боль DropBox.
Время от времени с помощью JavaScript, происходит проверка, не изменилась ли дата последнего изменения файла с информацией, которую отображает гаджет и реальным файлом. Если дата изменилась, то загружается обновленный текст (это значит, что текст гаджета был изменен на другом компьютере, либо после включения компьютера DropBox не успел обновить файл с текстом гаджета). Собственно все.
Реализация гаджета
Сам по себе гаджет – это, по сути, zip-архив (правда, с измененным расширением .gadget) с манифестом внутри (файл gadget.xml) и файлами для работы гаджета (HTML-файлы, скрипты JavaScript, изображения для гаджета, CSS-файлы). Манифест содержит информацию о гаджете и о том, какой файл необходимо отобразить гаджетом.
gadget.xml
<?xml version="1.0" encoding="utf-8" ?> <gadget> <name>DropBox заметки</name> <namespace>microsoft.windows</namespace> <version>1.0</version> <author name="Барилко Виталий"> <info url="http://sys1c.ru" /> <logo src="Images/icon.png" /> </author> <copyright>© 2012</copyright> <description>Гаджет для отображения списка дел, которые синхронизируются через DropBox.</description> <icons> <icon src="Images/icon.png" /> </icons> <hosts> <host name="sidebar"> <base type="HTML" apiVersion="1.0.0" src="main.html" /> <permissions>Full</permissions> <platform minPlatformVersion="1.0" /> </host> </hosts> </gadget>
Вот, что получаем в списке гаджетов:

Основные файлы работы гаджета
Для того, чтобы посмотреть на исходный код гаджета достаточно изменить расширение с ".gadget" на ".zip" и распаковать его любым архиватором.
Вот главный HTML-документ, который выполняет всю работу.
main.html
<html> <head> <meta http-equiv="Content-Type" content="text/html; charset=Unicode" /> <title>DropBox заметки</title> <style type="text/css"> body { margin: 0; } #textBox { border: none; position: absolute; font-size: 9pt; font-family: Segoe Print, Segoe Script, Segoe UI; background: clear; overflow: auto; } #erase_btn { position: absolute; left: 2px; bottom: 2px; width: 16px; height: 16px; border: none; z-index: 3; border-width: 0; } </style> <script type="text/javascript" src="main.js"></script> </head> <body unselectable="on" scroll="no" onload="initializeMain()"> <g:background id="backgroundObject" style="position:absolute;z-index:-1"/> <textarea id="textBox" onkeyup="OnTextChanged()" style="left:0px;top:0px;padding:15px 15px 15px 15px;"></textarea> <img id="rightGrippie" src="Images/grippie.png" style="position:absolute;right:6px; filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3);"/> <img id="bottomGrippie" src="Images/grippie.png" style="position:absolute;bottom:6px;"/> <div onmousedown="resizeTimer(handleRight)" id="handleRight" style="width:16px;height:190px;position:absolute;right:0px;top:0px;cursor:e-resize;z-index:2;"> <img src="Images/spacer.gif" style="width:100%; height:100%"/> </div> <div onmousedown="resizeTimer(handleBottom)" id="handleBottom" style="position:absolute;height:16px;left:0px;bottom:0px;cursor:s-resize;margin:0pxz-index:2;"> <img src="Images/spacer.gif" style="width:100%; height:100%"/> </div> <div onmousedown="resizeTimer(handleCorner)" id="handleCorner" style="width:17px;height:17px;position:absolute;right:0px;bottom:0px;cursor:se-resize;z-index:2;"> <img src="Images/icon_resize.gif" style="width:16px; height:16px; z-index:3;"/> </div> <span onclick="eraseText()" tabindex="1"><img id="erase_btn" src="Images/erase_btn.png" title="Очистить" /></span> </body> </html>
Основной скрипт, в котором происходит загрузка и сохранение текстовой информации, проверка с интервалом на изменение файла и т.д.
main.js
var width, height; // Ширина и высота гаджета var textFileName; // Имя файла с текстом гаджета var fs; // Объект FileSystemObject var timer, timerInterval; // Таймер и интервал таймера для проверки изменения текста в файле var dateLastMod; // Дата последнего изменения файла var startWidth, startHeight; // Изначальная ширина и высота гаджета var basey, basex; // Системные // Инициализация гаджета function initializeMain() { // Файл с настройками System.Gadget.settingsUI = "settings.html"; System.Gadget.onSettingsClosed = updateSettings; fs = new ActiveXObject("Scripting.FileSystemObject"); // Загружаем данные из файла в гаджет LoadTextFile(); updateSettings(); updateDisplay(); // Подключаем таймер проверки изменения текста timer = setInterval(checkText, updateinterval * 1000 * 60); // Переводим в минуты } // Раскодировка строки в Base64 function base64_decode (data) { var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, dec = "", tmp_arr = []; if (!data) { return data; } data += ''; do { // unpack four hexets into three octets using index points in b64 h1 = b64.indexOf(data.charAt(i++)); h2 = b64.indexOf(data.charAt(i++)); h3 = b64.indexOf(data.charAt(i++)); h4 = b64.indexOf(data.charAt(i++)); bits = h1 << 18 | h2 << 12 | h3 << 6 | h4; o1 = bits >> 16 & 0xff; o2 = bits >> 8 & 0xff; o3 = bits & 0xff; if (h3 == 64) { tmp_arr[ac++] = String.fromCharCode(o1); } else if (h4 == 64) { tmp_arr[ac++] = String.fromCharCode(o1, o2); } else { tmp_arr[ac++] = String.fromCharCode(o1, o2, o3); } } while (i < data.length); dec = tmp_arr.join(''); return dec; } // Загрузка данных из файла в гаджет function LoadTextFile() { // Читаем текст из файла (здесь проверка на существование файла) var text = ""; textFileName = System.Gadget.Settings.read("textfilename"); if (textFileName){ if (fs.FileExists(textFileName)){ var f = fs.OpenTextFile(textFileName, 1, true, -1); if (!f.AtEndOfStream) text = f.ReadAll(); f.Close(); } else { var f = fs.CreateTextFile(textFileName, true, true); f.Close(); } } else { try { // Определяем путь из файла host.db настроек DropBox var dbPath = System.Environment.getEnvironmentVariable("APPDATA"); var fconfig = fs.OpenTextFile(dbPath + "\\Dropbox\\host.db", 1, false, 0); var s = fconfig.ReadLine(); var folderPath = base64_decode(fconfig.ReadLine()); } catch(e) { // Если по какой то причине не сработало, то присваиваем путь по профайлу пользователя var folderPath = System.Environment.getEnvironmentVariable("USERPROFILE") + "\\Dropbox"; } textFileName = folderPath + "\\Tasks.txt"; if (fs.FileExists(textFileName)){ var f = fs.OpenTextFile(textFileName, 1, true, -1); if (!f.AtEndOfStream) text = f.ReadAll(); f.Close(); } else { var f = fs.CreateTextFile(textFileName, true, true); f.Close(); } } // Запоминаем время последнего изменения файла dateLastMod = fs.GetFile(textFileName).dateLastModified; // Загоняем текст в гаджет if (text) textBox.value = text; else textBox.value = ""; } // Проверяем где дата файла старше и если в файле, то загружаем текст из файла function checkText() { var f = fs.GetFile(textFileName); if (f.dateLastModified != dateLastMod){ // Дата изменилась загружаем dateLastMod = f.dateLastModified; var fi = fs.OpenTextFile(textFileName, 1, true, -1); if (!f.AtEndOfStream) textBox.value = fi.ReadAll(); fi.Close(); } } // Обновляем настройки function updateSettings() { var Color = System.Gadget.Settings.read("backgroundColor"); if (Color) textBox.style.backgroundColor = Color; else textBox.style.backgroundColor = "FFFFB9"; var fontname = System.Gadget.Settings.read("fontname"); if (fontname) textBox.style.fontFamily = fontname; else textBox.style.fontFamily = "Segoe UI"; var fontsize = System.Gadget.Settings.read("fontsize"); if (fontsize) textBox.style.fontSize = fontsize + "pt"; else { fontsize = "10" textBox.style.fontSize = "10pt"; } var tempWidth = System.Gadget.Settings.read("width"); if (tempWidth) width = tempWidth; else width = "200"; var tempHeight = System.Gadget.Settings.read("height"); if (tempHeight) height = tempHeight; else height = "200"; var tempupdateinterval = System.Gadget.Settings.read("updateinterval"); if (tempupdateinterval) updateinterval = tempupdateinterval; else updateinterval = 1; var tempFileName = System.Gadget.Settings.read("textfilename"); if (tempFileName) textFileName = tempFileName; LoadTextFile(); // Меняем цвет фона и скрола document.body.style.backgroundColor = textBox.style.backgroundColor; document.body.style.scrollbarFaceColor = textBox.style.backgroundColor; document.body.style.scrollbarFaceColor = textBox.style.backgroundColor; document.body.style.scrollbarTrackColor = textBox.style.backgroundColor; document.body.style.scrollbarShadowColor = textBox.style.backgroundColor; document.body.style.scrollbarHighlightColor = textBox.style.backgroundColor; document.body.style.scrollbar3dlightColor = textBox.style.backgroundColor; document.body.style.scrollbarDarkshadowColor = textBox.style.backgroundColor; // Сохраняем настройки System.Gadget.Settings.write("backgroundColor", textBox.style.backgroundColor); System.Gadget.Settings.write("fontname", textBox.style.fontFamily); System.Gadget.Settings.write("fontsize", fontsize); System.Gadget.Settings.write("width", width); System.Gadget.Settings.write("height", height); System.Gadget.Settings.write("textfilename", textFileName); System.Gadget.Settings.write("updateinterval", updateinterval); } // При изменении текста в гаджете function OnTextChanged() { var f = fs.CreateTextFile(textFileName, true, true); f.Write(textBox.value); f.Close(); } // Очистка текста function eraseText() { textBox.value = ""; OnTextChanged(); } // Обновление function updateDisplay() { document.body.style.height = height; document.body.style.width = width; textBox.style.width = width - 16; handleBottom.style.width = width - 18; textBox.style.height = height - 16; handleRight.style.height = height - 18; rightGrippie.style.top = Math.floor(height / 2 - 13); bottomGrippie.style.left = Math.floor(width / 2 - 13); } // Растягивание в гаджете function resizeTimer(field) { field.setCapture(); startWidth = parseInt(document.body.style.width); startHeight = parseInt(document.body.style.height); basey = event.y; basex = event.x; field.onmousemove = function () { doResize(field); } field.onmouseup = function () { field.releaseCapture(); field.onmousemove = null; field.onmouseup = null; doResize(field); System.Gadget.Settings.write("width", width); System.Gadget.Settings.write("height", height); } } function doResize(field) { if (field == handleRight || field == handleCorner) { width = startWidth + event.x - basex; width = width < 40 ? 40 : width; } if (field == handleBottom || field == handleCorner) { height = startHeight + event.y - basey; height = height < 40 ? 40 : height; } updateDisplay(); }
Как установить гаджет
Внимание! Для работы гаджета необходим установленный DropBox. Причем путь к синхронизируемой папке должен быть такой, какой DropBox предлагает при установке! Если вдруг это не так, то ничего страшного, можно установить гаджет, открыть настройки и вручную указать путь к файлу, в котором будет храниться текст нашей заметки.
Скачайте гаджет (в конце статьи) и запустите его:

Нажимаем «Установить». Готово, гаджет установлен, а на рабочем столе появилась заметка, которой уже можно пользоваться.
Минусы
Все таки, без минусов никуда. Есть проблемы, которые на данный момент не получилось устранить.
Если папка DropBox пенесена в другое место, отличное от того, куда она устанавливается по умолчанию, то гаджет сразу не заработает. Необходимо будет открыть настройки гаджета и указать путь к файлу.Этот минус устранен.- Нет форматирования в заметке. Только обычный текст, без курсива, жирного, зачеркнутого текста и т.п. Была попытка подвязать к гаджету WYSIWYG-редакторы (TinyMCE, CKEditor), но заставить их нормально работать в гаджете из коробки не получилось.
Подведем итоги
Лично для себя я получил именно то, что хотел: простой и удобный гаджет, который со мной и дома и на работе.
Так же, хочу отдельно сказать, что для людей, которые хорошо знают HTML + CSS + JavaScript сделать хороший гаджет, который облегчит жизнь очень просто.
Скачать гаджетНадеюсь, моя статья оказалась полезной для Вас.
PS: Исправил найденные ошибки.
Теперь папка синхронизации DropBox определяется программно с помощью JavaScript (отдельное спасибо пользователю isden за ссылку). Так же при изменении имени файла в настройках изменения принимаются сразу.

