3
// Copyright (C) 2008 GNOME Do
5
// This program is free software: you can redistribute it and/or modify
6
// it under the terms of the GNU General Public License as published by
7
// the Free Software Foundation, either version 3 of the License, or
8
// (at your option) any later version.
10
// This program is distributed in the hope that it will be useful,
11
// but WITHOUT ANY WARRANTY; without even the implied warranty of
12
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
// GNU General Public License for more details.
15
// You should have received a copy of the GNU General Public License
16
// along with this program. If not, see <http://www.gnu.org/licenses/>.
20
using System.Collections.Generic;
23
using System.Runtime.Serialization;
24
using System.Runtime.Serialization.Formatters.Binary;
31
using Docky.Utilities;
35
namespace Docky.Interface
37
public enum IconSource {
44
public delegate void UpdateRequestHandler (object sender, UpdateRequestArgs args);
45
public delegate void DockItemsChangedHandler (IEnumerable<BaseDockItem> items);
47
public class DockItemProvider : IDisposable
50
public event DockItemsChangedHandler DockItemsChanged;
51
public event UpdateRequestHandler ItemNeedsUpdate;
53
Dictionary<string, DockItem> custom_items;
54
List<DockItem> statistical_items;
55
List<BaseDockItem> output_items;
56
List<ApplicationDockItem> task_items;
57
bool enable_serialization = true;
58
bool custom_items_read;
60
string DesktopFilesPath {
62
return Path.Combine (Services.Paths.UserDataDirectory, "dock_desktop_files");
66
string SortDictionaryPath {
68
return Path.Combine (Services.Paths.UserDataDirectory, "dock_sort_dictionary");
74
if (!DragableItems.Any ())
76
//TODO make sane once mono 1.9 support is dropped
77
return DragableItems.Max ((Func<DockItem, int>) (di => di.Position));
81
BaseDockItem Separator { get; set; }
82
BaseDockItem MenuItem { get; set; }
83
BaseDockItem TrashItem { get; set; }
85
IEnumerable<DockItem> DragableItems {
86
get { return statistical_items.Concat (custom_items.Values).OrderBy (di => di.Position); }
89
public bool UpdatesEnabled { get; set; }
91
public List<BaseDockItem> DockItems {
93
if (output_items == null) {
94
output_items = new List<BaseDockItem> ();
95
output_items.Add (MenuItem);
96
output_items.AddRange (DragableItems.Cast<BaseDockItem> ());
98
if (task_items.Any () || DockPreferences.ShowTrash)
99
output_items.Add (Separator);
101
if (task_items.Any ()) {
102
output_items.AddRange (task_items.Cast<BaseDockItem> ());
105
if (DockPreferences.ShowTrash)
106
output_items.Add (TrashItem);
112
public DockItemProvider ()
114
Separator = new SeparatorItem ();
115
MenuItem = new DoDockItem ();
116
TrashItem = new TrashDockItem ();
118
custom_items = new Dictionary<string, DockItem> ();
119
statistical_items = new List<DockItem> ();
120
task_items = new List<ApplicationDockItem> ();
125
void RegisterEvents ()
127
Services.Core.UniverseInitialized += OnUniverseInitialized;
128
Wnck.Screen.Default.WindowClosed += OnWindowClosed;
129
Wnck.Screen.Default.WindowOpened += OnWindowOpened;
130
DockPreferences.TrashVisibilityChanged += OnDockItemsChanged;
131
DockPreferences.AutomaticIconsChanged += UpdateItems;
134
void UnregisterEvents ()
136
Services.Core.UniverseInitialized -= OnUniverseInitialized;
137
Wnck.Screen.Default.WindowClosed -= OnWindowClosed;
138
Wnck.Screen.Default.WindowOpened -= OnWindowOpened;
139
DockPreferences.TrashVisibilityChanged -= OnDockItemsChanged;
140
DockPreferences.AutomaticIconsChanged -= UpdateItems;
143
private void OnWindowClosed (object o, WindowClosedArgs args)
145
if (args.Window.IsSkipTasklist)
150
private void OnWindowOpened (object o, WindowOpenedArgs args)
152
if (args.Window.IsSkipTasklist)
157
public void AddCustomItem (Element item)
159
if (!(item is Item)) {
160
Log<DockItemProvider>.Error ("Could not add {0} to custom items for dock", item.Safe.Name);
163
string id = item.UniqueId;
164
if (custom_items.ContainsKey (id))
167
DockItem di = new DockItem (item as Item);
168
di.RemoveClicked += HandleRemoveClicked;
169
di.UpdateNeeded += HandleUpdateNeeded;
170
di.Position = LastPosition + 1;
171
custom_items [id] = di;
173
UpdateStatisticalItems ();
174
OnDockItemsChanged ();
176
if (enable_serialization)
180
public void AddCustomItem (string identifier)
182
if (custom_items.ContainsKey (identifier))
185
DockItem customItem = GetCustomItem (identifier);
187
if (customItem != null) {
188
customItem.RemoveClicked += HandleRemoveClicked;
189
customItem.UpdateNeeded += HandleUpdateNeeded;
190
customItem.Position = LastPosition + 1;
191
custom_items [identifier] = customItem;
194
UpdateStatisticalItems ();
195
OnDockItemsChanged ();
197
if (enable_serialization)
201
DockItem GetCustomItem (string identifier)
203
DockItem customItem = null;
205
if (identifier.StartsWith ("file://"))
206
identifier = identifier.Substring ("file://".Length);
208
if (File.Exists (identifier) || Directory.Exists (identifier)) {
209
if (identifier.EndsWith (".desktop")) {
210
Item o = Services.UniverseFactory.NewApplicationItem (identifier) as Item;
211
customItem = new DockItem (o);
213
Item o = Services.UniverseFactory.NewFileItem (identifier) as Item;
214
customItem = new DockItem (o);
217
Item e = Services.Core.GetElement (identifier) as Item;
219
customItem = new DockItem (e);
221
Log<DockItemProvider>.Error ("Could not add custom item with id: {0}", identifier);
226
public bool ItemCanBeMoved (int item)
228
if (DockItems [item] is DockItem) {
229
return DragableItems.Contains (DockItems [item] as DockItem);
234
public void MoveItemToPosition (int item, int position)
236
if (item == position)
239
IconSource itemSource = GetIconSource (DockItems [item]);
240
IconSource targetSource = GetIconSource (DockItems [position]);
242
if (itemSource == IconSource.Application || itemSource == IconSource.Unknown)
245
if (targetSource == IconSource.Application || targetSource == IconSource.Unknown) {
246
if (DockItems [position] is TrashDockItem) {
252
DockItem primaryItem = DockItems [item] as DockItem;
253
DockItem targetItem = DockItems [position] as DockItem;
255
int startPosition = primaryItem.Position;
256
int targetPosition = targetItem.Position;
258
foreach (DockItem di in DragableItems) {
259
if (startPosition < targetPosition) {
260
// the item is being shifted to the right. Everything greater than item up to and including target item
261
// needs to be shifted to the left
262
if (di.Position > startPosition && di.Position <= targetPosition)
265
// the item is being shifted to the left. Everthing less than the item and up to and including target item
266
// needs to be shifted to the right
267
if (di.Position < startPosition && di.Position >= targetPosition)
272
primaryItem.Position = targetPosition;
274
OnDockItemsChanged ();
278
public void ForceUpdate ()
283
public IconSource GetIconSource (BaseDockItem item) {
284
if (item is ApplicationDockItem && task_items.Contains (item as ApplicationDockItem))
285
return IconSource.Application;
287
if (item is DockItem && statistical_items.Contains (item as DockItem))
288
return IconSource.Statistics;
290
if (item is DockItem && custom_items.Values.Contains (item as DockItem))
291
return IconSource.Custom;
293
return IconSource.Unknown;
296
void HandleUpdateNeeded(object sender, UpdateRequestArgs args)
298
if (ItemNeedsUpdate != null)
299
ItemNeedsUpdate (this, args);
302
void HandleRemoveClicked(object sender, EventArgs e)
304
for (int i=0; i<DockItems.Count; i++) {
305
if (DockItems [i] == sender) {
312
IEnumerable<Item> MostUsedItems ()
315
.GetItemsOrderedByRelevance ()
316
.Where (item => item.GetType ().Name != "SelectedTextItem" && item.GetType ().Name != "GNOMETrashFileItem")
317
.Where (item => !DockPreferences.ItemBlacklist.Contains (item.UniqueId))
318
.Take (DockPreferences.AutomaticIcons)
319
.OrderByDescending (item => item is IApplicationItem)
320
.ThenBy (item => item.GetType ().Name)
321
.ThenBy (item => item.Safe.Name);
324
public bool RemoveItem (int item)
326
bool ret_val = false;
328
if (GetIconSource (DockItems [item]) == IconSource.Statistics) {
329
DockPreferences.AddBlacklistItem ((DockItems [item] as DockItem).Element.UniqueId);
330
DockPreferences.AutomaticIcons--;
333
} else if (GetIconSource (DockItems [item]) == IconSource.Custom) {
334
foreach (KeyValuePair<string, DockItem> kvp in custom_items) {
335
if (kvp.Value.Equals (DockItems [item])) {
336
custom_items.Remove (kvp.Key);
346
if (enable_serialization)
351
void SerializeData ()
353
SerializeCustomItems ();
354
SerializeSortDictionary ();
357
void SerializeCustomItems ()
360
using (Stream s = File.OpenWrite (DesktopFilesPath)) {
361
BinaryFormatter f = new BinaryFormatter ();
362
f.Serialize (s, custom_items.Keys.ToArray ());
364
} catch (Exception e) {
365
Log<DockItemProvider>.Error ("Could not serialize custom items");
366
Log<DockItemProvider>.Error (e.Message);
370
void SerializeSortDictionary ()
373
using (Stream s = File.OpenWrite (SortDictionaryPath)) {
374
BinaryFormatter f = new BinaryFormatter ();
375
f.Serialize (s, DragableItems.ToDictionary (di => di.Element.UniqueId, di => di.Position));
377
} catch (Exception e) {
378
Log<DockItemProvider>.Error ("Could not serialize sort items");
379
Log<DockItemProvider>.Error (e.Message);
383
string[] DeserializeCustomItems ()
387
using (Stream s = File.OpenRead (DesktopFilesPath)) {
388
BinaryFormatter f = new BinaryFormatter ();
389
filenames = f.Deserialize (s) as string[];
391
} catch (FileNotFoundException e) {
392
Log<DockItemProvider>.Debug ("Custom items file not present, nothing to add. " + e.Message);
393
filenames = new string[0];
395
Log<DockItemProvider>.Error ("Could not deserialize custom items");
396
filenames = new string[0];
401
Dictionary<string, int> DeserializeSortDictionary ()
403
Dictionary<string, int> sortDictionary;
405
using (Stream s = File.OpenRead (SortDictionaryPath)) {
406
BinaryFormatter f = new BinaryFormatter ();
407
sortDictionary = f.Deserialize (s) as Dictionary<string, int>;
409
} catch (FileNotFoundException e) {
410
Log<DockItemProvider>.Debug ("Sort Dictionary file not present, nothing to add. " + e.Message);
411
sortDictionary = new Dictionary<string, int> ();
413
Log<DockItemProvider>.Error ("Could not deserialize sort dictionary");
414
sortDictionary = new Dictionary<string, int> ();
416
return sortDictionary;
424
UpdateStatisticalItems ();
426
if (!custom_items_read) {
427
enable_serialization = false;
428
foreach (string s in DeserializeCustomItems ())
430
enable_serialization = true;
432
custom_items_read = true;
434
Dictionary<string, int> sortDictionary = DeserializeSortDictionary ();
435
foreach (DockItem item in DragableItems) {
436
if (sortDictionary.ContainsKey (item.Element.UniqueId))
437
item.Position = sortDictionary [item.Element.UniqueId];
441
UpdateWindowItems ();
442
SimplifyPositions (DragableItems);
443
OnDockItemsChanged ();
447
void UpdateStatisticalItems ()
449
List<DockItem> old_items = statistical_items;
450
statistical_items = new List<DockItem> ();
452
IEnumerable<Item> mostUsedItems = MostUsedItems ();
454
foreach (Item item in mostUsedItems) {
455
if (custom_items.Values.Any (di => di.Element == item))
458
if (old_items.Any (di => di.Element == item)) {
459
statistical_items.AddRange (old_items.Where (di => di.Element == item));
461
DockItem di = new DockItem (item);
462
di.RemoveClicked += HandleRemoveClicked;
463
di.UpdateNeeded += HandleUpdateNeeded;
464
di.DockAddItem = DateTime.UtcNow;
466
int position = LastPosition + 1;
468
//TODO fixme once mono 1.9 support is dropped
469
if (old_items.Any ())
470
position += old_items.Max ((Func<DockItem, int>) (oi => oi.Position));
472
di.Position = position;
473
statistical_items.Add (di);
477
foreach (DockItem item in old_items.Where (di => !statistical_items.Contains (di))) {
478
item.RemoveClicked -= HandleRemoveClicked;
479
item.UpdateNeeded -= HandleUpdateNeeded;
484
void UpdateWindowItems ()
486
foreach (DockItem di in statistical_items.Concat (custom_items.Values)) {
487
di.UpdateApplication ();
490
if (Wnck.Screen.Default.ActiveWorkspace == null)
492
List<ApplicationDockItem> out_items = new List<ApplicationDockItem> ();
494
IEnumerable<int> knownPids = statistical_items.Concat (custom_items.Values).SelectMany (di => di.Pids);
496
IEnumerable<Application> prunedApps = WindowUtils.GetApplications ()
497
.Where (app => app.Windows.Any (w => !w.IsSkipTasklist))
498
.Where (app => !knownPids.Contains (app.Pid) && app.Windows.Any ());
500
foreach (IEnumerable<Wnck.Application> apps in prunedApps.
501
GroupBy (app => (app as Wnck.Application).Windows [0].ClassGroup.ResClass)) {
502
ApplicationDockItem api = new ApplicationDockItem (apps);
504
if (task_items.Any (di => di.Equals (api)))
505
api.DockAddItem = task_items.Where (di => di.Equals (api)).First ().DockAddItem;
507
api.DockAddItem = DateTime.UtcNow;
510
api.UpdateNeeded += HandleUpdateNeeded;
513
foreach (ApplicationDockItem item in task_items) {
515
item.UpdateNeeded -= HandleUpdateNeeded;
518
task_items = out_items;
521
void SimplifyPositions (IEnumerable<DockItem> items)
524
foreach (DockItem item in items.OrderBy (di => di.Position).ToArray ()) {
529
void OnDockItemsChanged ()
534
if (DockItemsChanged != null)
535
DockItemsChanged (DockItems);
538
void OnUniverseInitialized (object sender, EventArgs e)
540
UpdatesEnabled = true;
544
public void Dispose ()
548
foreach (BaseDockItem di in DockItems) {
549
if (di is IRightClickable)
550
(di as IRightClickable).RemoveClicked -= HandleRemoveClicked;
551
if (di is IDockAppItem)
552
(di as IDockAppItem).UpdateNeeded -= HandleUpdateNeeded;
557
if (!DockItems.Contains (TrashItem))
558
TrashItem.Dispose ();
560
if (!DockItems.Contains (Separator))
561
Separator.Dispose ();
563
custom_items.Clear ();
564
statistical_items.Clear ();
565
output_items.Clear ();