Pull to refresh

Sidebar Gadget Sticky Notes с синхронизацией через DropBox для Windows 7 своими руками

Reading time 10 min
Views 31K
Очень долго я искал для себя маленькую записную книжку для хранения важной информации на компьютере, которая была бы на моем рабочем столе на работе и дома. В Windows 7 есть даже специальная программка, которая называется Sticky Notes, она всем хороша, но имеет три существенных (для меня) минуса:
  1. Она не имеет встроенной синхронизации с несколькими компьютерами (точнее сделать то ее можно, через тот же DropBox, но с бубном);
  2. Она постоянно висит в панели задач в списке открытых окон;
  3. При нажатии на Ctrl+D (свернуть все окна), она сворачивается, как и любое другое окно. Что не очень удобно.


Также в сети была найдена программа Evernote Sticky Notes. Она также имеет из недостатков два последних пункта, но позволяет через учетную запись Evernote производить синхронизацию. После не продолжительного использования, я от нее решил отказаться.
Итак, что же делать?

Сначала покажу, что у меня получилось в итоге. И опишу основные возможности, затем как все это было сделано.
Внешний вид гаджета: А вот страница настроек:

Основные возможности:
  1. Заметки сделаны в виде гаджета для боковой панели Windows 7;
  2. Синхронизация хранимых данных через DropBox между несколькими компьютерами;
  3. Возможность изменения цвета заметки, шрифта, размера шрифта;
  4. Изменение размеров заметки с помощью мышки;

Реализация гаджета


Я решил сделать гаджет для 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 предлагает при установке! Если вдруг это не так, то ничего страшного, можно установить гаджет, открыть настройки и вручную указать путь к файлу, в котором будет храниться текст нашей заметки.
Скачайте гаджет (в конце статьи) и запустите его:

Нажимаем «Установить». Готово, гаджет установлен, а на рабочем столе появилась заметка, которой уже можно пользоваться.

Минусы


Все таки, без минусов никуда. Есть проблемы, которые на данный момент не получилось устранить.
  1. Если папка DropBox пенесена в другое место, отличное от того, куда она устанавливается по умолчанию, то гаджет сразу не заработает. Необходимо будет открыть настройки гаджета и указать путь к файлу. Этот минус устранен.
  2. Нет форматирования в заметке. Только обычный текст, без курсива, жирного, зачеркнутого текста и т.п. Была попытка подвязать к гаджету WYSIWYG-редакторы (TinyMCE, CKEditor), но заставить их нормально работать в гаджете из коробки не получилось.

Подведем итоги


Лично для себя я получил именно то, что хотел: простой и удобный гаджет, который со мной и дома и на работе.
Так же, хочу отдельно сказать, что для людей, которые хорошо знают HTML + CSS + JavaScript сделать хороший гаджет, который облегчит жизнь очень просто.

Скачать гаджет

Надеюсь, моя статья оказалась полезной для Вас.

PS: Исправил найденные ошибки.
Теперь папка синхронизации DropBox определяется программно с помощью JavaScript (отдельное спасибо пользователю isden за ссылку). Так же при изменении имени файла в настройках изменения принимаются сразу.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+21
Comments 28
Comments Comments 28

Articles