new add dialog. prevent main window from opening when there are no folders

This commit is contained in:
Hylke Bons 2010-05-05 00:46:14 +01:00
parent 74d7844b64
commit 699f791faf
2 changed files with 641 additions and 0 deletions

View file

@ -0,0 +1,51 @@
// SparkleShare, an instant update workflow to Git.
// 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 Gtk;
using Notifications;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
namespace SparkleShare {
public class SparkleShareStatusIcon : StatusIcon {
public SparkleShareStatusIcon () : base () {
IconName = "folder-sparkleshare";
}
public void SetIdleState () {
IconName = "folder-sparkleshare";
}
public void SetSyncingState () {
IconName = "view-refresh"; // Massively abusing this icon here :)
}
}
}

View file

@ -0,0 +1,590 @@
// SparkleShare, an instant update workflow to Git.
// 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 Gtk;
using Notifications;
using SparkleShare;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Timers;
namespace SparkleShare {
public class SparkleShareWindow : Window {
private bool Visibility;
private VBox LayoutVerticalLeft;
private VBox LayoutVerticalRight;
private HBox LayoutHorizontal;
private TreeView ReposView;
private ListStore ReposStore;
private Repository [] Repositories;
public SparkleShareWindow (Repository [] R) : base ("SparkleShare") {
Repositories = R;
// Show a notification if there are no folders yet
if (Repositories.Length == 0) {
Notification Notification;
Notification = new Notification ("Welcome to SparkleShare!",
"You don't have any folders " +
"configured yet.");
Notification.AddAction ("", "Add a Folder",
delegate { CreateAddDialog (); } );
Notification.Urgency = Urgency.Normal;
Notification.Timeout = 7500;
Notification.Show ();
} else {
CreateWindow ();
}
}
public void CreateWindow () {
Visibility = false;
SetSizeRequest (720, 540);
SetPosition (WindowPosition.Center);
BorderWidth = 6;
IconName = "folder-sparkleshare";
VBox LayoutVertical = new VBox (false, 0);
Notebook Notebook = new Notebook ();
Notebook.BorderWidth = 6;
LayoutHorizontal = new HBox (false, 0);
ReposStore = new ListStore (typeof (Gdk.Pixbuf),
typeof (string),
typeof (Repository));
LayoutVerticalLeft = CreateReposList ();
LayoutVerticalLeft.BorderWidth = 12;
LayoutVerticalRight = CreateDetailedView (Repositories [0]);
LayoutHorizontal.PackStart (LayoutVerticalLeft, false, false, 0);
LayoutHorizontal.PackStart (LayoutVerticalRight, true, true, 12);
Notebook.AppendPage (LayoutHorizontal, new Label ("Folders"));
Notebook.AppendPage (CreateEventLog (), new Label ("Events"));
LayoutVertical.PackStart (Notebook, true, true, 0);
HButtonBox DialogButtons = new HButtonBox ();
DialogButtons.BorderWidth = 6;
Button QuitServiceButton = new Button ("Quit Service");
QuitServiceButton.Clicked += Quit;
Button CloseButton = new Button (Stock.Close);
CloseButton.Clicked += delegate (object o, EventArgs args) {
Visibility = false;
HideAll ();
};
DialogButtons.Add (QuitServiceButton);
DialogButtons.Add (CloseButton);
LayoutVertical.PackStart (DialogButtons, false, false, 0);
// Fetch remote changes every 20 seconds
/* Timer RedrawTimer = new Timer ();
RedrawTimer.Interval = 5000;
RedrawTimer.Elapsed += delegate {
TreeSelection Selection = ReposView.Selection;;
TreeIter Iter = new TreeIter ();;
Selection.GetSelected (out Iter);
Repository Repository = (Repository)ReposStore.GetValue (Iter, 2);
Console.WriteLine(Repository.Name);
LayoutHorizontal.Remove (LayoutVerticalRight);
LayoutVerticalRight = CreateDetailedView (Repository);
LayoutHorizontal.PackStart (LayoutVerticalRight, true, true, 12);
ShowAll ();
};
RedrawTimer.Start();
*/
Add (LayoutVertical);
}
// Creates a visual list of repositories
public VBox CreateReposList() {
string RemoteFolderIcon = "/usr/share/icons/gnome/32x32/places/folder.png";
TreeIter ReposIter;
foreach (Repository Repository in Repositories) {
ReposIter = ReposStore.Prepend ();
ReposStore.SetValue (ReposIter, 0, new Gdk.Pixbuf (RemoteFolderIcon));
ReposStore.SetValue (ReposIter, 1, Repository.Name + " \n" +
Repository.Domain + " ");
ReposStore.SetValue (ReposIter, 2, Repository);
}
ScrolledWindow ScrolledWindow = new ScrolledWindow ();
ReposView = new TreeView (ReposStore);
ReposView.AppendColumn ("", new CellRendererPixbuf () , "pixbuf", 0);
ReposView.AppendColumn ("", new Gtk.CellRendererText (), "text", 1);
TreeViewColumn [] ReposViewColumns = ReposView.Columns;
ReposViewColumns [0].MinWidth = 48;
ReposView.HeadersVisible = false;
ReposStore.IterNthChild (out ReposIter, 0);
ReposView.ActivateRow (ReposStore.GetPath (ReposIter),
ReposViewColumns [1]);
ReposView.CursorChanged += delegate {
TreeSelection Selection = ReposView.Selection;;
TreeIter Iter = new TreeIter ();;
Selection.GetSelected (out Iter);
Repository Repository = (Repository)ReposStore.GetValue (Iter, 2);
Console.WriteLine(Repository.Name);
LayoutHorizontal.Remove (LayoutVerticalRight);
LayoutVerticalRight = CreateDetailedView (Repository);
LayoutHorizontal.PackStart (LayoutVerticalRight, true, true, 12);
ShowAll ();
};
HBox AddRemoveButtons = new HBox (false, 6);
Button AddButton = new Button ("Add...");
AddRemoveButtons.PackStart (AddButton, true, true, 0);
Image RemoveImage = new Image ("/usr/share/icons/gnome/16x16/actions/list-remove.png");
Button RemoveButton = new Button ();
RemoveButton.Image = RemoveImage;
AddRemoveButtons.PackStart (RemoveButton, false, false, 0);
ScrolledWindow.AddWithViewport (ReposView);
ScrolledWindow.WidthRequest = 200;
VBox VBox = new VBox (false, 6);
VBox.PackStart (ScrolledWindow, true, true, 0);
VBox.PackStart (AddRemoveButtons, false, false, 0);
return VBox;
}
// Creates the detailed view
public VBox CreateDetailedView (Repository Repository) {
Console.WriteLine ("repo: " + Repository.Name);
// Create box layout for Remote Address
HBox RemoteUrlBox = new HBox (false, 0);
Label Property1 = new Label ("Remote Address:");
Property1.WidthRequest = 120;
Property1.SetAlignment (0, 0);
Label Value1 = new Label
("<b>" + Repository.RemoteOriginUrl + "</b>");
Value1.UseMarkup = true;
RemoteUrlBox.PackStart (Property1, false, false, 0);
RemoteUrlBox.PackStart (Value1, false, false, 0);
// Create box layout for repository path
HBox LocalPathBox = new HBox (false, 0);
Label Property2 = new Label ("Local path:");
Property2.WidthRequest = 120;
Property2.SetAlignment (0, 0);
Label Value2 = new Label
("<b>" + Repository.LocalPath + "</b>");
Value2.UseMarkup = true;
LocalPathBox.PackStart (Property2, false, false, 0);
LocalPathBox.PackStart (Value2, false, false, 0);
CheckButton NotificationsCheckButton =
new CheckButton ("Notify me when something changes");
NotificationsCheckButton.Active = true;
CheckButton ChangesCheckButton =
new CheckButton ("Synchronize my changes");
ChangesCheckButton.Active = true;
Table Table = new Table(7, 2, false);
Table.RowSpacing = 6;
Table.Attach (RemoteUrlBox, 0, 2, 0, 1);
Table.Attach (LocalPathBox, 0, 2, 1, 2);
Table.Attach (NotificationsCheckButton, 0, 2, 4, 5);
Table.Attach (ChangesCheckButton, 0, 2, 5, 6);
VBox VBox = new VBox (false, 0);
VBox.PackStart (Table, false, false, 12);
Label PeopleLabel =
new Label ("<span font_size='large'><b>Active users" +
"</b></span>");
PeopleLabel.UseMarkup = true;
PeopleLabel.SetAlignment (0, 0);
VBox.PackStart (PeopleLabel, false, false, 0);
VBox.PackStart
(CreatePeopleList (Repository ), true, true, 12);
return VBox;
}
public ScrolledWindow CreateEventLog() {
ListStore LogStore = new ListStore (typeof (Gdk.Pixbuf),
typeof (string),
typeof (string));
Process Process = new Process();
Process.EnableRaisingEvents = false;
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.UseShellExecute = false;
Process.StartInfo.FileName = "git";
string Output = "";
foreach (Repository Repository in Repositories) {
// We're using the snowman here to separate messages :)
Process.StartInfo.Arguments =
"log --format=\"%at☃In " + Repository.Name + ", %an %s☃%cr\" -25";
Process.StartInfo.WorkingDirectory = Repository.LocalPath;
Process.Start();
Output += "\n" + Process.StandardOutput.ReadToEnd().Trim ();
}
Output = Output.TrimStart ("\n".ToCharArray ());
string [] Lines = Regex.Split (Output, "\n");
// Sort by time and get the last 25
Array.Sort (Lines);
Array.Reverse (Lines);
string [] LastTwentyFive = new string [25];
Array.Copy (Lines, 0, LastTwentyFive, 0, 25);
TreeIter Iter;
foreach (string Line in LastTwentyFive) {
// Look for the snowman!
string [] Parts = Regex.Split (Line, "☃");
string Message = Parts [1];
string TimeAgo = Parts [2];
string IconFile =
"/usr/share/icons/hicolor/16x16/status/document-edited.png";
if (Message.IndexOf (" added ") > -1)
IconFile =
"/usr/share/icons/hicolor/16x16/status/document-added.png";
if (Message.IndexOf (" deleted ") > -1)
IconFile =
"/usr/share/icons/hicolor/16x16/status/document-removed.png";
if (Message.IndexOf (" moved ") > -1 ||
Message.IndexOf (" renamed ") > -1)
IconFile =
"/usr/share/icons/hicolor/16x16/status/document-moved.png";
Iter = LogStore.Append ();
LogStore.SetValue (Iter, 0, new Gdk.Pixbuf (IconFile));
LogStore.SetValue (Iter, 1, Message);
// TODO: right align time
LogStore.SetValue (Iter, 2, " " + TimeAgo);
}
TreeView LogView = new TreeView (LogStore);
LogView.HeadersVisible = false;
CellRendererText TextCellRight = new Gtk.CellRendererText ();
TextCellRight.Alignment = Pango.Alignment.Right;
LogView.AppendColumn ("", new Gtk.CellRendererPixbuf (), "pixbuf", 0);
LogView.AppendColumn ("", new Gtk.CellRendererText (), "text", 1);
LogView.AppendColumn ("", TextCellRight, "text", 2);
TreeViewColumn [] Columns = LogView.Columns;
Columns [0].MinWidth = 32;
Columns [1].Expand = true;
Columns [1].MaxWidth = 150;
ScrolledWindow ScrolledWindow = new ScrolledWindow ();
ScrolledWindow.AddWithViewport (LogView);
ScrolledWindow.BorderWidth = 12;
return ScrolledWindow;
}
// Creates a visual list of people working in the repo
public ScrolledWindow CreatePeopleList (Repository Repository) {
Process Process = new Process ();
Process.EnableRaisingEvents = false;
Process.StartInfo.RedirectStandardOutput = true;
Process.StartInfo.UseShellExecute = false;
// Get a log of commits, example: "Hylke Bons☃added 'file'."
Process.StartInfo.FileName = "git";
Process.StartInfo.Arguments = "log --format=\"%an☃%ae\" -50";
Process.StartInfo.WorkingDirectory = Repository.LocalPath;
Process.Start();
string Output = Process.StandardOutput.ReadToEnd().Trim ();
string [] People = new string [50];
string [] Lines = Regex.Split (Output, "\n");
ListStore PeopleStore = new ListStore (typeof (Gdk.Pixbuf),
typeof (string));
TreeIter PeopleIter;
int i = 0;
foreach (string Line in Lines) {
// Only add name if it isn't there already
if (Array.IndexOf (People, Line) == -1) {
People [i] = Line;
string [] Parts = Regex.Split (Line, "☃");
string UserName = Parts [0];
string UserEmail = Parts [1];
// Do something special if the person is you
if (UserName.Equals (Repository.UserName))
UserName += " (thats you!)";
// Actually add to the list
PeopleIter = PeopleStore.Prepend ();
PeopleStore.SetValue (PeopleIter, 0, new Gdk.Pixbuf (GetAvatarFileName (UserEmail, 32)));
PeopleStore.SetValue (PeopleIter, 1, UserName + "\n" + UserEmail);
}
i++;
}
TreeView PeopleView = new TreeView (PeopleStore);
PeopleView.AppendColumn ("", new CellRendererPixbuf () , "pixbuf", 0);
PeopleView.AppendColumn ("", new Gtk.CellRendererText (), "text", 1);
TreeViewColumn [] PeopleViewColumns = PeopleView.Columns;
PeopleViewColumns [0].MinWidth = 48;
PeopleViewColumns [1].Expand = true;
PeopleView.HeadersVisible = false;
ScrolledWindow ScrolledWindow = new ScrolledWindow ();
ScrolledWindow.AddWithViewport (PeopleView);
return ScrolledWindow;
}
public void CreateAddDialog () {
Window AddDialog = new Window ("Add Folder");
AddDialog.SetPosition (WindowPosition.Center);
// AddDialog.SetSizeRequest (320, 200);
AddDialog.BorderWidth = 6;
Label NameLabel = new Label ("Folder Name: ");
Entry NameEntry = new Entry ();
Label NameExample = new Label ("<span size='small'><i>Example: Project.</i></span>");
NameExample.UseMarkup = true;
NameExample.SetAlignment (0, 0);
NameLabel.Xalign = 1;
Label RemoteUrlLabel = new Label ("Remote Address: ");
string [] DefaultUrls = new string [3] { "ssh://git@github.com/",
"ssh://git@git.gnome.org/",
"ssh://git@gitorious.org/" };
ComboBoxEntry RemoteUrlCombo = new ComboBoxEntry (DefaultUrls);
Label RemoteUrlExample = new Label ("<span size='small'><i>Example: ssh://git@github.com/.</i></span>");
RemoteUrlExample.UseMarkup = true;
RemoteUrlExample.SetAlignment (0, 0);
RemoteUrlLabel.Xalign = 1;
HButtonBox ButtonBox = new HButtonBox ();
ButtonBox.Layout = ButtonBoxStyle.End;
ButtonBox.Spacing = 6;
ButtonBox.BorderWidth = 6;
Button AddButton = new Button (Stock.Add);
Button CancelButton = new Button (Stock.Cancel);
CancelButton.Clicked += delegate {
AddDialog.Destroy ();
};
ButtonBox.Add (CancelButton);
ButtonBox.Add (AddButton);
Table Table = new Table(4, 2, false);
Table.RowSpacing = 6;
Table.BorderWidth = 6;
Table.Attach (NameLabel, 0, 1, 0, 1);
Table.Attach (NameEntry, 1, 2, 0, 1);
Table.Attach (NameExample, 1, 2, 1, 2);
Table.Attach (RemoteUrlLabel, 0, 1, 3, 4);
Table.Attach (RemoteUrlCombo, 1, 2, 3, 4);
Table.Attach (RemoteUrlExample, 1, 2, 4, 5);
VBox VBox = new VBox (false, 0);
VBox.PackStart (Table, false, false, 0);
VBox.PackStart (ButtonBox, false, false, 0);
AddDialog.Add (VBox);
AddDialog.ShowAll ();
// Name:
// Remote Address:
// (Example: ssh://git@github.com/james/project.git)
//
}
public void ToggleVisibility() {
if (Repositories.Length > 0) {
Present ();
if (Visibility) {
if (HasFocus)
HideAll ();
} else {
ShowAll ();
}
} else CreateAddDialog ();
}
public void Quit (object o, EventArgs args) {
File.Delete ("/tmp/sparkleshare/sparkleshare.pid");
Application.Quit ();
}
public static string GetAvatarFileName (string Email, int Size) {
string AvatarPath = Environment.GetEnvironmentVariable("HOME") +
"/.config/sparkleshare/avatars/" +
Size + "x" + Size + "/";
if (!Directory.Exists (AvatarPath)) {
Directory.CreateDirectory (AvatarPath);
Console.WriteLine ("[Config] Created '" + AvatarPath + "'");
}
string AvatarFile = AvatarPath + Email;
if (File.Exists (AvatarFile))
return AvatarFile;
else {
// Let's try to get the person's gravatar for next time
WebClient WebClient = new WebClient ();
Uri GravatarUri = new Uri ("http://www.gravatar.com/avatar/" +
GetMD5 (Email) + ".jpg?s=" + Size + "&d=404");
string TmpFile = "/tmp/" + Email + Size;
if (!File.Exists (TmpFile)) {
WebClient.DownloadFileAsync (GravatarUri, TmpFile);
WebClient.DownloadFileCompleted += delegate {
File.Delete (AvatarPath + Email);
FileInfo TmpFileInfo = new FileInfo (TmpFile);
if (TmpFileInfo.Length > 255)
File.Move (TmpFile, AvatarPath + Email);
};
}
string FallbackFileName = "/usr/share/icons/hicolor/" +
Size + "x" + Size +
"/status/avatar-default.png";
if (File.Exists (FallbackFileName))
return FallbackFileName;
else
return "/usr/share/icons/hicolor/16x16/status/avatar-default.png";
}
}
// Helper that creates an MD5 hash
public static string GetMD5 (string s) {
MD5 md5 = new MD5CryptoServiceProvider ();
Byte[] Bytes = ASCIIEncoding.Default.GetBytes (s);
Byte[] EncodedBytes = md5.ComputeHash (Bytes);
return BitConverter.ToString(EncodedBytes).ToLower ().Replace ("-", "");
}
}
}