Merge branch 'master' into gettext-cs

Conflicts:
	SparkleLib/SparkleListenerTcp.cs
This commit is contained in:
serras 2012-02-27 10:04:28 +01:00
commit 87ef4ed013
67 changed files with 2197 additions and 1018 deletions

1
.gitignore vendored
View file

@ -3,7 +3,6 @@
*.exe
*.exe.mdb
*.userprefs
*.app
*.pidb
*.gmo
*.bak

8
NEWS
View file

@ -1,3 +1,11 @@
0.8.3 for Linux and Mac (Mon Feb 19 2012):
Hylke:
-
-
-
0.8.2 for Linux and Mac (Sat Feb 11 2012):
Hylke:

View file

@ -21,7 +21,7 @@ information see the LICENSE file or visit http://www.gnu.org/licenses/gpl-3.0.ht
Requirements:
- git >= 1.7.0
- git >= 1.7.3
- gtk-sharp2
- mono-core >= 2.8
- notify-sharp

View file

@ -19,6 +19,7 @@ using System;
using System.IO;
using System.Diagnostics;
using System.Text.RegularExpressions;
using System.Threading;
namespace SparkleLib {
@ -28,23 +29,27 @@ namespace SparkleLib {
private SparkleGit git;
public SparkleFetcherGit (string server, string remote_folder, string target_folder) :
base (server, remote_folder, target_folder)
public SparkleFetcherGit (string server, string remote_path, string target_folder) :
base (server, remote_path, target_folder)
{
if (server.EndsWith ("/"))
server = server.Substring (0, server.Length - 1);
if (!remote_folder.StartsWith ("/"))
remote_folder = "/" + remote_folder;
// FIXME: Adding these lines makes the fetcher fail
// if (remote_path.EndsWith ("/"))
// remote_path = remote_path.Substring (0, remote_path.Length - 1);
if (!remote_path.StartsWith ("/"))
remote_path = "/" + remote_path;
Uri uri;
try {
uri = new Uri (server + remote_folder);
uri = new Uri (server + remote_path);
} catch (UriFormatException) {
uri = new Uri ("ssh://" + server + remote_folder);
uri = new Uri ("ssh://" + server + remote_path);
}
@ -132,6 +137,18 @@ namespace SparkleLib {
this.git.WaitForExit ();
SparkleHelpers.DebugInfo ("Git", "Exit code " + this.git.ExitCode.ToString ());
while (percentage < 100) {
percentage += 25;
if (percentage >= 100)
break;
base.OnProgressChanged (percentage);
Thread.Sleep (750);
}
base.OnProgressChanged (100);
Thread.Sleep (1000);
if (this.git.ExitCode != 0) {
return false;

View file

@ -626,12 +626,11 @@ namespace SparkleLib {
if (match.Success) {
SparkleChangeSet change_set = new SparkleChangeSet ();
change_set.Folder = Name;
change_set.Revision = match.Groups [1].Value;
change_set.User.Name = match.Groups [2].Value;
change_set.User.Email = match.Groups [3].Value;
change_set.IsMagical = is_merge_commit;
change_set.Url = Url;
change_set.Folder = Name;
change_set.Revision = match.Groups [1].Value;
change_set.User = new SparkleUser (match.Groups [2].Value, match.Groups [3].Value);
change_set.IsMagical = is_merge_commit;
change_set.Url = Url;
change_set.Timestamp = new DateTime (int.Parse (match.Groups [4].Value),
int.Parse (match.Groups [5].Value), int.Parse (match.Groups [6].Value),
@ -724,6 +723,9 @@ namespace SparkleLib {
} else if (child_path.EndsWith (".notes")) {
continue;
} else if (child_path.EndsWith (".git")) {
continue;
}
PrepareDirectories (child_path);
@ -780,7 +782,10 @@ namespace SparkleLib {
if (file_name.EndsWith (".empty"))
file_name = file_name.Substring (0, file_name.Length - 6);
message += "+ " + file_name + "" + n;
if (file_name.StartsWith (".notes"))
message += "added a note";
else
message += "+ " + file_name + "" + n;
count++;
if (count == max_count)

View file

@ -17,6 +17,7 @@ SOURCES = \
SparkleListenerFactory.cs \
SparkleListenerTcp.cs \
SparkleRepoBase.cs \
SparkleUser.cs \
SparkleWatcher.cs

View file

@ -84,22 +84,6 @@ namespace SparkleLib {
}
public class SparkleUser {
public string Name;
public string Email;
public string PublicKey;
public SparkleUser (string name, string email)
{
Name = name;
Email = email;
}
}
public class SparkleFolder {
public string Name;

View file

@ -18,8 +18,8 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Xml;
using System.Security.Principal;
using System.Xml;
namespace SparkleLib {
@ -145,7 +145,17 @@ namespace SparkleLib {
XmlNode email_node = SelectSingleNode ("/sparkleshare/user/email/text()");
string email = email_node.Value;
return new SparkleUser (name, email);
string pubkey_file_path = Path.Combine (
Path.GetDirectoryName (FullPath),
"sparkleshare." + email + ".key.pub"
);
SparkleUser user = new SparkleUser (name, email);
if (File.Exists (pubkey_file_path))
user.PublicKey = File.ReadAllText (pubkey_file_path);
return user;
}
set {
@ -157,7 +167,7 @@ namespace SparkleLib {
XmlNode email_node = SelectSingleNode ("/sparkleshare/user/email/text()");
email_node.InnerText = user.Email;
this.Save ();
Save ();
// ConfigureSSH ();
}
@ -248,7 +258,7 @@ namespace SparkleLib {
XmlNode node_root = SelectSingleNode ("/sparkleshare");
node_root.AppendChild (node_folder);
this.Save ();
Save ();
}
@ -259,32 +269,32 @@ namespace SparkleLib {
SelectSingleNode ("/sparkleshare").RemoveChild (node_folder);
}
this.Save ();
Save ();
}
public bool FolderExists (string name)
{
XmlNode folder = this.GetFolder (name);
XmlNode folder = GetFolder (name);
return (folder != null);
}
public string GetBackendForFolder (string name)
{
return this.GetFolderValue (name, "backend");
return GetFolderValue (name, "backend");
}
public string GetUrlForFolder (string name)
{
return this.GetFolderValue (name, "url");
return GetFolderValue (name, "url");
}
public bool SetFolderOptionalAttribute (string folder_name, string key, string value)
{
XmlNode folder = this.GetFolder (folder_name);
XmlNode folder = GetFolder (folder_name);
if (folder == null)
return false;
@ -298,13 +308,14 @@ namespace SparkleLib {
folder.AppendChild (new_node);
}
Save ();
return true;
}
public string GetFolderOptionalAttribute (string folder_name, string key)
{
XmlNode folder = this.GetFolder (folder_name);
XmlNode folder = GetFolder (folder_name);
if (folder != null) {
if (folder [key] != null)
@ -364,7 +375,7 @@ namespace SparkleLib {
private string GetFolderValue (string name, string key)
{
XmlNode folder = this.GetFolder(name);
XmlNode folder = GetFolder(name);
if ((folder != null) && (folder [key] != null)) {
return folder [key].InnerText;
@ -401,7 +412,7 @@ namespace SparkleLib {
}
SparkleHelpers.DebugInfo ("Config", "Updated " + name + ":" + content);
this.Save ();
Save ();
}
@ -410,7 +421,7 @@ namespace SparkleLib {
if (!File.Exists (FullPath))
throw new ConfigFileNotFoundException (FullPath + " does not exist");
this.Save (FullPath);
Save (FullPath);
SparkleHelpers.DebugInfo ("Config", "Updated \"" + FullPath + "\"");
}

View file

@ -42,6 +42,7 @@ namespace SparkleLib {
public string RemoteUrl;
public string [] ExcludeRules;
public string [] Warnings;
public bool IsActive { get; private set; }
private Thread thread;
@ -50,6 +51,7 @@ namespace SparkleLib {
{
TargetFolder = target_folder;
RemoteUrl = server + "/" + remote_folder;
IsActive = false;
ExcludeRules = new string [] {
// gedit and emacs
@ -125,6 +127,7 @@ namespace SparkleLib {
// Clones the remote repository
public void Start ()
{
IsActive = true;
SparkleHelpers.DebugInfo ("Fetcher", "[" + TargetFolder + "] Fetching folder: " + RemoteUrl);
if (Started != null)
@ -149,6 +152,7 @@ namespace SparkleLib {
SparkleHelpers.DebugInfo ("Fetcher", "Finished");
EnableHostKeyCheckingForHost (host);
IsActive = false;
if (Finished != null)
Finished (Warnings);
@ -157,6 +161,7 @@ namespace SparkleLib {
SparkleHelpers.DebugInfo ("Fetcher", "Failed");
EnableHostKeyCheckingForHost (host);
IsActive = false;
if (Failed != null)
Failed ();
@ -178,7 +183,7 @@ namespace SparkleLib {
protected void OnProgressChanged (double percentage) {
if (ProgressChanged != null)
ProgressChanged (percentage);
ProgressChanged (percentage);
}

View file

@ -91,7 +91,8 @@ namespace SparkleLib {
this.is_connected = false;
this.is_connecting = false;
// this.socket.Dispose ();
if (this.socket != null)
this.socket.Close ();
OnDisconnected (e.Message);
return;
@ -158,7 +159,8 @@ namespace SparkleLib {
this.is_connected = false;
this.is_connecting = false;;
// this.socket.Dispose ();
if (this.socket != null)
this.socket.Close ();
OnDisconnected ("Ping timeout");
return;
@ -244,6 +246,9 @@ namespace SparkleLib {
this.thread.Abort ();
this.thread.Join ();
if (this.socket != null)
this.socket.Close ();
base.Dispose ();
}

36
SparkleLib/SparkleUser.cs Normal file
View file

@ -0,0 +1,36 @@
// SparkleShare, a collaboration and sharing tool.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
using System;
namespace SparkleLib {
public class SparkleUser {
public readonly string Name;
public readonly string Email;
public string PublicKey;
public SparkleUser (string name, string email)
{
Name = name;
Email = email;
}
}
}

View file

@ -54,18 +54,23 @@ namespace SparkleShare {
CreateAbout ();
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
Controller.HideWindowEvent += delegate {
InvokeOnMainThread (delegate {
PerformClose (this);
});
};
OrderFrontRegardless ();
Program.UI.UpdateDockIconVisibility ();
Controller.ShowWindowEvent += delegate {
InvokeOnMainThread (delegate {
OrderFrontRegardless ();
});
};
Controller.NewVersionEvent += delegate (string new_version) {
InvokeOnMainThread (delegate {
UpdatesTextField.StringValue = "A newer version (" + new_version + ") is available!";
UpdatesTextField.TextColor =
NSColor.FromCalibratedRgba (0.96f, 0.47f, 0.0f, 1.0f); // Tango Orange #2
NSColor.FromCalibratedRgba (0.45f, 0.62f, 0.81f, 1.0f);
});
};
@ -73,7 +78,7 @@ namespace SparkleShare {
InvokeOnMainThread (delegate {
UpdatesTextField.StringValue = "You are running the latest version.";
UpdatesTextField.TextColor =
NSColor.FromCalibratedRgba (0.45f, 0.62f, 0.81f, 1.0f); // Tango Sky Blue #1
NSColor.FromCalibratedRgba (0.45f, 0.62f, 0.81f, 1.0f);
});
};
@ -157,6 +162,29 @@ namespace SparkleShare {
ContentView.AddSubview (UpdatesTextField);
ContentView.AddSubview (CreditsTextField);
}
public override void OrderFrontRegardless ()
{
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
base.OrderFrontRegardless ();
}
public override void PerformClose (NSObject sender)
{
base.OrderOut (this);
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
return;
}
}
@ -164,9 +192,7 @@ namespace SparkleShare {
public override bool WindowShouldClose (NSObject sender)
{
(sender as SparkleAbout).OrderOut (this);
Program.UI.UpdateDockIconVisibility ();
(sender as SparkleAbout).Controller.WindowClosed ();
return false;
}
}

View file

@ -26,12 +26,12 @@ namespace SparkleShare {
public class SparkleBubbles : NSObject {
private SparkleBubblesController controller = new SparkleBubblesController ();
public SparkleBubblesController Controller = new SparkleBubblesController ();
public SparkleBubbles ()
{
this.controller.ShowBubbleEvent += delegate (string title, string subtext, string image_path) {
Controller.ShowBubbleEvent += delegate (string title, string subtext, string image_path) {
InvokeOnMainThread (delegate {
if (!GrowlApplicationBridge.IsGrowlRunning ()) {
NSApplication.SharedApplication.RequestUserAttention (
@ -68,17 +68,7 @@ namespace SparkleShare {
[Export("growlNotificationWasClicked")]
public override void GrowlNotificationWasClicked (NSObject o)
{
InvokeOnMainThread (delegate {
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
if (SparkleUI.EventLog == null)
SparkleUI.EventLog = new SparkleEventLog ();
SparkleUI.EventLog.Controller.SelectedFolder = null;
SparkleUI.EventLog.OrderFrontRegardless ();
SparkleUI.EventLog.MakeKeyAndOrderFront (this);
});
SparkleUI.Bubbles.Controller.BubbleClicked ();
}
}
}

View file

@ -102,16 +102,40 @@ namespace SparkleShare {
}
public override void EnableSystemAutostart ()
public override void CreateStartupItem ()
{
// N/A
// There aren't any bindings in MonoMac to support this yet, so
// we call out to an applescript to do the job
Process process = new Process ();
process.EnableRaisingEvents = true;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.FileName = "osascript";
process.StartInfo.CreateNoWindow = true;
string app_path = Path.GetDirectoryName (NSBundle.MainBundle.ResourcePath);
app_path = Path.GetDirectoryName (app_path);
process.StartInfo.Arguments = "-e 'tell application \"System Events\" to " +
"make login item at end with properties {path:\"" + app_path + "\", hidden:false}'";
process.Exited += delegate {
SparkleHelpers.DebugInfo ("Controller", "Added " + app_path + " to login items");
};
try {
process.Start ();
} catch (Exception e) {
SparkleHelpers.DebugInfo ("Controller", "Failed adding " + app_path + " to login items: " + e.Message);
}
}
public override void InstallLauncher ()
{
// N/A
}
public override void InstallProtocolHandler ()
{
// We ship SparkleShareInviteHandler.app in the bundle
}
// Adds the SparkleShare folder to the user's

View file

@ -54,7 +54,8 @@ namespace SparkleShare {
// TODO: Window needs to be made resizable
public SparkleEventLog () : base ()
{
Title = "Recent Events";
Title = "Recent Changes";
Delegate = new SparkleEventsDelegate ();
SetFrame (new RectangleF (0, 0, 480, 640), true);
@ -77,7 +78,7 @@ namespace SparkleShare {
};
this.hidden_close_button.Activated += delegate {
PerformClose (this);
Controller.WindowClosed ();
};
ContentView.AddSubview (this.hidden_close_button);
@ -142,13 +143,28 @@ namespace SparkleShare {
ContentView.AddSubview (this.progress_indicator);
UpdateContent (null);
UpdateChooser (null);
OrderFrontRegardless ();
(this.web_view.PolicyDelegate as SparkleWebPolicyDelegate)
.LinkClicked += delegate (string href) {
Controller.LinkClicked (href);
};
Program.UI.UpdateDockIconVisibility ();
// Hook up the controller events
Controller.HideWindowEvent += delegate {
InvokeOnMainThread (delegate {
PerformClose (this);
});
};
Controller.ShowWindowEvent += delegate {
InvokeOnMainThread (delegate {
OrderFrontRegardless ();
UpdateContent (null);
UpdateChooser (null);
});
};
Controller.UpdateChooserEvent += delegate (string [] folders) {
InvokeOnMainThread (delegate {
UpdateChooser (folders);
@ -226,9 +242,9 @@ namespace SparkleShare {
html = html.Replace ("<!-- $day-entry-header-background-color -->", "#f5f5f5");
html = html.Replace ("<!-- $a-color -->", "#0085cf");
html = html.Replace ("<!-- $a-hover-color -->", "#009ff8");
html = html.Replace ("<!-- $no-buddy-icon-background-image -->",
html = html.Replace ("<!-- $pixmaps-path -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps","avatar-default.png"));
"Pixmaps"));
html = html.Replace ("<!-- $document-added-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
@ -245,7 +261,8 @@ namespace SparkleShare {
html = html.Replace ("<!-- $document-moved-background-image -->",
"file://" + Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "document-moved-12.png"));
InvokeOnMainThread (delegate {
if (this.progress_indicator.Superview == ContentView)
this.progress_indicator.RemoveFromSuperview ();
@ -258,6 +275,29 @@ namespace SparkleShare {
thread.Start ();
}
}
public override void OrderFrontRegardless ()
{
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
base.OrderFrontRegardless ();
}
public override void PerformClose (NSObject sender)
{
base.OrderOut (this);
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
return;
}
}
@ -265,20 +305,23 @@ namespace SparkleShare {
public override bool WindowShouldClose (NSObject sender)
{
(sender as SparkleEventLog).OrderOut (this);
Program.UI.UpdateDockIconVisibility ();
(sender as SparkleEventLog).Controller.WindowClosed ();
return false;
}
}
public class SparkleWebPolicyDelegate : WebPolicyDelegate {
public event LinkClickedHandler LinkClicked;
public delegate void LinkClickedHandler (string href);
public override void DecidePolicyForNavigation (WebView web_view, NSDictionary action_info,
NSUrlRequest request, WebFrame frame, NSObject decision_token)
{
SparkleEventLogController.LinkClicked (request.Url.ToString ());
if (LinkClicked != null)
LinkClicked (request.Url.ToString ());
}
}
}

View file

@ -33,10 +33,11 @@ namespace SparkleShare {
public SparkleSetupController Controller = new SparkleSetupController ();
private NSButton ContinueButton;
private NSButton SyncButton;
private NSButton AddButton;
private NSButton TryAgainButton;
private NSButton CancelButton;
private NSButton SkipTutorialButton;
private NSButton StartupCheckButton;
private NSButton OpenFolderButton;
private NSButton FinishButton;
private NSImage SlideImage;
@ -48,10 +49,10 @@ namespace SparkleShare {
private NSTextField FullNameLabel;
private NSTextField AddressTextField;
private NSTextField AddressLabel;
private NSTextField AddressHelpLabel;
private NSTextField PathTextField;
private NSTextField PathLabel;
private NSTextField PathHelpLabel;
private NSTextField AddProjectTextField;
private NSTextField WarningTextField;
private NSImage WarningImage;
private NSImageView WarningImageView;
@ -64,6 +65,18 @@ namespace SparkleShare {
public SparkleSetup () : base ()
{
Controller.HideWindowEvent += delegate {
InvokeOnMainThread (delegate {
PerformClose (this);
});
};
Controller.ShowWindowEvent += delegate {
InvokeOnMainThread (delegate {
OrderFrontRegardless ();
});
};
Controller.ChangePageEvent += delegate (PageType type, string [] warnings) {
InvokeOnMainThread (delegate {
Reset ();
@ -72,8 +85,8 @@ namespace SparkleShare {
case PageType.Setup: {
Header = "Welcome to SparkleShare!";
Description = "We'll need some info to mark your changes in the event log. " +
"Don't worry, this stays between you and your peers.";
Description = "Before we get started, what's your name and email? " +
"Don't worry, this information is only visible to your team members.";
FullNameLabel = new NSTextField () {
@ -158,10 +171,85 @@ namespace SparkleShare {
break;
}
case PageType.Invite: {
Header = "You've received an invite!";
Description = "Do you want to add this project to SparkleShare?";
AddressLabel = new NSTextField () {
Alignment = NSTextAlignment.Right,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (165, Frame.Height - 240, 160, 17),
StringValue = "Address:",
Font = SparkleUI.Font
};
PathLabel = new NSTextField () {
Alignment = NSTextAlignment.Right,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (165, Frame.Height - 264, 160, 17),
StringValue = "Remote Path:",
Font = SparkleUI.Font
};
AddressTextField = new NSTextField () {
Alignment = NSTextAlignment.Left,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (330, Frame.Height - 240, 260, 17),
StringValue = Controller.PendingInvite.Address,
Font = SparkleUI.BoldFont
};
PathTextField = new NSTextField () {
Alignment = NSTextAlignment.Left,
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Frame = new RectangleF (330, Frame.Height - 264, 260, 17),
StringValue = Controller.PendingInvite.RemotePath,
Font = SparkleUI.BoldFont
};
ContentView.AddSubview (AddressLabel);
ContentView.AddSubview (PathLabel);
ContentView.AddSubview (AddressTextField);
ContentView.AddSubview (PathTextField);
CancelButton = new NSButton () {
Title = "Cancel"
};
CancelButton.Activated += delegate {
Controller.PageCancelled ();
};
AddButton = new NSButton () {
Title = "Add"
};
AddButton.Activated += delegate {
Controller.InvitePageCompleted ();
};
Buttons.Add (AddButton);
Buttons.Add (CancelButton);
break;
}
case PageType.Add: {
Header = "Where's your project hosted?";
Description = "";
Header = "Where's your project hosted?";
Description = "";
AddressLabel = new NSTextField () {
Alignment = NSTextAlignment.Left,
@ -170,7 +258,7 @@ namespace SparkleShare {
Editable = false,
Frame = new RectangleF (190, Frame.Height - 308, 160, 17),
StringValue = "Address:",
Font = SparkleUI.Font
Font = SparkleUI.BoldFont
};
AddressTextField = new NSTextField () {
@ -189,7 +277,7 @@ namespace SparkleShare {
Editable = false,
Frame = new RectangleF (190 + 196 + 16, Frame.Height - 308, 160, 17),
StringValue = "Remote Path:",
Font = SparkleUI.Font
Font = SparkleUI.BoldFont
};
PathTextField = new NSTextField () {
@ -210,7 +298,18 @@ namespace SparkleShare {
TextColor = NSColor.DisabledControlText,
Editable = false,
Frame = new RectangleF (190 + 196 + 16, Frame.Height - 355, 204, 17),
StringValue = "e.g. rupert/website-design",
StringValue = "",
Font = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Condensed, 0, 11)
};
AddressHelpLabel = new NSTextField () {
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
TextColor = NSColor.DisabledControlText,
Editable = false,
Frame = new RectangleF (190, Frame.Height - 355, 204, 17),
StringValue = "",
Font = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Condensed, 0, 11)
};
@ -265,6 +364,7 @@ namespace SparkleShare {
InvokeOnMainThread (delegate {
AddressTextField.StringValue = text;
AddressTextField.Enabled = (state == FieldState.Enabled);
AddressHelpLabel.StringValue = example_text;
});
};
@ -275,9 +375,7 @@ namespace SparkleShare {
InvokeOnMainThread (delegate {
PathTextField.StringValue = text;
PathTextField.Enabled = (state == FieldState.Enabled);
if (!string.IsNullOrEmpty (example_text))
PathHelpLabel.StringValue = "e.g. " + example_text;
PathHelpLabel.StringValue = example_text;
});
};
@ -285,7 +383,7 @@ namespace SparkleShare {
TableView.SelectRow (Controller.SelectedPluginIndex, false);
(AddressTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
(AddressTextField.Delegate as SparkleTextFieldDelegate).StringValueChanged += delegate {
Controller.CheckAddPage (
AddressTextField.StringValue,
PathTextField.StringValue,
@ -314,7 +412,7 @@ namespace SparkleShare {
Controller.UpdateAddProjectButtonEvent += delegate (bool button_enabled) {
InvokeOnMainThread (delegate {
SyncButton.Enabled = button_enabled;
AddButton.Enabled = button_enabled;
});
};
@ -322,32 +420,31 @@ namespace SparkleShare {
ContentView.AddSubview (ScrollView);
ContentView.AddSubview (AddressLabel);
ContentView.AddSubview (AddressTextField);
ContentView.AddSubview (AddressHelpLabel);
ContentView.AddSubview (PathLabel);
ContentView.AddSubview (PathTextField);
ContentView.AddSubview (PathHelpLabel);
SyncButton = new NSButton () {
AddButton = new NSButton () {
Title = "Add",
Enabled = false
};
SyncButton.Activated += delegate {
AddButton.Activated += delegate {
Controller.AddPageCompleted (
AddressTextField.StringValue,
PathTextField.StringValue
);
};
Buttons.Add (SyncButton);
Buttons.Add (AddButton);
CancelButton = new NSButton () {
Title = "Cancel"
};
CancelButton.Activated += delegate {
InvokeOnMainThread (delegate {
PerformClose (this);
});
Controller.PageCancelled ();
};
Buttons.Add (CancelButton);
@ -493,10 +590,7 @@ namespace SparkleShare {
};
FinishButton.Activated += delegate {
InvokeOnMainThread (delegate {
Controller.FinishedPageCompleted ();
PerformClose (this);
});
Controller.FinishPageCompleted ();
};
OpenFolderButton = new NSButton () {
@ -504,7 +598,7 @@ namespace SparkleShare {
};
OpenFolderButton.Activated += delegate {
Program.Controller.OpenSparkleShareFolder (Path.GetFileName (Controller.PreviousPath));
Controller.OpenFolderClicked ();
};
Buttons.Add (FinishButton);
@ -523,7 +617,7 @@ namespace SparkleShare {
switch (Controller.TutorialPageNumber) {
case 1: {
Header = "What's happening next?";
Description = "SparkleShare creates a special folder in your personal folder " +
Description = "SparkleShare creates a special folder on your computer " +
"that will keep track of your projects.";
SkipTutorialButton = new NSButton () {
@ -563,8 +657,8 @@ namespace SparkleShare {
case 2: {
Header = "Sharing files with others";
Description = "All files added to your project folders are synced with the host " +
"automatically, as well as with your collaborators.";
Description = "All files added to your project folders are synced automatically with " +
"the host and your team members.";
ContinueButton = new NSButton () {
Title = "Continue"
@ -594,8 +688,8 @@ namespace SparkleShare {
case 3: {
Header = "The status icon is here to help";
Description = "It shows the syncing process status, " +
"and contains links to your projects and the event log.";
Description = "It shows the syncing progress, provides easy access to " +
"your projects and let's you view recent changes.";
ContinueButton = new NSButton () {
Title = "Continue"
@ -625,17 +719,20 @@ namespace SparkleShare {
case 4: {
Header = "Adding projects to SparkleShare";
Description = "Just click this button when you see it on the web, and " +
"the project will be automatically added:";
Description = "You can do this through the status icon menu, or by clicking " +
"magic buttons on webpages that look like this:";
AddProjectTextField = new NSTextField () {
Frame = new RectangleF (190, Frame.Height - 290, 640 - 240, 44),
BackgroundColor = NSColor.WindowBackground,
Bordered = false,
Editable = false,
Font = SparkleUI.Font,
StringValue = "…or select Add Hosted Project… from the status icon menu " +
"to add one by hand."
StartupCheckButton = new NSButton () {
Frame = new RectangleF (190, Frame.Height - 400, 300, 18),
Title = "Add SparkleShare to startup items",
State = NSCellStateValue.On
};
StartupCheckButton.SetButtonType (NSButtonType.Switch);
StartupCheckButton.Activated += delegate {
Controller.StartupItemChanged (StartupCheckButton.State == NSCellStateValue.On);
};
FinishButton = new NSButton () {
@ -643,11 +740,10 @@ namespace SparkleShare {
};
FinishButton.Activated += delegate {
InvokeOnMainThread (delegate {
PerformClose (this);
});
Controller.TutorialPageCompleted ();
};
string slide_image_path = Path.Combine (NSBundle.MainBundle.ResourcePath,
"Pixmaps", "tutorial-slide-4.png");
@ -661,7 +757,7 @@ namespace SparkleShare {
};
ContentView.AddSubview (SlideImageView);
ContentView.AddSubview (AddProjectTextField);
ContentView.AddSubview (StartupCheckButton);
Buttons.Add (FinishButton);
break;

View file

@ -83,11 +83,6 @@ namespace SparkleShare {
Font = SparkleUI.Font
};
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
OrderFrontRegardless ();
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
}
@ -140,6 +135,12 @@ namespace SparkleShare {
public override void OrderFrontRegardless ()
{
NSApplication.SharedApplication.AddWindowsItem (this, "SparkleShare Setup", false);
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
MakeKeyAndOrderFront (this);
if (Program.UI != null)
Program.UI.UpdateDockIconVisibility ();
base.OrderFrontRegardless ();
}

View file

@ -11,7 +11,7 @@
<RootNamespace>SparkleShare</RootNamespace>
<AssemblyName>SparkleShare</AssemblyName>
<TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
<ReleaseVersion>0.8.2</ReleaseVersion>
<ReleaseVersion>0.8.3</ReleaseVersion>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
@ -24,7 +24,7 @@
<ConsolePause>false</ConsolePause>
<CustomCommands>
<CustomCommands>
<Command type="AfterBuild" command="mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp -r Growl.framework ${TargetDir}/${SolutionName}.app/Contents/Frameworks;mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Resources; cp -r git ${TargetDir}/${SolutionName}.app/Contents/Resources" externalConsole="true" />
<Command type="AfterBuild" command="mkdir -p ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp -r Growl.framework ${TargetDir}/${SolutionName}.app/Contents/Frameworks; cp -r git ${TargetDir}/${SolutionName}.app/Contents/Resources; cp -r SparkleShareInviteOpener.app ${TargetDir}/${SolutionName}.app/Contents/Resources" externalConsole="true" />
</CustomCommands>
</CustomCommands>
</PropertyGroup>
@ -59,7 +59,7 @@
<SpecificVersion>False</SpecificVersion>
</Reference>
<Reference Include="System.Net" />
<Reference Include="SparkleLib, Version=0.8.1.0, Culture=neutral, PublicKeyToken=null">
<Reference Include="SparkleLib, Version=0.8.2.0, Culture=neutral, PublicKeyToken=null">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\bin\SparkleLib.dll</HintPath>
</Reference>
@ -120,9 +120,6 @@
</ItemGroup>
<ItemGroup>
<None Include="Info.plist" />
<None Include="..\..\data\icons\avatar-default.png">
<Link>Pixmaps\avatar-default.png</Link>
</None>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<Import Project="$(MSBuildExtensionsPath)\Mono\MonoMac\v0.0\Mono.MonoMac.targets" />
@ -326,6 +323,42 @@
<Content Include="..\..\data\plugins\own-server.png">
<Link>Plugins\own-server.png</Link>
</Content>
<Content Include="..\..\data\icons\avatar-default.png">
<Link>Pixmaps\avatar-default.png</Link>
</Content>
<Content Include="..\..\data\avatar-a.png">
<Link>Pixmaps\avatar-a.png</Link>
</Content>
<Content Include="..\..\data\avatar-b.png">
<Link>Pixmaps\avatar-b.png</Link>
</Content>
<Content Include="..\..\data\avatar-c.png">
<Link>Pixmaps\avatar-c.png</Link>
</Content>
<Content Include="..\..\data\avatar-d.png">
<Link>Pixmaps\avatar-d.png</Link>
</Content>
<Content Include="..\..\data\avatar-e.png">
<Link>Pixmaps\avatar-e.png</Link>
</Content>
<Content Include="..\..\data\avatar-f.png">
<Link>Pixmaps\avatar-f.png</Link>
</Content>
<Content Include="..\..\data\avatar-g.png">
<Link>Pixmaps\avatar-g.png</Link>
</Content>
<Content Include="..\..\data\avatar-h.png">
<Link>Pixmaps\avatar-h.png</Link>
</Content>
<Content Include="..\..\data\avatar-i.png">
<Link>Pixmaps\avatar-i.png</Link>
</Content>
<Content Include="..\..\data\avatar-j.png">
<Link>Pixmaps\avatar-j.png</Link>
</Content>
<Content Include="..\..\data\avatar-k.png">
<Link>Pixmaps\avatar-k.png</Link>
</Content>
</ItemGroup>
<ItemGroup>
<Folder Include="Pixmaps\" />

View file

@ -16,6 +16,6 @@ Global
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = SparkleShare.csproj
version = 0.8.2
version = 0.8.3
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleAllowMixedLocalizations</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>applet</string>
<key>CFBundleIconFile</key>
<string>applet</string>
<key>CFBundleIdentifier</key>
<string>org.sparkleshare.SparkleShareInviteOpener</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>SparkleShareInviteOpen</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>aplt</string>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>SparkleShareInviteOpener</string>
<key>CFBundleURLSchemes</key>
<array>
<string>sparkleshare</string>
</array>
</dict>
</array>
<key>LSMinimumSystemVersionByArchitecture</key>
<dict>
<key>x86_64</key>
<string>10.6</string>
</dict>
<key>LSRequiresCarbon</key>
<true/>
<key>WindowState</key>
<dict>
<key>dividerCollapsed</key>
<false/>
<key>eventLogLevel</key>
<integer>-1</integer>
<key>name</key>
<string>ScriptWindowState</string>
<key>positionOfDivider</key>
<real>333</real>
<key>savedFrame</key>
<string>263 111 773 597 0 0 1280 778 </string>
<key>selectedTabView</key>
<string>event log</string>
</dict>
</dict>
</plist>

View file

@ -0,0 +1 @@
APPLaplt

Binary file not shown.

After

Width:  |  Height:  |  Size: 362 B

View file

@ -0,0 +1,4 @@
{\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf230
{\fonttbl}
{\colortbl;\red255\green255\blue255;}
}

View file

@ -85,7 +85,7 @@ namespace SparkleShare {
if (Controller.Folders.Length == 0)
StateText = _("Welcome to SparkleShare!");
else
StateText = _("Up to date") + Controller.FolderSize;
StateText = _("Files up to date") + Controller.FolderSize;
CreateMenu ();
@ -112,7 +112,7 @@ namespace SparkleShare {
if (Controller.Folders.Length == 0)
StateText = _("Welcome to SparkleShare!");
else
StateText = _("Up to date") + Controller.FolderSize;
StateText = _("Files up to date") + Controller.FolderSize;
StateMenuItem.Title = StateText;
CreateMenu ();
@ -180,7 +180,7 @@ namespace SparkleShare {
};
FolderMenuItem.Activated += delegate {
Program.Controller.OpenSparkleShareFolder ();
Controller.SparkleShareClicked ();
};
FolderMenuItem.Image = SparkleShareImage;
@ -230,48 +230,26 @@ namespace SparkleShare {
Menu.AddItem (NSMenuItem.SeparatorItem);
SyncMenuItem = new NSMenuItem () {
Title = "Add Hosted Project…",
Title = "Add Hosted Project…",
Enabled = true
};
if (!Program.Controller.FirstRun) {
SyncMenuItem.Activated += delegate {
InvokeOnMainThread (delegate {
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
if (SparkleUI.Setup == null) {
SparkleUI.Setup = new SparkleSetup ();
SparkleUI.Setup.Controller.ShowAddPage ();
}
if (!SparkleUI.Setup.IsVisible)
SparkleUI.Setup.Controller.ShowAddPage ();
SparkleUI.Setup.OrderFrontRegardless ();
SparkleUI.Setup.MakeKeyAndOrderFront (this);
});
};
}
SyncMenuItem.Activated += delegate {
Controller.AddHostedProjectClicked ();
};
Menu.AddItem (SyncMenuItem);
Menu.AddItem (NSMenuItem.SeparatorItem);
RecentEventsMenuItem = new NSMenuItem () {
Title = "Open Recent Events",
Title = "View Recent Changes…",
Enabled = (Controller.Folders.Length > 0)
};
if (Controller.Folders.Length > 0) {
RecentEventsMenuItem.Activated += delegate {
InvokeOnMainThread (delegate {
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
if (SparkleUI.EventLog == null)
SparkleUI.EventLog = new SparkleEventLog ();
SparkleUI.EventLog.OrderFrontRegardless ();
SparkleUI.EventLog.MakeKeyAndOrderFront (this);
});
Controller.OpenRecentEventsClicked ();
};
}
@ -304,17 +282,9 @@ namespace SparkleShare {
Title = "About SparkleShare",
Enabled = true
};
AboutMenuItem.Activated += delegate {
InvokeOnMainThread (delegate {
NSApplication.SharedApplication.ActivateIgnoringOtherApps (true);
if (SparkleUI.About == null)
SparkleUI.About = new SparkleAbout ();
else
SparkleUI.About.OrderFrontRegardless ();
});
AboutMenuItem.Activated += delegate {
Controller.AboutClicked ();
};
Menu.AddItem (AboutMenuItem);
@ -343,7 +313,7 @@ namespace SparkleShare {
private EventHandler OpenFolderDelegate (string name)
{
return delegate {
Program.Controller.OpenSparkleShareFolder (name);
Controller.SubfolderClicked (name);
};
}

View file

@ -63,15 +63,14 @@ namespace SparkleShare {
BoldFont = NSFontManager.SharedFontManager.FontWithFamily
("Lucida Grande", NSFontTraitMask.Bold, 0, 13);
Setup = new SparkleSetup ();
EventLog = new SparkleEventLog ();
About = new SparkleAbout ();
Bubbles = new SparkleBubbles ();
StatusIcon = new SparkleStatusIcon ();
Bubbles = new SparkleBubbles ();
if (Program.Controller.FirstRun) {
Setup = new SparkleSetup ();
Setup.Controller.ShowSetupPage ();
UpdateDockIconVisibility ();
}
if (Program.Controller.FirstRun)
Program.Controller.ShowSetupWindow (PageType.Setup);
}
}
@ -96,8 +95,8 @@ namespace SparkleShare {
public void UpdateDockIconVisibility ()
{
// if (true) { // TODO: check for open windows
// TODO: check for open windows
// if (true) {
ShowDockIcon ();
// } else {
@ -106,13 +105,15 @@ namespace SparkleShare {
}
private void HideDockIcon () {
private void HideDockIcon ()
{
// Currently not supported, here for completeness sake (see Apple's docs)
// NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.None;
}
private void ShowDockIcon () {
private void ShowDockIcon ()
{
NSApplication.SharedApplication.ActivationPolicy = NSApplicationActivationPolicy.Regular;
}

View file

@ -19,12 +19,11 @@ SOURCES = \
SparkleBubblesController.cs \
SparkleController.cs \
SparkleControllerBase.cs \
SparkleEntry.cs \
SparkleEventLog.cs \
SparkleEventLogController.cs \
SparkleExtensions.cs \
SparkleOptions.cs \
SparkleInvite.cs \
SparkleOptions.cs \
SparklePlugin.cs \
SparkleSetup.cs \
SparkleSetupController.cs \

View file

@ -27,7 +27,7 @@ namespace SparkleShare {
public class SparkleAbout : Window {
public SparkleAboutController Controller;
public SparkleAboutController Controller = new SparkleAboutController ();
private Label updates;
@ -41,10 +41,11 @@ namespace SparkleShare {
public SparkleAbout () : base ("")
{
DeleteEvent += delegate (object o, DeleteEventArgs args) {
HideAll ();
Controller.WindowClosed ();
args.RetVal = true;
};
DefaultSize = new Gdk.Size (600, 260);
Resizable = false;
BorderWidth = 0;
@ -62,11 +63,23 @@ namespace SparkleShare {
buf.RenderPixmapAndMask (out map, out map2, 255);
GdkWindow.SetBackPixmap (map, false);
Controller = new SparkleAboutController ();
Controller.HideWindowEvent += delegate {
Application.Invoke (delegate {
HideAll ();
});
};
Controller.ShowWindowEvent += delegate {
Application.Invoke (delegate {
ShowAll ();
Present ();
});
};
Controller.NewVersionEvent += delegate (string new_version) {
Application.Invoke (delegate {
this.updates.Markup = String.Format ("<span font_size='small' fgcolor='#f57900'>{0}</span>",
this.updates.Markup = String.Format ("<span font_size='small' fgcolor='#729fcf'>{0}</span>",
String.Format (_("A newer version ({0}) is available!"), new_version));
this.updates.ShowAll ();

View file

@ -26,6 +26,12 @@ namespace SparkleShare {
public class SparkleAboutController {
public event ShowWindowEventHandler ShowWindowEvent;
public delegate void ShowWindowEventHandler ();
public event HideWindowEventHandler HideWindowEvent;
public delegate void HideWindowEventHandler ();
public event NewVersionEventHandler NewVersionEvent;
public delegate void NewVersionEventHandler (string new_version);
@ -35,32 +41,34 @@ namespace SparkleShare {
public event CheckingForNewVersionEventHandler CheckingForNewVersionEvent;
public delegate void CheckingForNewVersionEventHandler ();
public string RunningVersion {
get {
return SparkleBackend.Version;
}
}
// Check for a new version once a day
private System.Timers.Timer version_checker = new System.Timers.Timer () {
Enabled = true,
Interval = 24 * 60 * 60 * 1000
};
public SparkleAboutController ()
{
CheckForNewVersion ();
Program.Controller.ShowAboutWindowEvent += delegate {
if (ShowWindowEvent != null)
ShowWindowEvent ();
this.version_checker.Elapsed += delegate {
CheckForNewVersion ();
};
}
public void WindowClosed ()
{
if (HideWindowEvent != null)
HideWindowEvent ();
}
public void CheckForNewVersion ()
{
this.version_checker.Stop ();
if (CheckingForNewVersionEvent != null)
CheckingForNewVersionEvent ();
@ -82,7 +90,7 @@ namespace SparkleShare {
// Add a little delay, making it seems we're
// actually doing hard work
Thread.Sleep (2 * 1000);
Thread.Sleep (1000);
if (running_version >= new_version) {
if (VersionUpToDateEvent != null)
@ -92,8 +100,6 @@ namespace SparkleShare {
if (NewVersionEvent != null)
NewVersionEvent (result);
}
this.version_checker.Start ();
};
web_client.DownloadStringAsync (uri);

View file

@ -43,16 +43,9 @@ namespace SparkleShare {
else
notification.IconName = "folder-sparkleshare";
notification.Closed += delegate {
Application.Invoke (delegate {
if (SparkleUI.EventLog == null)
SparkleUI.EventLog = new SparkleEventLog ();
SparkleUI.EventLog.Controller.SelectedFolder = null;
SparkleUI.EventLog.ShowAll ();
SparkleUI.EventLog.Present ();
});
notification.Closed += delegate (object o, EventArgs args) {
if ((args as CloseArgs).Reason == CloseReason.User)
Controller.BubbleClicked ();
};
notification.Show ();

View file

@ -28,10 +28,8 @@ namespace SparkleShare {
public SparkleBubblesController ()
{
Program.Controller.ConflictNotificationRaised += delegate {
ShowBubble ("Conflict detected.",
"Don't worry, SparkleShare made a copy of each conflicting file.",
null);
Program.Controller.AlertNotificationRaised += delegate (string title, string message) {
ShowBubble (title, message, null);
};
Program.Controller.NotificationRaised += delegate (SparkleChangeSet change_set) {
@ -53,6 +51,12 @@ namespace SparkleShare {
}
public void BubbleClicked ()
{
Program.Controller.ShowEventLogWindow ();
}
private string FormatMessage (SparkleChangeSet change_set)
{
string file_name = "";

View file

@ -45,10 +45,12 @@ namespace SparkleShare {
// Creates a .desktop entry in autostart folder to
// start SparkleShare automatically at login
public override void EnableSystemAutostart ()
public override void CreateStartupItem ()
{
string autostart_path = Path.Combine (Environment.GetFolderPath (
Environment.SpecialFolder.ApplicationData), "autostart");
string autostart_path = Path.Combine (
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
"autostart"
);
string desktopfile_path = Path.Combine (autostart_path, "sparkleshare.desktop");
@ -56,57 +58,33 @@ namespace SparkleShare {
Directory.CreateDirectory (autostart_path);
if (!File.Exists (desktopfile_path)) {
TextWriter writer = new StreamWriter (desktopfile_path);
writer.WriteLine ("[Desktop Entry]\n" +
"Type=Application\n" +
"Name=SparkleShare\n" +
"Exec=sparkleshare start\n" +
"Icon=folder-sparkleshare\n" +
"Terminal=false\n" +
"X-GNOME-Autostart-enabled=true\n" +
"Categories=Network");
writer.Close ();
try {
File.WriteAllText (desktopfile_path,
"[Desktop Entry]\n" +
"Type=Application\n" +
"Name=SparkleShare\n" +
"Exec=sparkleshare start\n" +
"Icon=folder-sparkleshare\n" +
"Terminal=false\n" +
"X-GNOME-Autostart-enabled=true\n" +
"Categories=Network");
// Give the launcher the right permissions so it can be launched by the user
UnixFileInfo file_info = new UnixFileInfo (desktopfile_path);
file_info.Create (FileAccessPermissions.UserReadWriteExecute);
// Give the launcher the right permissions so it can be launched by the user
UnixFileInfo file_info = new UnixFileInfo (desktopfile_path);
file_info.Create (FileAccessPermissions.UserReadWriteExecute);
SparkleHelpers.DebugInfo ("Controller", "Enabled autostart on login");
SparkleHelpers.DebugInfo ("Controller", "Added " + app_path + " to login items");
} catch (Exception e) {
SparkleHelpers.DebugInfo ("Controller", "Failed adding " + app_path + " to login items: " + e.Message);
}
}
}
// Installs a launcher so the user can launch SparkleShare
// from the Internet category if needed
public override void InstallLauncher ()
public override void InstallProtocolHandler ()
{
string apps_path =
new string [] {SparkleConfig.DefaultConfig.HomePath,
".local", "share", "applications"}.Combine ();
string desktopfile_path = Path.Combine (apps_path, "sparkleshare.desktop");
if (!File.Exists (desktopfile_path)) {
if (!Directory.Exists (apps_path))
Directory.CreateDirectory (apps_path);
TextWriter writer = new StreamWriter (desktopfile_path);
writer.WriteLine ("[Desktop Entry]\n" +
"Type=Application\n" +
"Name=SparkleShare\n" +
"Comment=Share documents\n" +
"Exec=sparkleshare start\n" +
"Icon=folder-sparkleshare\n" +
"Terminal=false\n" +
"Categories=Network;");
writer.Close ();
// Give the launcher the right permissions so it can be launched by the user
UnixFileInfo file_info = new UnixFileInfo (desktopfile_path);
file_info.FileAccessPermissions = FileAccessPermissions.UserReadWriteExecute;
SparkleHelpers.DebugInfo ("Controller", "Created '" + desktopfile_path + "'");
}
// TODO
}
@ -226,7 +204,7 @@ namespace SparkleShare {
{
Process process = new Process ();
process.StartInfo.FileName = "xdg-open";
process.StartInfo.Arguments = url.Replace (" ", "%20");
process.StartInfo.Arguments = "\"" + url + "\"";
process.Start ();
}
}

View file

@ -36,12 +36,23 @@ namespace SparkleShare {
public abstract class SparkleControllerBase {
public List <SparkleRepoBase> Repositories;
public List<SparkleRepoBase> Repositories = new List<SparkleRepoBase> ();
public readonly string SparklePath = SparkleConfig.DefaultConfig.FoldersPath;
public double ProgressPercentage = 0.0;
public string ProgressSpeed = "";
public event ShowSetupWindowEventHandler ShowSetupWindowEvent;
public delegate void ShowSetupWindowEventHandler (PageType page_type);
public event ShowAboutWindowEventHandler ShowAboutWindowEvent;
public delegate void ShowAboutWindowEventHandler ();
public event ShowEventLogWindowEventHandler ShowEventLogWindowEvent;
public delegate void ShowEventLogWindowEventHandler ();
public event FolderFetchedEventHandler FolderFetched;
public delegate void FolderFetchedEventHandler (string [] warnings);
@ -66,24 +77,47 @@ namespace SparkleShare {
public event OnErrorHandler OnError;
public delegate void OnErrorHandler ();
public event OnInviteHandler OnInvite;
public delegate void OnInviteHandler (SparkleInvite invite);
public event ConflictNotificationRaisedHandler ConflictNotificationRaised;
public delegate void ConflictNotificationRaisedHandler ();
public event InviteReceivedHandler InviteReceived;
public delegate void InviteReceivedHandler (SparkleInvite invite);
public event NotificationRaisedEventHandler NotificationRaised;
public delegate void NotificationRaisedEventHandler (SparkleChangeSet change_set);
public event AlertNotificationRaisedEventHandler AlertNotificationRaised;
public delegate void AlertNotificationRaisedEventHandler (string title, string message);
public event NoteNotificationRaisedEventHandler NoteNotificationRaised;
public delegate void NoteNotificationRaisedEventHandler (SparkleUser user, string folder_name);
// Path where the plugins are kept
public abstract string PluginsPath { get; }
// Enables SparkleShare to start automatically at login
public abstract void CreateStartupItem ();
// Installs the sparkleshare:// protocol handler
public abstract void InstallProtocolHandler ();
// Adds the SparkleShare folder to the user's
// list of bookmarked places
public abstract void AddToBookmarks ();
// Creates the SparkleShare folder in the user's home folder
public abstract bool CreateSparkleShareFolder ();
// Opens the SparkleShare folder or an (optional) subfolder
public abstract void OpenSparkleShareFolder (string subfolder);
// Opens a file with the appropriate application
public abstract void OpenFile (string url);
private SparkleFetcherBase fetcher;
private List<string> failed_avatars = new List<string> ();
private Object avatar_lock = new Object ();
private Object repo_lock = new Object ();
// Short alias for the translations
@ -97,11 +131,10 @@ namespace SparkleShare {
{
}
public virtual void Initialize ()
{
InstallLauncher ();
EnableSystemAutostart ();
InstallProtocolHandler ();
// Create the SparkleShare folder and add it to the bookmarks
if (CreateSparkleShareFolder ())
@ -128,16 +161,37 @@ namespace SparkleShare {
FolderListChanged ();
};
watcher.Created += delegate (object o, FileSystemEventArgs args) {
if (!args.FullPath.EndsWith (".xml"))
return;
SparkleInviteListener invite_listener = new SparkleInviteListener (1987);
if (this.fetcher != null &&
this.fetcher.IsActive) {
invite_listener.InviteReceived += delegate (SparkleInvite invite) {
if (OnInvite != null && !FirstRun)
OnInvite (invite);
if (AlertNotificationRaised != null)
AlertNotificationRaised ("SparkleShare Setup seems busy",
"Please wait for it to finish");
} else {
if (InviteReceived != null) {
SparkleInvite invite = new SparkleInvite (args.FullPath);
if (invite.IsValid) {
InviteReceived (invite);
} else {
invite = null;
if (AlertNotificationRaised != null)
AlertNotificationRaised ("Oh noes!",
"This invite seems screwed up...");
}
File.Delete (args.FullPath);
}
}
};
invite_listener.Start ();
new Thread (new ThreadStart (PopulateRepositories)).Start ();
}
@ -149,43 +203,11 @@ namespace SparkleShare {
}
// Uploads the user's public key to the server
public bool AcceptInvitation (string server, string folder, string token)
{
// The location of the user's public key for SparkleShare
string public_key_file_path = SparkleHelpers.CombineMore (SparkleConfig.DefaultConfig.HomePath,
".ssh", "sparkleshare." + UserEmail + ".key.pub");
if (!File.Exists (public_key_file_path))
return false;
StreamReader reader = new StreamReader (public_key_file_path);
string public_key = reader.ReadToEnd ();
reader.Close ();
string url = "https://" + server + "/?folder=" + folder +
"&token=" + token + "&pubkey=" + public_key;
SparkleHelpers.DebugInfo ("WebRequest", url);
HttpWebRequest request = (HttpWebRequest) WebRequest.Create (url);
HttpWebResponse response = (HttpWebResponse) request.GetResponse();
if (response.StatusCode == HttpStatusCode.OK) {
response.Close ();
return true;
} else {
response.Close ();
return false;
}
}
public List<string> Folders {
get {
List<string> folders = SparkleConfig.DefaultConfig.Folders;
folders.Sort ();
return folders;
}
}
@ -196,6 +218,7 @@ namespace SparkleShare {
List<string> hosts = SparkleConfig.DefaultConfig.HostsWithUsername;
hosts.AddRange(SparkleConfig.DefaultConfig.Hosts);
hosts.Sort ();
return hosts;
}
}
@ -205,27 +228,53 @@ namespace SparkleShare {
get {
List<string> unsynced_folders = new List<string> ();
foreach (SparkleRepoBase repo in Repositories) {
if (repo.HasUnsyncedChanges)
unsynced_folders.Add (repo.Name);
}
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.HasUnsyncedChanges)
unsynced_folders.Add (repo.Name);
}
}
return unsynced_folders;
}
}
public void ShowSetupWindow (PageType page_type)
{
if (ShowSetupWindowEvent != null)
ShowSetupWindowEvent (page_type);
}
public void ShowAboutWindow ()
{
if (ShowAboutWindowEvent != null)
ShowAboutWindowEvent ();
}
public void ShowEventLogWindow ()
{
if (ShowEventLogWindowEvent != null)
ShowEventLogWindowEvent ();
}
public List<SparkleChangeSet> GetLog ()
{
List<SparkleChangeSet> list = new List<SparkleChangeSet> ();
foreach (SparkleRepoBase repo in Repositories) {
List<SparkleChangeSet> change_sets = repo.GetChangeSets (30);
if (change_sets != null)
list.AddRange (change_sets);
else
SparkleHelpers.DebugInfo ("Log", "Could not create log for " + repo.Name);
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
List<SparkleChangeSet> change_sets = repo.GetChangeSets (30);
if (change_sets != null)
list.AddRange (change_sets);
else
SparkleHelpers.DebugInfo ("Log", "Could not create log for " + repo.Name);
}
}
list.Sort ((x, y) => (x.Timestamp.CompareTo (y.Timestamp)));
@ -245,10 +294,12 @@ namespace SparkleShare {
string path = Path.Combine (SparkleConfig.DefaultConfig.FoldersPath, name);
int log_size = 50;
foreach (SparkleRepoBase repo in Repositories) {
if (repo.LocalPath.Equals (path))
return repo.GetChangeSets (log_size);
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.LocalPath.Equals (path))
return repo.GetChangeSets (log_size);
}
}
return null;
@ -437,8 +488,8 @@ namespace SparkleShare {
if (File.Exists (change_set_avatar))
change_set_avatar = "file://" + change_set_avatar;
else
change_set_avatar = "<!-- $no-buddy-icon-background-image -->";
change_set_avatar = "<!-- $pixmaps-path -->/" + AssignAvatar (change_set.User.Email);
event_entry += "</dl>";
string timestamp = change_set.Timestamp.ToString ("H:mm");
@ -508,47 +559,26 @@ namespace SparkleShare {
}
// Creates a .desktop entry in autostart folder to
// start SparkleShare automatically at login
public abstract void EnableSystemAutostart ();
// Installs a launcher so the user can launch SparkleShare
// from the Internet category if needed
public abstract void InstallLauncher ();
// Adds the SparkleShare folder to the user's
// list of bookmarked places
public abstract void AddToBookmarks ();
// Creates the SparkleShare folder in the user's home folder
public abstract bool CreateSparkleShareFolder ();
// Opens the SparkleShare folder or an (optional) subfolder
public abstract void OpenSparkleShareFolder (string subfolder);
// Opens a file with the appropriate application
public abstract void OpenFile (string url);
// Fires events for the current syncing state
public void UpdateState ()
{
bool has_syncing_repos = false;
bool has_unsynced_repos = false;
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncDown ||
repo.Status == SyncStatus.SyncUp ||
repo.IsBuffering) {
has_syncing_repos = true;
} else if (repo.HasUnsyncedChanges) {
has_unsynced_repos = true;
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncDown ||
repo.Status == SyncStatus.SyncUp ||
repo.IsBuffering) {
has_syncing_repos = true;
} else if (repo.HasUnsyncedChanges) {
has_unsynced_repos = true;
}
}
}
if (has_syncing_repos) {
if (OnSyncing != null)
OnSyncing ();
@ -586,7 +616,6 @@ namespace SparkleShare {
repo.NewChangeSet += delegate (SparkleChangeSet change_set) {
if (NotificationRaised != null)
NotificationRaised (change_set);
};
@ -598,8 +627,9 @@ namespace SparkleShare {
};
repo.ConflictResolved += delegate {
if (ConflictNotificationRaised != null)
ConflictNotificationRaised ();
if (AlertNotificationRaised != null)
AlertNotificationRaised ("Conflict detected.",
"Don't worry, SparkleShare made a copy of each conflicting file.");
};
repo.SyncStatusChanged += delegate (SyncStatus status) {
@ -629,7 +659,10 @@ namespace SparkleShare {
};
Repositories.Add (repo);
lock (this.repo_lock) {
Repositories.Add (repo);
}
repo.Initialize ();
}
@ -640,14 +673,16 @@ namespace SparkleShare {
{
string folder_name = Path.GetFileName (folder_path);
for (int i = 0; i < Repositories.Count; i++) {
SparkleRepoBase repo = Repositories [i];
if (repo.Name.Equals (folder_name)) {
repo.Dispose ();
Repositories.Remove (repo);
repo = null;
break;
lock (this.repo_lock) {
for (int i = 0; i < Repositories.Count; i++) {
SparkleRepoBase repo = Repositories [i];
if (repo.Name.Equals (folder_name)) {
repo.Dispose ();
Repositories.Remove (repo);
repo = null;
break;
}
}
}
}
@ -657,8 +692,6 @@ namespace SparkleShare {
// folders in the SparkleShare folder
private void PopulateRepositories ()
{
Repositories = new List<SparkleRepoBase> ();
foreach (string folder_name in SparkleConfig.DefaultConfig.Folders) {
string folder_path = new SparkleFolder (folder_name).FullPath;
@ -701,34 +734,6 @@ namespace SparkleShare {
}
public string GetSize (string folder_name)
{
double folder_size = 0;
/* TODO
foreach (SparkleRepoBase repo in
Repositories.GetRange (0, Repositories.Count)) {
folder_size += repo.Size + repo.HistorySize;
}
*/
return FormatSize (folder_size);
}
public string GetHistorySize (string folder_name)
{
double folder_size = 0;
/* TODO
foreach (SparkleRepoBase repo in
Repositories.GetRange (0, Repositories.Count)) {
folder_size += repo.Size + repo.HistorySize;
}
*/
return FormatSize (folder_size);
}
// Format a file size nicely with small caps.
// Example: 1048576 becomes "1 ᴍʙ"
public string FormatSize (double byte_count)
@ -738,9 +743,9 @@ namespace SparkleShare {
else if (byte_count >= 1073741824)
return String.Format ("{0:##.##} ɢʙ", Math.Round (byte_count / 1073741824, 1));
else if (byte_count >= 1048576)
return String.Format ("{0:##.##} ᴍʙ", Math.Round (byte_count / 1048576, 1));
return String.Format ("{0:##.##} ᴍʙ", Math.Round (byte_count / 1048576, 0));
else if (byte_count >= 1024)
return String.Format ("{0:##.##} ᴋʙ", Math.Round (byte_count / 1024, 1));
return String.Format ("{0:##.##} ᴋʙ", Math.Round (byte_count / 1024, 0));
else
return byte_count.ToString () + " bytes";
}
@ -881,7 +886,6 @@ namespace SparkleShare {
}
foreach (string raw_email in emails) {
// Gravatar wants lowercase emails
string email = raw_email.ToLower ();
string avatar_file_path = Path.Combine (avatar_path, "avatar-" + email);
@ -896,9 +900,6 @@ namespace SparkleShare {
old_avatars.Add (email);
} catch (FileNotFoundException) {
// FIXME: For some reason the previous File.Exists () check
// doesn't cover all cases sometimes, so we catch any errors
if (old_avatars.Contains (email))
old_avatars.Remove (email);
}
@ -906,11 +907,11 @@ namespace SparkleShare {
} else if (this.failed_avatars.Contains (email)) {
break;
} else {
WebClient client = new WebClient ();
string url = "http://gravatar.com/avatar/" + GetMD5 (email) +
".jpg?s=" + size + "&d=404";
try {
// Fetch the avatar
byte [] buffer = client.DownloadData (url);
@ -930,11 +931,10 @@ namespace SparkleShare {
SparkleHelpers.DebugInfo ("Avatar", "Failed fetching gravatar for " + email);
// Stop downloading further avatars if we have no internet access
if (e.Status == WebExceptionStatus.Timeout){
if (e.Status == WebExceptionStatus.Timeout)
break;
} else {
else
this.failed_avatars.Add (email);
}
}
}
}
@ -969,10 +969,14 @@ namespace SparkleShare {
}
public void FetchFolder (string server, string remote_folder)
public void FetchFolder (string server, string remote_folder, string announcements_url)
{
server = server.Trim ();
remote_folder = remote_folder.Trim ();
server = server.Trim ();
remote_folder = remote_folder.Trim ();
if (announcements_url != null)
announcements_url = announcements_url.Trim ();
string tmp_path = SparkleConfig.DefaultConfig.TmpPath;
if (!Directory.Exists (tmp_path)) {
@ -1042,8 +1046,14 @@ namespace SparkleShare {
try {
Directory.Move (tmp_folder, target_folder_path);
SparkleConfig.DefaultConfig.AddFolder (target_folder_name, this.fetcher.RemoteUrl, backend);
if (!string.IsNullOrEmpty (announcements_url)) {
SparkleConfig.DefaultConfig.SetFolderOptionalAttribute (target_folder_name,
"announcements_url", announcements_url);
}
AddRepository (target_folder_path);
if (FolderFetched != null)
@ -1060,6 +1070,8 @@ namespace SparkleShare {
} catch (Exception e) {
SparkleHelpers.DebugInfo ("Controller", "Error moving folder: " + e.Message);
}
this.fetcher = null;
};
this.fetcher.Failed += delegate {
@ -1070,6 +1082,8 @@ namespace SparkleShare {
if (Directory.Exists (tmp_path))
Directory.Delete (tmp_path, true);
this.fetcher = null;
};
this.fetcher.ProgressChanged += delegate (double percentage) {
@ -1093,12 +1107,14 @@ namespace SparkleShare {
// quits if safe
public void TryQuit ()
{
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncUp ||
repo.Status == SyncStatus.SyncDown ||
repo.IsBuffering) {
return;
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Status == SyncStatus.SyncUp ||
repo.Status == SyncStatus.SyncDown ||
repo.IsBuffering) {
return;
}
}
}
@ -1108,8 +1124,10 @@ namespace SparkleShare {
public virtual void Quit ()
{
foreach (SparkleRepoBase repo in Repositories)
repo.Dispose ();
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories)
repo.Dispose ();
}
#if __MonoCS__
Environment.Exit (0);
@ -1124,9 +1142,11 @@ namespace SparkleShare {
folder_name = folder_name.Replace ("%20", " ");
note = note.Replace ("%20", " ");
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Name.Equals (folder_name))
repo.AddNote (revision, note);
lock (this.repo_lock) {
foreach (SparkleRepoBase repo in Repositories) {
if (repo.Name.Equals (folder_name))
repo.AddNote (revision, note);
}
}
}
@ -1138,13 +1158,25 @@ namespace SparkleShare {
private string AssignColor (string s)
{
string hash = GetMD5 (s).Substring (0, 8);
string hash = "0" + GetMD5 (s).Substring (0, 8);
string numbers = Regex.Replace (hash, "[a-z]", "");
int number = 3 + int.Parse (numbers);
int number = int.Parse (numbers);
return this.tango_palette [number % this.tango_palette.Length];
}
private string AssignAvatar (string s)
{
string hash = "0" + GetMD5 (s).Substring (0, 8);
string numbers = Regex.Replace (hash, "[a-z]", "");
int number = int.Parse (numbers);
string letters = "abcdefghijklmnopqrstuvwxyz";
return "avatar-" + letters [(number % 11)] + ".png";
}
// Creates an MD5 hash of input
private string GetMD5 (string s)
{

View file

@ -1,106 +0,0 @@
// SparkleShare, a collaboration and sharing tool.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General private License as published by
// the Free Software Foundation, either version 3 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 private License for more details.
//
// You should have received a copy of the GNU General private License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
using Gtk;
// TODO: Remove with Gtk3
namespace SparkleShare {
public class SparkleEntry : Entry {
private string example_text;
private bool example_text_active;
public SparkleEntry ()
{
ExampleTextActive = true;
FocusGrabbed += delegate { OnEntered (); };
ClipboardPasted += delegate { OnEntered (); };
FocusOutEvent += delegate {
if (Text.Equals ("") || Text == null)
ExampleTextActive = true;
if (ExampleTextActive)
UseExampleText ();
};
}
private void OnEntered ()
{
if (ExampleTextActive) {
ExampleTextActive = false;
Text = "";
UseNormalTextColor ();
}
}
public bool ExampleTextActive {
get {
return this.example_text_active;
}
set {
this.example_text_active = value;
if (this.example_text_active)
UseSecondaryTextColor ();
else
UseNormalTextColor ();
}
}
public string ExampleText
{
get {
return this.example_text;
}
set {
this.example_text = value;
if (this.example_text_active)
UseExampleText ();
}
}
private void UseExampleText ()
{
Text = this.example_text;
UseSecondaryTextColor ();
}
private void UseSecondaryTextColor ()
{
ModifyText (StateType.Normal, Style.Foreground (StateType.Insensitive));
}
private void UseNormalTextColor ()
{
ModifyText (StateType.Normal, Style.Foreground (StateType.Normal));
}
}
}

View file

@ -30,6 +30,7 @@ namespace SparkleShare {
public SparkleEventLogController Controller = new SparkleEventLogController ();
private Label size_label;
private Label history_label;
private HBox layout_horizontal;
private ComboBox combo_box;
private EventBox content_wrapper;
@ -54,14 +55,27 @@ namespace SparkleShare {
Resizable = true;
BorderWidth = 0;
Title = _("Recent Events");
Title = _("Recent Changes");
IconName = "folder-sparkleshare";
DeleteEvent += Close;
DeleteEvent += delegate (object o, DeleteEventArgs args) {
Controller.WindowClosed ();
args.RetVal = true;
};
this.size_label = new Label () {
Markup = "<b>Size:</b> … <b>History:</b> …"
Markup = "<b>Size:</b> …",
Xalign = 0
};
this.history_label = new Label () {
Markup = "<b>History:</b> …",
Xalign = 0
};
HBox layout_sizes = new HBox (false, 12);
layout_sizes.Add (this.size_label);
layout_sizes.Add (this.history_label);
VBox layout_vertical = new VBox (false, 0);
this.spinner = new SparkleSpinner (22);
@ -78,7 +92,7 @@ namespace SparkleShare {
this.web_view.NavigationRequested += delegate (object o, WebKit.NavigationRequestedArgs args) {
if (args.Request.Uri == this.link_status)
SparkleEventLogController.LinkClicked (args.Request.Uri);
Controller.LinkClicked (args.Request.Uri);
// Don't follow HREFs (as this would cause a page refresh)
if (!args.Request.Uri.Equals ("file:"))
@ -90,22 +104,33 @@ namespace SparkleShare {
this.spinner.Start ();
this.layout_horizontal = new HBox (false, 0);
this.layout_horizontal.PackStart (this.size_label, true, true, 0);
this.layout_horizontal.PackStart (new Label (" "), false, false, 0);
this.layout_horizontal = new HBox (true, 0);
this.layout_horizontal.PackStart (layout_sizes, true, true, 12);
layout_vertical.PackStart (this.layout_horizontal, false, false, 0);
layout_vertical.PackStart (CreateShortcutsBar (), false, false, 0);
layout_vertical.PackStart (this.content_wrapper, true, true, 0);
Add (layout_vertical);
ShowAll ();
UpdateChooser (null);
UpdateContent (null);
// Hook up the controller events
Controller.HideWindowEvent += delegate {
Application.Invoke (delegate {
HideAll ();
});
};
Controller.ShowWindowEvent += delegate {
Application.Invoke (delegate {
ShowAll ();
Present ();
UpdateChooser (null);
UpdateContent (null);
});
};
Controller.UpdateChooserEvent += delegate (string [] folders) {
Application.Invoke (delegate {
UpdateChooser (folders);
@ -131,10 +156,11 @@ namespace SparkleShare {
Controller.UpdateSizeInfoEvent += delegate (string size, string history_size) {
Application.Invoke (delegate {
this.size_label.Markup = "<b>Size:</b> " + size + " " +
"<b>History:</b> " + history_size;
this.size_label.Markup = "<b>Size:</b> " + size;
this.history_label.Markup = "<b>History:</b> " + history_size;
this.size_label.ShowAll ();
this.history_label.ShowAll ();
});
};
}
@ -207,9 +233,8 @@ namespace SparkleShare {
html = html.Replace ("<!-- $day-entry-header-background-color -->", SparkleUIHelpers.GdkColorToHex (Style.Background (StateType.Normal)));
html = html.Replace ("<!-- $secondary-font-color -->", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive)));
html = html.Replace ("<!-- $small-color -->", SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive)));
html = html.Replace ("<!-- $no-buddy-icon-background-image -->", "file://" +
new string [] {SparkleUI.AssetsPath, "icons",
"hicolor", "32x32", "status", "avatar-default.png"}.Combine ());
html = html.Replace ("<!-- $pixmaps-path -->", "file://" +
new string [] {SparkleUI.AssetsPath, "pixmaps"}.Combine ());
html = html.Replace ("<!-- $document-added-background-image -->", "file://" +
new string [] {SparkleUI.AssetsPath, "icons",
"hicolor", "12x12", "status", "document-added.png"}.Combine ());
@ -223,6 +248,8 @@ namespace SparkleShare {
new string [] {SparkleUI.AssetsPath, "icons",
"hicolor", "12x12", "status", "document-moved.png"}.Combine ());
Console.WriteLine (html);
Application.Invoke (delegate {
this.spinner.Stop ();
this.web_view.LoadString (html, null, null, "file://");
@ -236,13 +263,6 @@ namespace SparkleShare {
}
public void Close (object o, DeleteEventArgs args)
{
HideAll ();
args.RetVal = true;
}
private MenuBar CreateShortcutsBar ()
{
// Adds a hidden menubar that contains to enable keyboard
@ -261,15 +281,15 @@ namespace SparkleShare {
AddAccelGroup (accel_group);
// Close on Esc
close_1.AddAccelerator ("activate", accel_group, new AccelKey (Gdk.Key.W, Gdk.ModifierType.ControlMask,
AccelFlags.Visible));
close_1.AddAccelerator ("activate", accel_group, new AccelKey (Gdk.Key.W,
Gdk.ModifierType.ControlMask, AccelFlags.Visible));
close_1.Activated += delegate { HideAll (); };
close_1.Activated += delegate { Controller.WindowClosed (); };
// Close on Ctrl+W
close_2.AddAccelerator ("activate", accel_group, new AccelKey (Gdk.Key.Escape, Gdk.ModifierType.None,
AccelFlags.Visible));
close_2.Activated += delegate { HideAll (); };
close_2.AddAccelerator ("activate", accel_group, new AccelKey (Gdk.Key.Escape,
Gdk.ModifierType.None, AccelFlags.Visible));
close_2.Activated += delegate { Controller.WindowClosed (); };
file_menu.Append (close_1);
file_menu.Append (close_2);

View file

@ -28,6 +28,12 @@ namespace SparkleShare {
public class SparkleEventLogController {
public event ShowWindowEventHandler ShowWindowEvent;
public delegate void ShowWindowEventHandler ();
public event HideWindowEventHandler HideWindowEvent;
public delegate void HideWindowEventHandler ();
public event UpdateContentEventEventHandler UpdateContentEvent;
public delegate void UpdateContentEventEventHandler (string html);
@ -146,6 +152,11 @@ namespace SparkleShare {
public SparkleEventLogController ()
{
Program.Controller.ShowEventLogWindowEvent += delegate {
if (ShowWindowEvent != null)
ShowWindowEvent ();
};
Program.Controller.AvatarFetched += delegate {
if (UpdateContentEvent != null)
UpdateContentEvent (HTML);
@ -178,7 +189,14 @@ namespace SparkleShare {
}
public static void LinkClicked (string url)
public void WindowClosed ()
{
if (HideWindowEvent != null)
HideWindowEvent ();
}
public void LinkClicked (string url)
{
if (url.StartsWith (Path.VolumeSeparatorChar.ToString ())) {
Program.Controller.OpenFile (url);

View file

@ -18,10 +18,8 @@
using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml;
using System.Threading;
using SparkleLib;
@ -29,32 +27,31 @@ namespace SparkleShare {
public class SparkleInvite {
public readonly Uri FullAddress;
public readonly string Token;
public string Address { get; private set; }
public string RemotePath { get; private set; }
public Uri AcceptUrl { get; private set; }
public Uri AnnouncementsUrl { get; private set; }
public string Host {
get {
return FullAddress.Host;
}
}
public string Path {
public bool IsValid {
get {
return FullAddress.AbsolutePath;
return (!string.IsNullOrEmpty (Address) &&
!string.IsNullOrEmpty (RemotePath));
}
}
public SparkleInvite (string host, string path, string token)
public SparkleInvite (string address, string remote_path,
string accept_url)
{
if (path.StartsWith ("/"))
path = path.Substring (1);
Initialize (address, remote_path, accept_url, null);
}
if (!host.EndsWith ("/"))
host = host + "/";
FullAddress = new Uri ("ssh://" + host + path);
Token = token;
public SparkleInvite (string address, string remote_path,
string accept_url, string announcements_url)
{
Initialize (address, remote_path, accept_url, announcements_url);
}
@ -63,166 +60,81 @@ namespace SparkleShare {
XmlDocument xml_document = new XmlDocument ();
XmlNode node;
string host = "", path = "", token = "";
string address = "";
string remote_path = "";
string accept_url = "";
string announcements_url = "";
try {
xml_document.Load (xml_file_path);
node = xml_document.SelectSingleNode ("/sparkleshare/invite/host/text()");
if (node != null) { host = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/address/text()");
if (node != null) { address = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/path/text()");
if (node != null) { path = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/remote_path/text()");
if (node != null) { remote_path = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/token/text()");
if (node != null) { token = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/accept_url/text()");
if (node != null) { accept_url = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/announcements_url/text()");
if (node != null) { announcements_url = node.Value; }
Initialize (address, remote_path, accept_url, announcements_url);
} catch (XmlException e) {
SparkleHelpers.DebugInfo ("Invite", "Invalid XML: " + e.Message);
return;
}
if (path.StartsWith ("/"))
path = path.Substring (1);
if (!host.EndsWith ("/"))
host = host + "/";
FullAddress = new Uri ("ssh://" + host + path);
Token = token;
}
}
public class SparkleInviteListener {
public event InviteReceivedHandler InviteReceived;
public delegate void InviteReceivedHandler (SparkleInvite invite);
private Thread thread;
private TcpListener tcp_listener;
public SparkleInviteListener (int port)
{
this.tcp_listener = new TcpListener (IPAddress.Loopback, port);
this.thread = new Thread(new ThreadStart (Listen));
}
public void Start ()
public bool Accept ()
{
this.thread.Start ();
}
if (AcceptUrl == null)
return true;
try {
WebRequest request = WebRequest.Create (AcceptUrl);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
string post_data = "pubkey=" + SparkleConfig.DefaultConfig.User.PublicKey;
byte [] post_bytes = Encoding.UTF8.GetBytes (post_data);
request.ContentLength = post_bytes.Length;
Stream data_stream = request.GetRequestStream ();
data_stream.Write (post_bytes, 0, post_bytes.Length);
data_stream.Close ();
WebResponse response = request.GetResponse ();
response.Close ();
private void Listen ()
{
this.tcp_listener.Start ();
while (true)
{
// Blocks until a client connects
TcpClient client = this.tcp_listener.AcceptTcpClient ();
// Create a thread to handle communications
Thread client_thread = new Thread (HandleClient);
client_thread.Start (client);
}
}
private void HandleClient (object client)
{
TcpClient tcp_client = (TcpClient) client;
NetworkStream client_stream = tcp_client.GetStream ();
byte [] message = new byte [4096];
int bytes_read;
while (true)
{
bytes_read = 0;
try {
// Blocks until the client sends a message
bytes_read = client_stream.Read (message, 0, 4096);
} catch {
Console.WriteLine ("Socket error...");
}
// The client has disconnected
if (bytes_read == 0)
break;
ASCIIEncoding encoding = new ASCIIEncoding ();
string received_message = encoding.GetString (message, 0, bytes_read);
string invite_xml = "";
if (received_message.StartsWith (Uri.UriSchemeHttp) ||
received_message.StartsWith (Uri.UriSchemeHttps)) {
WebClient web_client = new WebClient ();
try {
// Fetch the invite file
byte [] buffer = web_client.DownloadData (received_message);
SparkleHelpers.DebugInfo ("Invite", "Received: " + received_message);
invite_xml = ASCIIEncoding.ASCII.GetString (buffer);
} catch (WebException e) {
SparkleHelpers.DebugInfo ("Invite", "Failed downloading: " +
received_message + " " + e.Message);
continue;
}
} else if (received_message.StartsWith (Uri.UriSchemeFile)) {
try {
received_message = received_message.Replace (Uri.UriSchemeFile + "://", "");
invite_xml = File.ReadAllText (received_message);
} catch {
SparkleHelpers.DebugInfo ("Invite", "Failed opening: " + received_message);
continue;
}
if ((response as HttpWebResponse).StatusCode == HttpStatusCode.OK) {
SparkleHelpers.DebugInfo ("Invite", "Uploaded public key to " + AcceptUrl);
return true;
} else {
SparkleHelpers.DebugInfo ("Invite",
"Path to invite must use either the file:// or http(s):// scheme");
continue;
SparkleHelpers.DebugInfo ("Invite", "Failed uploading public key to " + AcceptUrl);
return false;
}
XmlDocument xml_document = new XmlDocument ();
XmlNode node;
} catch (WebException e) {
SparkleHelpers.DebugInfo ("Invite", "Failed uploading public key to " + AcceptUrl + ": " + e.Message);
string host = "", path = "", token = "";
try {
xml_document.LoadXml (invite_xml);
node = xml_document.SelectSingleNode ("/sparkleshare/invite/host/text()");
if (node != null) { host = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/path/text()");
if (node != null) { path = node.Value; }
node = xml_document.SelectSingleNode ("/sparkleshare/invite/token/text()");
if (node != null) { token = node.Value; }
} catch (XmlException e) {
SparkleHelpers.DebugInfo ("Invite", "Invalid XML: " + received_message + " " + e.Message);
return;
}
if (InviteReceived != null)
InviteReceived (new SparkleInvite (host, path, token));
return false;
}
}
tcp_client.Close ();
private void Initialize (string address, string remote_path,
string accept_url, string announcements_url)
{
Address = address;
RemotePath = remote_path;
AcceptUrl = new Uri (accept_url);
AnnouncementsUrl = new Uri (announcements_url);
}
}
}

View file

@ -49,6 +49,12 @@ namespace SparkleShare {
}
}
public string AnnouncementsUrl {
get {
return GetValue ("info", "announcements_url");
}
}
public string Address {
get {
return GetValue ("address", "value");

View file

@ -31,9 +31,6 @@ namespace SparkleShare {
public SparkleSetupController Controller = new SparkleSetupController ();
private string SecondaryTextColor;
private string SecondaryTextColorSelected;
private ProgressBar progress_bar = new ProgressBar ();
@ -46,15 +43,18 @@ namespace SparkleShare {
public SparkleSetup () : base ()
{
SecondaryTextColor = SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive));
SecondaryTextColorSelected =
SparkleUIHelpers.GdkColorToHex (
MixColors (
new TreeView ().Style.Foreground (StateType.Selected),
new TreeView ().Style.Background (StateType.Selected),
0.15
)
);
Controller.HideWindowEvent += delegate {
Application.Invoke (delegate {
HideAll ();
});
};
Controller.ShowWindowEvent += delegate {
Application.Invoke (delegate {
ShowAll ();
Present ();
});
};
Controller.ChangePageEvent += delegate (PageType type, string [] warnings) {
Application.Invoke (delegate {
@ -64,8 +64,8 @@ namespace SparkleShare {
case PageType.Setup: {
Header = _("Welcome to SparkleShare!");
Description = "We'll need some info to mark your changes in the event log. " +
"Don't worry, this stays between you and your peers.";
Description = "Before we get started, what's your name and email? " +
"Don't worry, this information is only visible to your team members.";
Table table = new Table (2, 3, true) {
@ -172,8 +172,10 @@ namespace SparkleShare {
tree.AppendColumn (service_column);
SparkleEntry path_entry = new SparkleEntry ();
SparkleEntry address_entry = new SparkleEntry ();
Entry address_entry = new Entry ();
Entry path_entry = new Entry ();
Label address_example = new Label ("1") { Xalign = 0, UseMarkup = true };
Label path_example = new Label ("2") { Xalign = 0, UseMarkup = true };
// Select the first plugin by default
@ -186,18 +188,11 @@ namespace SparkleShare {
string example_text, FieldState state) {
Application.Invoke (delegate {
address_entry.Text = text;
address_entry.Sensitive = (state == FieldState.Enabled);
address_entry.Text = text;
address_entry.Sensitive = (state == FieldState.Enabled);
address_example.Markup = "<span size=\"small\" fgcolor=\""
+ SecondaryTextColor + "\">" + example_text + "</span>";
if (string.IsNullOrEmpty (example_text))
address_entry.ExampleText = null;
else
address_entry.ExampleText = example_text;
if (string.IsNullOrEmpty (text))
address_entry.ExampleTextActive = true;
else
address_entry.ExampleTextActive = false;
});
};
@ -205,20 +200,13 @@ namespace SparkleShare {
string example_text, FieldState state) {
Application.Invoke (delegate {
path_entry.Text = text;
path_entry.Sensitive = (state == FieldState.Enabled);
if (string.IsNullOrEmpty (example_text))
path_entry.ExampleText = null;
else
path_entry.ExampleText = example_text;
if (string.IsNullOrEmpty (text))
path_entry.ExampleTextActive = true;
else
path_entry.ExampleTextActive = false;
path_entry.Text = text;
path_entry.Sensitive = (state == FieldState.Enabled);
path_example.Markup = "<span size=\"small\" fgcolor=\""
+ SecondaryTextColor + "\">" + example_text + "</span>";
});
};
// Update the address field text when the selection changes
tree.CursorChanged += delegate (object sender, EventArgs e) {
@ -272,11 +260,12 @@ namespace SparkleShare {
};
layout_address.PackStart (new Label () {
Markup = "<b>" + _("Address") + "</b>",
Markup = "<b>" + _("Address:") + "</b>",
Xalign = 0
}, true, true, 0);
layout_address.PackStart (address_entry, true, true, 0);
layout_address.PackStart (address_entry, false, false, 0);
layout_address.PackStart (address_example, false, false, 0);
path_entry.Completion = new EntryCompletion();
@ -293,11 +282,12 @@ namespace SparkleShare {
};
layout_path.PackStart (new Label () {
Markup = "<b>" + _("Remote Path") + "</b>",
Markup = "<b>" + _("Remote Path:") + "</b>",
Xalign = 0
}, true, true, 0);
layout_path.PackStart (path_entry, true, true, 0);
layout_path.PackStart (path_entry, false, false, 0);
layout_path.PackStart (path_example, false, false, 0);
layout_fields.PackStart (layout_address);
layout_fields.PackStart (layout_path);
@ -312,10 +302,9 @@ namespace SparkleShare {
Button cancel_button = new Button (_("Cancel"));
cancel_button.Clicked += delegate {
Close ();
Controller.PageCancelled ();
};
// Sync button
Button add_button = new Button (_("Add"));
add_button.Clicked += delegate {
@ -325,10 +314,72 @@ namespace SparkleShare {
Controller.AddPageCompleted (server, folder_name);
};
Controller.UpdateAddProjectButtonEvent += delegate (bool button_enabled) {
Application.Invoke (delegate {
add_button.Sensitive = button_enabled;
});
};
AddButton (cancel_button);
AddButton (add_button);
Controller.CheckAddPage (address_entry.Text, path_entry.Text, tree.SelectedRow);
Controller.CheckAddPage (address_entry.Text, path_entry.Text, 1);
break;
}
case PageType.Invite: {
Header = _("You've reveived an invite!");
Description = _("Do you want to add this project to SparkleShare?");
Table table = new Table (2, 3, true) {
RowSpacing = 6,
ColumnSpacing = 6
};
Label address_label = new Label (_("Address:")) {
Xalign = 1
};
Label path_label = new Label (_("Remote Path:")) {
Xalign = 1
};
Label address_value = new Label ("<b>" + Controller.PendingInvite.Address + "</b>") {
UseMarkup = true,
Xalign = 0
};
Label path_value = new Label ("<b>" + Controller.PendingInvite.RemotePath + "</b>") {
UseMarkup = true,
Xalign = 0
};
table.Attach (address_label, 0, 1, 0, 1);
table.Attach (address_value, 1, 2, 0, 1);
table.Attach (path_label, 0, 1, 1, 2);
table.Attach (path_value, 1, 2, 1, 2);
VBox wrapper = new VBox (false, 9);
wrapper.PackStart (table, true, false, 0);
Button cancel_button = new Button (_("Cancel"));
cancel_button.Clicked += delegate {
Controller.PageCancelled ();
};
Button add_button = new Button (_("Add"));
add_button.Clicked += delegate {
Controller.InvitePageCompleted ();
};
AddButton (cancel_button);
AddButton (add_button);
Add (wrapper);
break;
}
@ -455,14 +506,13 @@ namespace SparkleShare {
Button open_folder_button = new Button (_("Open Folder"));
open_folder_button.Clicked += delegate {
Program.Controller.OpenSparkleShareFolder (System.IO.Path.GetFileName (Controller.PreviousPath));
Controller.OpenFolderClicked ();
};
Button finish_button = new Button (_("Finish"));
finish_button.Clicked += delegate {
Controller.FinishedPageCompleted ();
Close ();
Controller.FinishPageCompleted ();
};
@ -502,8 +552,8 @@ namespace SparkleShare {
switch (Controller.TutorialPageNumber) {
case 1: {
Header = _("What's happening next?");
Description = _("SparkleShare creates a special folder in your personal folder " +
"that will keep track of your projects.");
Description = "SparkleShare creates a special folder on your computer " +
"that will keep track of your projects.";
Button skip_tutorial_button = new Button (_("Skip Tutorial"));
skip_tutorial_button.Clicked += delegate {
@ -527,8 +577,8 @@ namespace SparkleShare {
case 2: {
Header = _("Sharing files with others");
Description = _("All files added to your project folders are synced with the host " +
"automatically, as well as with your collaborators.");
Description = _("All files added to your project folders are synced automatically with " +
"the host and your team members.");
Button continue_button = new Button (_("Continue"));
continue_button.Clicked += delegate {
@ -545,8 +595,8 @@ namespace SparkleShare {
case 3: {
Header = _("The status icon is here to help");
Description = _("It shows the syncing process status, " +
"and contains links to your projects and the event log.");
Description = _("It shows the syncing progress, provides easy access to " +
"your projects and let's you view recent changes.");
Button continue_button = new Button (_("Continue"));
continue_button.Clicked += delegate {
@ -563,29 +613,25 @@ namespace SparkleShare {
case 4: {
Header = _("Adding projects to SparkleShare");
Description = _("Just click this button when you see it on the web, and " +
"the project will be automatically added:");
Label label = new Label (_("…or select <b>Add Hosted Project…</b> from the status icon menu " +
"to add one by hand.")) {
Wrap = true,
Xalign = 0,
UseMarkup = true
};
Description = _("You can do this through the status icon menu, or by clicking " +
"magic buttons on webpages that look like this:");
Image slide = SparkleUIHelpers.GetImage ("tutorial-slide-4.png");
Button finish_button = new Button (_("Finish"));
finish_button.Clicked += delegate {
Close ();
Controller.FinishPageCompleted ();
};
VBox box = new VBox (false, 0);
box.Add (slide);
box.Add (label);
check_button = new CheckButton ("Add SparkleShare to startup items");
Add (box);
check_button.Toggled += delegate {
Controller.StartupItemChanged (check_button.Active);
};
Add (slide);
AddOption (check_button);
AddButton (finish_button);
break;
@ -615,16 +661,6 @@ namespace SparkleShare {
(cell as CellRendererText).Markup = markup;
}
private Gdk.Color MixColors (Gdk.Color first_color, Gdk.Color second_color, double ratio)
{
return new Gdk.Color (
Convert.ToByte ((255 * (Math.Min (65535, first_color.Red * (1.0 - ratio) + second_color.Red * ratio))) / 65535),
Convert.ToByte ((255 * (Math.Min (65535, first_color.Green * (1.0 - ratio) + second_color.Green * ratio))) / 65535),
Convert.ToByte ((255 * (Math.Min (65535, first_color.Blue * (1.0 - ratio) + second_color.Blue * ratio))) / 65535)
);
}
}

View file

@ -19,6 +19,7 @@ using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading;
using SparkleLib;
@ -27,6 +28,7 @@ namespace SparkleShare {
public enum PageType {
Setup,
Add,
Invite,
Syncing,
Error,
Finished,
@ -41,6 +43,12 @@ namespace SparkleShare {
public class SparkleSetupController {
public event ShowWindowEventHandler ShowWindowEvent;
public delegate void ShowWindowEventHandler ();
public event HideWindowEventHandler HideWindowEvent;
public delegate void HideWindowEventHandler ();
public event ChangePageEventHandler ChangePageEvent;
public delegate void ChangePageEventHandler (PageType page, string [] warnings);
@ -58,12 +66,18 @@ namespace SparkleShare {
string example_text, FieldState state);
public event ChangePathFieldEventHandler ChangePathFieldEvent;
public delegate void ChangePathFieldEventHandler (string text,
string example_text, FieldState state);
public delegate void ChangePathFieldEventHandler (string text, string example_text, FieldState state);
public readonly List<SparklePlugin> Plugins = new List<SparklePlugin> ();
public SparklePlugin SelectedPlugin;
public SparkleInvite PendingInvite { get; private set; }
public int TutorialPageNumber { get; private set; }
public string PreviousUrl { get; private set; }
public string PreviousAddress { get; private set; }
public string PreviousPath { get; private set; }
public string SyncingFolder { get; private set; }
public int SelectedPluginIndex {
get {
@ -71,42 +85,6 @@ namespace SparkleShare {
}
}
public int TutorialPageNumber {
get {
return this.tutorial_page_number;
}
}
public string PreviousUrl {
get {
return this.previous_url;
}
}
public string PreviousAddress {
get {
return this.previous_address;
}
}
public string PreviousPath {
get {
return this.previous_path;
}
}
public string SyncingFolder {
get {
return this.syncing_folder;
}
}
public PageType PreviousPage {
get {
return this.previous_page;
}
}
public string GuessedUserName {
get {
return Program.Controller.UserName;
@ -123,16 +101,19 @@ namespace SparkleShare {
}
private string previous_address = "";
private string previous_path = "";
private string previous_url = "";
private string syncing_folder = "";
private int tutorial_page_number = 1;
private PageType previous_page;
private string saved_address = "";
private string saved_remote_path = "";
private bool create_startup_item = true;
public SparkleSetupController ()
{
TutorialPageNumber = 0;
PreviousAddress = "";
PreviousPath = "";
PreviousUrl = "";
SyncingFolder = "";
string local_plugins_path = SparkleHelpers.CombineMore (
Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData),
"sparkleshare", "plugins");
@ -152,16 +133,57 @@ namespace SparkleShare {
SelectedPlugin = Plugins [0];
ChangePageEvent += delegate (PageType page, string [] warning) {
this.previous_page = page;
Program.Controller.InviteReceived += delegate (SparkleInvite invite) {
if (ChangePageEvent != null)
ChangePageEvent (PageType.Invite, null);
if (ShowWindowEvent != null)
ShowWindowEvent ();
};
Program.Controller.ShowSetupWindowEvent += delegate (PageType page_type) {
if (PendingInvite != null) {
if (ShowWindowEvent != null)
ShowWindowEvent ();
return;
}
if (page_type == PageType.Add) {
if (!Program.Controller.FirstRun &&
(TutorialPageNumber == 0 ||
TutorialPageNumber > 4)) {
if (ChangePageEvent != null)
ChangePageEvent (page_type, null);
if (ShowWindowEvent != null)
ShowWindowEvent ();
SelectedPluginChanged (SelectedPluginIndex);
}
return;
}
if (ChangePageEvent != null)
ChangePageEvent (page_type, null);
if (ShowWindowEvent != null)
ShowWindowEvent ();
};
}
public void ShowSetupPage ()
public void PageCancelled ()
{
if (ChangePageEvent != null)
ChangePageEvent (PageType.Setup, null);
PendingInvite = null;
if (HideWindowEvent != null)
HideWindowEvent ();
}
@ -185,16 +207,8 @@ namespace SparkleShare {
Program.Controller.GenerateKeyPair ();
Program.Controller.ImportPrivateKey ();
Program.Controller.UpdateState ();
if (ChangePageEvent != null)
ChangePageEvent (PageType.Tutorial, null);
}
public void TutorialPageCompleted ()
{
this.tutorial_page_number++;
TutorialPageNumber = 1;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Tutorial, null);
@ -203,101 +217,34 @@ namespace SparkleShare {
public void TutorialSkipped ()
{
this.tutorial_page_number = 4;
TutorialPageNumber = 4;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Tutorial, null);
}
public void ShowAddPage ()
public void StartupItemChanged (bool create_startup_item)
{
if (ChangePageEvent != null)
ChangePageEvent (PageType.Add, null);
SelectedPluginChanged (SelectedPluginIndex);
this.create_startup_item = create_startup_item;
}
public void CheckAddPage (string address, string remote_path, int selected_plugin)
public void TutorialPageCompleted ()
{
if (SelectedPluginIndex != selected_plugin)
SelectedPluginChanged (selected_plugin);
TutorialPageNumber++;
address = address.Trim ();
remote_path = remote_path.Trim ();
if (TutorialPageNumber == 5) {
if (HideWindowEvent != null)
HideWindowEvent ();
bool fields_valid = address != null && address.Trim().Length > 0 &&
remote_path != null && remote_path.Trim().Length > 0;
if (this.create_startup_item)
Program.Controller.CreateStartupItem ();
if (UpdateAddProjectButtonEvent != null)
UpdateAddProjectButtonEvent (fields_valid);
}
public void AddPageCompleted (string address, string path)
{
this.syncing_folder = Path.GetFileNameWithoutExtension (path);
this.previous_address = address;
this.previous_path = path;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Syncing, null);
// TODO: Remove events afterwards
Program.Controller.FolderFetched += delegate (string [] warnings) {
} else {
if (ChangePageEvent != null)
ChangePageEvent (PageType.Finished, warnings);
this.previous_address = "";
this.syncing_folder = "";
this.previous_url = "";
SelectedPlugin = Plugins [0];
};
Program.Controller.FolderFetchError += delegate (string remote_url) {
this.previous_url = remote_url;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Error, null);
this.syncing_folder = "";
};
Program.Controller.FolderFetching += delegate (double percentage) {
if (UpdateProgressBarEvent != null)
UpdateProgressBarEvent (percentage);
};
Program.Controller.FetchFolder (address, path);
}
public void ErrorPageCompleted ()
{
if (ChangePageEvent != null)
ChangePageEvent (PageType.Add, null);
}
public void SyncingCancelled ()
{
Program.Controller.StopFetcher ();
if (ChangePageEvent != null)
ChangePageEvent (PageType.Add, null);
}
// TODO: public void WindowClosed () { }
public void FinishedPageCompleted ()
{
this.previous_address = "";
this.previous_path = "";
Program.Controller.UpdateState ();
ChangePageEvent (PageType.Tutorial, null);
}
}
@ -311,10 +258,10 @@ namespace SparkleShare {
} else if (SelectedPlugin.AddressExample != null) {
if (ChangeAddressFieldEvent != null)
ChangeAddressFieldEvent ("", SelectedPlugin.AddressExample, FieldState.Enabled);
ChangeAddressFieldEvent (this.saved_address, SelectedPlugin.AddressExample, FieldState.Enabled);
} else {
if (ChangeAddressFieldEvent != null)
ChangeAddressFieldEvent ("", "", FieldState.Enabled);
ChangeAddressFieldEvent (this.saved_address, "", FieldState.Enabled);
}
if (SelectedPlugin.Path != null) {
@ -323,15 +270,175 @@ namespace SparkleShare {
} else if (SelectedPlugin.PathExample != null) {
if (ChangePathFieldEvent != null)
ChangePathFieldEvent ("", SelectedPlugin.PathExample, FieldState.Enabled);
ChangePathFieldEvent (this.saved_remote_path, SelectedPlugin.PathExample, FieldState.Enabled);
} else {
if (ChangePathFieldEvent != null)
ChangePathFieldEvent ("", "", FieldState.Enabled);
ChangePathFieldEvent (this.saved_remote_path, "", FieldState.Enabled);
}
}
public void CheckAddPage (string address, string remote_path, int selected_plugin)
{
if (SelectedPluginIndex != selected_plugin)
SelectedPluginChanged (selected_plugin);
address = address.Trim ();
remote_path = remote_path.Trim ();
if (selected_plugin == 0)
this.saved_address = address;
this.saved_remote_path = remote_path;
bool fields_valid = address != null && address.Trim ().Length > 0 &&
remote_path != null && remote_path.Trim ().Length > 0;
if (UpdateAddProjectButtonEvent != null)
UpdateAddProjectButtonEvent (fields_valid);
}
public void AddPageCompleted (string address, string path)
{
SyncingFolder = Path.GetFileNameWithoutExtension (path);
PreviousAddress = address;
PreviousPath = path;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Syncing, null);
// TODO: Remove events afterwards
Program.Controller.FolderFetched += delegate (string [] warnings) {
if (ChangePageEvent != null)
ChangePageEvent (PageType.Finished, warnings);
PreviousAddress = "";
SyncingFolder = "";
PreviousUrl = "";
SelectedPlugin = Plugins [0];
};
Program.Controller.FolderFetchError += delegate (string remote_url) {
Thread.Sleep (1000);
PreviousUrl = remote_url;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Error, null);
SyncingFolder = "";
};
Program.Controller.FolderFetching += delegate (double percentage) {
if (UpdateProgressBarEvent != null)
UpdateProgressBarEvent (percentage);
};
Program.Controller.FetchFolder (address, path, SelectedPlugin.AnnouncementsUrl);
}
public void InvitePageCompleted ()
{
SyncingFolder = Path.GetFileNameWithoutExtension (PendingInvite.RemotePath);
PreviousAddress = PendingInvite.Address;
// TODO: trailing slash should work
PreviousPath = PendingInvite.RemotePath;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Syncing, null);
if (!PendingInvite.Accept ()) {
if (ChangePageEvent != null)
ChangePageEvent (PageType.Error, null);
return;
}
// TODO: Remove events afterwards
Program.Controller.FolderFetched += delegate (string [] warnings) {
if (ChangePageEvent != null)
ChangePageEvent (PageType.Finished, warnings);
PreviousAddress = "";
SyncingFolder = "";
PreviousUrl = "";
SelectedPlugin = Plugins [0];
PendingInvite = null;
};
Program.Controller.FolderFetchError += delegate (string remote_url) {
Thread.Sleep (1000);
PreviousUrl = remote_url;
if (ChangePageEvent != null)
ChangePageEvent (PageType.Error, null);
SyncingFolder = "";
};
Program.Controller.FolderFetching += delegate (double percentage) {
if (UpdateProgressBarEvent != null)
UpdateProgressBarEvent (percentage);
};
Program.Controller.FetchFolder (PendingInvite.Address,
PendingInvite.RemotePath, PendingInvite.AnnouncementsUrl.ToString ());
}
public void SyncingCancelled ()
{
Program.Controller.StopFetcher ();
if (ChangePageEvent == null)
return;
if (PendingInvite != null)
ChangePageEvent (PageType.Invite, null);
else
ChangePageEvent (PageType.Add, null);
}
public void ErrorPageCompleted ()
{
if (ChangePageEvent == null)
return;
if (PendingInvite != null)
ChangePageEvent (PageType.Invite, null);
else
ChangePageEvent (PageType.Add, null);
}
public void OpenFolderClicked ()
{
Program.Controller.OpenSparkleShareFolder (
Path.GetFileName (PreviousPath));
FinishPageCompleted ();
}
public void FinishPageCompleted ()
{
PreviousAddress = "";
PreviousPath = "";
Program.Controller.UpdateState ();
if (HideWindowEvent != null)
HideWindowEvent ();
}
private bool IsValidEmail (string email)
{
Regex regex = new Regex (@"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$",

View file

@ -29,13 +29,18 @@ namespace SparkleShare {
public class SparkleSetupWindow : Window {
// TODO: caps
private HBox HBox;
private VBox VBox;
private VBox Wrapper;
private VBox OptionArea;
private HButtonBox Buttons;
public string Header;
public string Description;
public string SecondaryTextColor;
public string SecondaryTextColorSelected;
public Container Content;
@ -48,25 +53,43 @@ namespace SparkleShare {
WindowPosition = WindowPosition.Center;
Deletable = false;
SetSizeRequest (680, 440);
SecondaryTextColor = SparkleUIHelpers.GdkColorToHex (Style.Foreground (StateType.Insensitive));
SecondaryTextColorSelected =
SparkleUIHelpers.GdkColorToHex (
MixColors (
new TreeView ().Style.Foreground (StateType.Selected),
new TreeView ().Style.Background (StateType.Selected),
0.15
)
);
DeleteEvent += delegate (object o, DeleteEventArgs args) {
args.RetVal = true;
Close ();
};
SetSizeRequest (680, 400);
HBox = new HBox (false, 6);
HBox = new HBox (false, 0);
VBox = new VBox (false, 0);
Wrapper = new VBox (false, 0) {
BorderWidth = 30
BorderWidth = 0
};
OptionArea = new VBox (false, 0) {
BorderWidth = 0
};
Buttons = CreateButtonBox ();
HBox layout_horizontal = new HBox (false , 0) {
BorderWidth = 0
};
layout_horizontal.PackStart (OptionArea, true, true, 0);
layout_horizontal.PackStart (Buttons, false, false, 0);
VBox.PackStart (Wrapper, true, true, 0);
VBox.PackStart (Buttons, false, false, 0);
VBox.PackStart (layout_horizontal, false, false, 15);
EventBox box = new EventBox ();
Gdk.Color bg_color = new Gdk.Color ();
@ -79,7 +102,7 @@ namespace SparkleShare {
box.Add (side_splash);
HBox.PackStart (box, false, false, 0);
HBox.PackStart (VBox, true, true, 0);
HBox.PackStart (VBox, true, true, 30);
base.Add (HBox);
}
@ -88,7 +111,7 @@ namespace SparkleShare {
private HButtonBox CreateButtonBox ()
{
return new HButtonBox () {
BorderWidth = 12,
BorderWidth = 0,
Layout = ButtonBoxStyle.End,
Spacing = 6
};
@ -102,11 +125,18 @@ namespace SparkleShare {
}
public void AddOption (Widget widget)
{
OptionArea.Add (widget);
ShowAll ();
}
new public void Add (Widget widget)
{
Label header = new Label ("<span size='large'><b>" + Header + "</b></span>") {
UseMarkup = true,
Xalign = 0
Xalign = 0,
};
Label description = new Label (Description) {
@ -115,6 +145,7 @@ namespace SparkleShare {
};
VBox layout_vertical = new VBox (false, 0);
layout_vertical.PackStart (new Label (""), false, false, 6);
layout_vertical.PackStart (header, false, false, 0);
if (!string.IsNullOrEmpty (Description))
@ -142,15 +173,24 @@ namespace SparkleShare {
ShowAll ();
}
new public void ShowAll ()
{
Present ();
base.ShowAll ();
}
public void Close ()
private Gdk.Color MixColors (Gdk.Color first_color, Gdk.Color second_color, double ratio)
{
HideAll ();
return new Gdk.Color (
Convert.ToByte ((255 * (Math.Min (65535, first_color.Red * (1.0 - ratio) +
second_color.Red * ratio))) / 65535),
Convert.ToByte ((255 * (Math.Min (65535, first_color.Green * (1.0 - ratio) +
second_color.Green * ratio))) / 65535),
Convert.ToByte ((255 * (Math.Min (65535, first_color.Blue * (1.0 - ratio) +
second_color.Blue * ratio))) / 65535)
);
}
}
}

View file

@ -75,7 +75,7 @@ namespace SparkleShare {
if (Controller.Folders.Length == 0)
this.state_text = _("Welcome to SparkleShare!");
else
this.state_text = _("Up to date") + Controller.FolderSize;
this.state_text = _("Files up to date") + Controller.FolderSize;
CreateMenu ();
@ -99,7 +99,7 @@ namespace SparkleShare {
if (Controller.Folders.Length == 0)
this.state_text = _("Welcome to SparkleShare!");
else
this.state_text = _("Up to date") + Controller.FolderSize;
this.state_text = _("Files up to date") + Controller.FolderSize;
#if HAVE_APP_INDICATOR
this.indicator.IconName = "process-syncing-sparkleshare-i";
@ -209,7 +209,7 @@ namespace SparkleShare {
};
folder_item.Activated += delegate {
Program.Controller.OpenSparkleShareFolder ();
Controller.SparkleShareClicked ();
};
this.menu.Add (folder_item);
@ -249,41 +249,20 @@ namespace SparkleShare {
// Opens the wizard to add a new remote folder
MenuItem sync_item = new MenuItem (_("Add Hosted Project…"));
if (Program.Controller.FirstRun)
sync_item.Sensitive = false;
sync_item.Activated += delegate {
Application.Invoke (delegate {
if (SparkleUI.Setup == null) {
SparkleUI.Setup = new SparkleSetup ();
SparkleUI.Setup.Controller.ShowAddPage ();
}
if (!SparkleUI.Setup.Visible)
SparkleUI.Setup.Controller.ShowAddPage ();
//SparkleUI.Intro.ShowAll ();
//SparkleUI.Intro.Present ();
});
Controller.AddHostedProjectClicked ();
};
this.menu.Add (sync_item);
this.menu.Add (new SeparatorMenuItem ());
MenuItem recent_events_item = new MenuItem (_("Open Recent Events"));
MenuItem recent_events_item = new MenuItem (_("View Recent Changes…"));
recent_events_item.Sensitive = (Controller.Folders.Length > 0);
recent_events_item.Activated += delegate {
Application.Invoke (delegate {
if (SparkleUI.EventLog == null)
SparkleUI.EventLog = new SparkleEventLog ();
SparkleUI.EventLog.ShowAll ();
SparkleUI.EventLog.Present ();
});
Controller.OpenRecentEventsClicked ();
};
this.menu.Add (recent_events_item);
@ -307,13 +286,7 @@ namespace SparkleShare {
MenuItem about_item = new MenuItem (_("About SparkleShare"));
about_item.Activated += delegate {
Application.Invoke (delegate {
if (SparkleUI.About == null)
SparkleUI.About = new SparkleAbout ();
SparkleUI.About.ShowAll ();
SparkleUI.About.Present ();
});
Controller.AboutClicked ();
};
this.menu.Add (about_item);
@ -342,7 +315,7 @@ namespace SparkleShare {
private EventHandler OpenFolderDelegate (string name)
{
return delegate {
Program.Controller.OpenSparkleShareFolder (name);
Controller.SubfolderClicked (name);
};
}

View file

@ -129,5 +129,35 @@ namespace SparkleShare {
UpdateMenuEvent (IconState.Error);
};
}
public void SparkleShareClicked ()
{
Program.Controller.OpenSparkleShareFolder ();
}
public void SubfolderClicked (string subfolder)
{
Program.Controller.OpenSparkleShareFolder (subfolder);
}
public void AddHostedProjectClicked ()
{
Program.Controller.ShowSetupWindow (PageType.Add);
}
public void OpenRecentEventsClicked ()
{
Program.Controller.ShowEventLogWindow ();
}
public void AboutClicked ()
{
Program.Controller.ShowAboutWindow ();
}
}
}

View file

@ -41,6 +41,7 @@ namespace SparkleShare {
public static SparkleBubbles Bubbles;
public static SparkleSetup Setup;
public static SparkleAbout About;
public static string AssetsPath =
new string [] {Defines.PREFIX, "share", "sparkleshare"}.Combine ();
@ -62,13 +63,14 @@ namespace SparkleShare {
Catalog.Init (Defines.GETTEXT_PACKAGE, Defines.LOCALE_DIR);
#endif
StatusIcon = new SparkleStatusIcon ();
Setup = new SparkleSetup ();
EventLog = new SparkleEventLog ();
About = new SparkleAbout ();
Bubbles = new SparkleBubbles ();
if (Program.Controller.FirstRun) {
Setup = new SparkleSetup ();
Setup.Controller.ShowSetupPage ();
}
StatusIcon = new SparkleStatusIcon ();
if (Program.Controller.FirstRun)
Program.Controller.ShowSetupWindow (PageType.Setup);
}

View file

@ -0,0 +1,66 @@
// SparkleShare, a collaboration and sharing tool.
// Copyright (C) 2010 Hylke Bons <hylkebons@gmail.com>
//
// 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 3 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, see <http://www.gnu.org/licenses/>.
using System;
using System.IO;
using System.Net;
namespace SparkleShare {
public class SparkleShare {
public static void Main (string [] args) {
new SparkleInviteOpen (args [0]);
}
}
public class SparkleInviteOpen {
public SparkleInviteOpen (string url)
{
string safe_url = url.Replace ("sparkleshare://", "https://");
string unsafe_url = url.Replace ("sparkleshare://", "http://");
string xml = "";
WebClient web_client = new WebClient ();
try {
xml = web_client.DownloadString (safe_url);
} catch {
Console.WriteLine ("Failed downloading invite: " + safe_url);
try {
xml = web_client.DownloadString (safe_url);
} catch {
Console.WriteLine ("Failed downloading invite: " + unsafe_url);
}
}
string home_path = Environment.GetFolderPath (Environment.SpecialFolder.Personal);
string target_path = Path.Combine (home_path, "SparkleShare");
if (xml.Contains ("<sparkleshare>")) {
File.WriteAllText (target_path, xml);
Console.WriteLine ("Downloaded invite: " + safe_url);
}
}
}
}

View file

@ -1,9 +1,9 @@
dnl Process this file with autoconf to produce a configure script.
m4_define([sparkleshare_version],
[0.8.2])
[0.8.3])
m4_define([sparkleshare_asm_version],
[0.8.2])
[0.8.3])
AC_PREREQ([2.54])
AC_INIT([SparkleShare], sparkleshare_version)

View file

@ -9,6 +9,17 @@ dist_pixmaps_DATA = \
tutorial-slide-2.png \
tutorial-slide-3.png \
tutorial-slide-4.png \
avatar-a.png \
avatar-b.png \
avatar-c.png \
avatar-d.png \
avatar-e.png \
avatar-f.png \
avatar-g.png \
avatar-h.png \
avatar-i.png \
avatar-j.png \
avatar-k.png \
about.png
pixmapsdir = $(pkgdatadir)/pixmaps/

BIN
data/avatar-a.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 650 B

BIN
data/avatar-b.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 751 B

BIN
data/avatar-c.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 989 B

BIN
data/avatar-d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

BIN
data/avatar-e.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

BIN
data/avatar-f.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

BIN
data/avatar-g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 771 B

BIN
data/avatar-h.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

BIN
data/avatar-i.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 666 B

BIN
data/avatar-j.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

BIN
data/avatar-k.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 669 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<sparkleshare>
<invite>
<address>ssh://git@github.com/</address>
<remote_path>/hbons/Stuff</remote_path>
<accept_url>http://www.sparkleshare.org/test.php</accept_url>
</invite>
</sparkleshare>

View file

@ -0,0 +1,38 @@
<html>
<head>
<title>SparkleShare Protocol Handler Test</title>
<style>
body {
background-color: #58595b;
}
div {
text-align: center;
margin: auto;
width: 300px;
height: 50px;
position: absolute;
left: 50%;
top: 50%;
margin-left: -150px;
margin-top: -25px;
color: #ddd;
font-family: sans-serif;
font-size: 13px;
}
img {
padding-top: 5px;
}
</style>
</head>
<body>
<div>
<b>hbons's stuff</b> on <b>Github</b>
<a href="sparkleshare://www.sparkleshare.org/test-invite.xml">
<img src="add-to-sparkleshare-button.png" alt="Add to SparkleShare">
</a>
</div>
</body>
</html>

View file

@ -24,7 +24,7 @@ app_theme_icons = \
places,folder-sparkleshare-256.png \
places,folder-sparkleshare-32.png \
places,folder-sparkleshare-48.png \
status,sparkleshare-syncing-error-24.png \
status,sparkleshare-syncing-error-24.png \
status,avatar-default-16.png \
status,avatar-default-22.png \
status,avatar-default-24.png \

View file

@ -9,7 +9,7 @@
</info>
<address>
<value></value>
<example>domain name or IP address</example>
<example>[user@]hostname[:port]</example>
</address>
<path>
<value></value>
@ -17,4 +17,3 @@
</path>
</plugin>
</sparkleshare>

676
data/src/avatars.svg Normal file
View file

@ -0,0 +1,676 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="744.09448819"
height="1052.3622047"
id="svg2"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="drawing-1.svg.2012_02_21_21_26_37.0.svg">
<defs
id="defs4" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="0.5"
inkscape:cx="293.93245"
inkscape:cy="451.01426"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1256"
inkscape:window-height="744"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:snap-nodes="false"
inkscape:snap-bbox="true">
<inkscape:grid
type="xygrid"
id="grid4845" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect2991"
width="48"
height="48"
x="216.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-9.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
y="96.862183"
x="166.5"
height="48"
width="48"
id="rect4811"
style="fill:#ededed;fill-opacity:1;stroke:none"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-10.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect4813"
width="48"
height="48"
x="116.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-11.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
y="96.862183"
x="66.5"
height="48"
width="48"
id="rect4815"
style="fill:#ededed;fill-opacity:1;stroke:none" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect4817"
width="48"
height="48"
x="266.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-8.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
y="96.862183"
x="321.5"
height="48"
width="48"
id="rect4819"
style="fill:#ededed;fill-opacity:1;stroke:none"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-7.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect4821"
width="48"
height="48"
x="376.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-6.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect4825"
width="48"
height="48"
x="476.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-4.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
y="96.862183"
x="531.5"
height="48"
width="48"
id="rect4827"
style="fill:#ededed;fill-opacity:1;stroke:none"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-3.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
style="fill:#ededed;fill-opacity:1;stroke:none"
id="rect4829"
width="48"
height="48"
x="586.5"
y="96.862183"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-2.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<rect
y="96.862183"
x="641.5"
height="48"
width="48"
id="rect4831"
style="fill:#ededed;fill-opacity:1;stroke:none"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-1.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<g
id="g4795"
transform="translate(81.110528,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4669"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(1.125679,0,0,1.125679,128.51617,67.325585)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4671"
width="31.384169"
height="28.585962"
x="513.69739"
y="595.06921"
rx="11.508705"
ry="11.508705" />
<g
style="fill:#ffffff;fill-opacity:1"
transform="matrix(1.6967093,0,0,1.6967093,-190.21941,-314.59627)"
id="g4681">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4673"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(0.33194096,0,0,0.33194096,305.76396,368.83335)" />
<path
transform="matrix(0.33194096,0,0,0.33194096,301.01378,368.83335)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4675"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4677"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(0.33194096,0,0,0.33194096,310.51413,368.83335)" />
</g>
<rect
y="569.68445"
x="519.21381"
height="14.71875"
width="19.3125"
id="rect4679"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<g
id="g4804"
transform="translate(98.610528,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<path
transform="matrix(1.125679,0,0,1.125679,166.01617,67.325585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4705"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="551.19739"
height="28.585962"
width="31.384169"
id="rect4707"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4717"
width="19.3125"
height="20.022051"
x="556.71381"
y="564.38116" />
<rect
y="577.89685"
x="553.00146"
height="1.990828"
width="26.737122"
id="rect4719"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<rect
y="96.862183"
x="426.5"
height="48"
width="48"
id="rect4862"
style="fill:#ededed;fill-opacity:1;stroke:none"
inkscape:export-filename="/Users/hbons/Code/SparkleShare/data/icons/avatar-5.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90" />
<g
id="g4758"
transform="translate(33.139669,-463.98928)"
style="fill:#ffffff;fill-opacity:1">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4808"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(1.125679,0,0,1.125679,16.01617,67.325585)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4810"
width="31.384169"
height="28.585962"
x="401.19742"
y="595.06921"
rx="11.508705"
ry="11.508705" />
<rect
ry="0.76949799"
rx="0.55169797"
y="578.56921"
x="405.0878"
height="4.0581961"
width="22.501141"
id="rect4818"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
transform="matrix(1.125679,0,0,1.125679,16.01617,62.825585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4820"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<path
transform="matrix(0.31445451,0,0,0.31445451,304.40392,423.58816)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4822"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="g4750"
transform="translate(21.110497,-463.5)">
<path
transform="matrix(1.125679,0,0,1.125679,-21.507206,67.325585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4824"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="363.69742"
height="28.585962"
width="31.384169"
id="rect4826"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4828"
width="4.7350821"
height="7.6379242"
x="387.60385"
y="580.06921"
rx="2.3675411"
ry="2.3675411" />
<rect
ry="2.3675411"
rx="2.3675411"
y="580.06921"
x="365.10385"
height="7.6379242"
width="4.7350821"
id="rect4834"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
style="font-size:medium;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-indent:0;text-align:start;text-decoration:none;line-height:normal;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;text-anchor:start;baseline-shift:baseline;color:#000000;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate;font-family:Sans;-inkscape-font-specification:Sans"
d="m 368.53836,582.458 c -0.55072,-1.76231 -0.65891,-3.63018 -0.28125,-5.4375 1.22115,-5.84407 6.99968,-9.61178 12.84375,-8.39062 5.84407,1.22115 9.5649,6.9528 8.34375,12.79687 l -1.45313,-0.28125 c 1.05534,-5.05056 -2.16819,-10.00716 -7.21875,-11.0625 -5.05055,-1.05534 -9.96028,2.16819 -11.01562,7.21875 -0.32533,1.55697 -0.28693,3.16931 0.1875,4.6875 l -1.40625,0.46875 z"
id="path4836"
inkscape:connector-curvature="0" />
<rect
y="580.08246"
x="367.0675"
height="7.5571737"
width="22.936775"
id="rect4838"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<g
id="g4745"
transform="translate(3.610497,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4840"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(1.125679,0,0,1.125679,-59.007206,67.325585)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4842"
width="31.384169"
height="28.585962"
x="326.19742"
y="595.06921"
rx="11.508705"
ry="11.508705" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="rect4848"
d="m 341.7859,567.33246 15.4059,13.05718 -30.81178,0 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<g
id="g4739"
transform="translate(-13.8895,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<path
sodipodi:nodetypes="sssss"
inkscape:connector-curvature="0"
id="path4859"
d="m 313.61904,583.92537 c 0,5.38515 -4.36552,9.7507 -9.75069,9.7507 -5.38516,0 -9.75069,-4.36555 -9.75069,-9.7507 0,-5.38516 4.36553,-18.7507 9.75069,-18.7507 5.38517,0 9.75069,13.36554 9.75069,18.7507 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="288.69742"
height="28.585962"
width="31.384169"
id="rect4861"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<rect
y="-581.63733"
x="-314.92755"
height="1.990828"
width="22.494493"
id="rect4865"
style="fill:#ffffff;fill-opacity:1;stroke:none"
transform="scale(-1,-1)" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4867"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(0.13078104,0,0,0.60667722,264.64095,292.01174)" />
</g>
<g
id="g4734"
transform="translate(-26.3895,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4874"
width="31.384169"
height="28.585962"
x="251.19742"
y="595.06921"
rx="11.508705"
ry="11.508705" />
<path
transform="matrix(1.125679,0,0,1.125679,-134.00721,67.325585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4880"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<rect
transform="scale(-1,-1)"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4885"
width="23.697685"
height="13.60479"
x="-277.9678"
y="-579.57483"
rx="2.4738464"
ry="2.4738464" />
</g>
<g
id="g4779"
transform="translate(46.110497,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<path
transform="matrix(1.125679,0,0,1.125679,53.51617,67.325585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path4777"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="438.69742"
height="28.585962"
width="31.384169"
id="rect4779"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="rect4801"
d="m 453.77933,564.5282 9.46571,17.56287 -18.93141,0 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 444.02933,569.0282 19.21571,13.06287 -18.93141,0 z"
id="path4804"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccc" />
<path
sodipodi:nodetypes="cccc"
inkscape:connector-curvature="0"
id="path4806"
d="m 463.52933,569.0282 -19.2157,13.06287 18.93141,0 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
</g>
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4721"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 a 8.6620579,8.6620579 0 1 1 -17.32411,0 8.6620579,8.6620579 0 1 1 17.32411,0 z"
transform="matrix(1.125679,0,0,1.125679,155.02631,-396.17441)" />
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect4723"
width="31.384169"
height="28.585962"
x="540.20758"
y="131.56921"
rx="11.508705"
ry="11.508705" />
<rect
y="108.6593"
x="545.72394"
height="12.243876"
width="19.3125"
id="rect4733"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 547.13018,101.90681 c -5.38516,0 -9.75,4.36483 -9.75,9.75 0,1.33069 0.31424,2.59431 0.79688,3.75 1.47091,-3.52043 4.89925,-6 8.95312,-6 4.05387,0 7.52909,2.47957 9,6 0.48264,-1.15569 0.75,-2.41931 0.75,-3.75 0,-5.38517 -4.36484,-9.75 -9.75,-9.75 z"
id="path4747" />
<path
inkscape:connector-curvature="0"
style="fill:#ffffff;fill-opacity:1;stroke:none"
d="m 564.38018,101.90681 c -5.38516,0 -9.75,4.36483 -9.75,9.75 0,1.33069 0.31424,2.59431 0.79688,3.75 1.47091,-3.52043 4.89925,-6 8.95312,-6 4.05387,0 7.52909,2.47957 9,6 0.48264,-1.15569 0.75,-2.41931 0.75,-3.75 0,-5.38517 -4.36484,-9.75 -9.75,-9.75 z"
id="path4749" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path4763"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 a 8.6620579,8.6620579 0 1 1 -17.32411,0 8.6620579,8.6620579 0 1 1 17.32411,0 z"
transform="matrix(0.31445451,0,0,0.31445451,461.28148,-26.16184)" />
<path
transform="matrix(0.31445451,0,0,0.31445451,426.03148,-26.16184)"
d="m 364.33677,458.92282 a 8.6620579,8.6620579 0 1 1 -17.32411,0 8.6620579,8.6620579 0 1 1 17.32411,0 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path3812"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
<g
id="g4728"
transform="translate(-38.8895,-463.5)"
style="fill:#ffffff;fill-opacity:1">
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="213.69742"
height="28.585962"
width="31.384169"
id="rect3814"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path3816"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(1.125679,0,0,1.125679,-171.50721,67.325585)" />
<path
sodipodi:nodetypes="sssss"
inkscape:connector-curvature="0"
id="path3820"
d="m 239.86905,575.42756 c 0,2.34639 -5.36552,3.49852 -10.75069,3.49852 -5.38517,0 -11.00069,-1.15213 -11.00069,-3.49852 0,-2.34639 5.61552,-4.99853 11.00069,-4.99853 5.38517,0 10.75069,2.65214 10.75069,4.99853 z"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
transform="matrix(0.10913492,0,0,0.27356763,190.45793,445.64681)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path3823"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="g4723"
transform="translate(-51.3895,-465)"
style="fill:#ffffff;fill-opacity:1">
<rect
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="rect3825"
width="31.384169"
height="28.585962"
x="176.19742"
y="595.06921"
rx="11.508705"
ry="11.508705" />
<path
transform="matrix(1.125679,0,0,1.125679,-209.00721,67.325585)"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
sodipodi:ry="8.6620579"
sodipodi:rx="8.6620579"
sodipodi:cy="458.92282"
sodipodi:cx="355.67471"
id="path3827"
style="fill:#ffffff;fill-opacity:1;stroke:none"
sodipodi:type="arc" />
</g>
<g
id="g4719"
transform="translate(-158.8895,-465)"
style="fill:#ffffff;fill-opacity:1">
<rect
ry="11.508705"
rx="11.508705"
y="595.06921"
x="233.69742"
height="28.585962"
width="31.384169"
id="rect3837"
style="fill:#ffffff;fill-opacity:1;stroke:none" />
<path
sodipodi:type="arc"
style="fill:#ffffff;fill-opacity:1;stroke:none"
id="path3839"
sodipodi:cx="355.67471"
sodipodi:cy="458.92282"
sodipodi:rx="8.6620579"
sodipodi:ry="8.6620579"
d="m 364.33677,458.92282 c 0,4.78392 -3.87813,8.66206 -8.66206,8.66206 -4.78392,0 -8.66205,-3.87814 -8.66205,-8.66206 0,-4.78392 3.87813,-8.66206 8.66205,-8.66206 4.78393,0 8.66206,3.87814 8.66206,8.66206 z"
transform="matrix(1.125679,0,0,1.125679,-151.50721,67.325585)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB