Compare commits

..
8 Commits
26 changed files with 342 additions and 142 deletions

No files matched your search

+2 -4
View File
@@ -47,7 +47,7 @@ namespace mbts.ui
//public static ImageSource Ticket { get { return load_image("Ticket.png"); } } // replaced with AddOrders
public static ImageSource Date { get { return load_image("Date.png"); } }
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; /*return load_image("Cube-Molecule.png");*/ } }
public static ImageSource Cube3d { get { return (DrawingImage)_resources["Cube"]; } }
public static ImageSource Algo { get { return Cube3d; } }
public static ImageSource Quote { get { return Math; } }
public static ImageSource Math { get { return (DrawingImage)_resources["Surface"]; } }
@@ -62,9 +62,7 @@ namespace mbts.ui
#pragma warning restore CS0618
// Vector icons
public static ImageSource Favorites { get { return Favorites_Gold; } }
public static ImageSource Favorites_Gold { get { return (DrawingImage)_resources["FavoritesGold"]; } }
public static ImageSource Favorites_White { get { return (DrawingImage)_resources["FavoritesWhite"]; } }
public static ImageSource Bookmarks { get { return (DrawingImage)_resources["Bookmarks"]; } }
public static ImageSource Window { get { return (DrawingImage)_resources["Window"]; } }
public static ImageSource ActiveWindow { get { return (DrawingImage)_resources["ActiveWindow"]; } }
public static ImageSource Trash { get { return (DrawingImage)_resources["Trash"]; } }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

+3 -19
View File
@@ -3,33 +3,17 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mbts="clr-namespace:mbts.ui">
<DrawingImage x:Key="FavoritesGold">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<SolidColorBrush Color="#ffd932" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
<DrawingImage x:Key="FavoritesWhite">
<DrawingImage x:Key="Bookmarks">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Geometry="M1540.22,2082.07L1546.95,2102.78 1568.73,2102.78 1551.11,2115.58 1557.84,2136.29 1540.22,2123.49 1522.6,2136.29 1529.33,2115.58 1511.71,2102.78 1533.49,2102.78 1540.22,2082.07z">
<GeometryDrawing.Pen>
<Pen Thickness="1" Brush="Black" />
<Pen Thickness="3" Brush="Black" />
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<SolidColorBrush Color="#ffd932" />
<SolidColorBrush Color="Transparent" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup.Children>
@@ -0,0 +1,81 @@
using bts.spark;
using mbts.spark;
using mbts.ui;
using mbts.ui.settings;
using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
namespace bts.ui {
[Export(typeof(IShellModule))]
public class BookmarkShellModule : ILazyMenuModule {
readonly WindowManager _wm;
readonly IBookmarks _bookmarks;
readonly ISavedWindowFactory _window_factory;
readonly WorkspacesModel _workspaces;
[ImportingConstructor]
public BookmarkShellModule(
WorkspacesModel workspaces,
WindowManager wm,
IBookmarks bm,
ISavedWindowFactory swf) {
_workspaces = workspaces;
_wm = wm;
_bookmarks = bm;
_window_factory = swf;
}
public ImageSource icon => Icons.Bookmarks;
public string name => "My Windows";
public int priority => 35;
public IEnumerable<SparkCommand> actions {
get {
// The windows that are open
var open =
_wm.list_documents()
.Select(td => td.ViewModel)
.Where(vm => vm.HasCustomTitle)
.Select(vm =>
SparkCommand.create(vm.Title, vm.Icon, SparkTags.ActivateWindow, () => vm.Model))
.Cast<SparkCommand>();
var open_set = new HashSet<string>(open.Select(c => c.title));
// The windows that are not open
var closed =
_bookmarks.bookmarks
.Where(state => !open_set.Contains(state.title))
.Select(state =>
SparkCommand.create(state.title, Icons.Bookmarks, SparkTags.ShowWindowAsync,
() => {
_bookmarks.remove(state);
return _window_factory.restore(state);
}))
.Cast<SparkCommand>();
// todo: The windows that are closed
foreach (var w in open.Concat(closed).OrderBy(w => w.title)) {
yield return w;
}
}
}
public Task<IShellWindow> create(Stream load_stream) {
// This module shouldn't have any windows
throw new NotImplementedException();
}
public void Dispose() { }
}
}
@@ -0,0 +1,48 @@
using mbts.ui.settings;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace mbts.ui.shell {
public interface IBookmarks {
void save_bookmark(IShellWindow window);
void save_bookmark(WindowState window);
void remove(WindowState window);
void clear();
IEnumerable<WindowState> bookmarks { get; }
}
[Export(typeof(IBookmarks))]
public class BookmarksModel : IBookmarks {
readonly ISettingsCollector _settings_collector;
readonly Dictionary<string, WindowState> _bookmarks = new Dictionary<string, WindowState>();
[ImportingConstructor]
public BookmarksModel(ISettingsCollector settings) {
_settings_collector = settings;
}
public void save_bookmark(IShellWindow window) {
var state = _settings_collector.get_window_state(window);
if (state != null) {
_bookmarks[state.title] = state;
}
}
public void save_bookmark(WindowState window) {
_bookmarks[window.title] = window;
}
public void clear() {
_bookmarks.Clear();
}
public void remove(WindowState window) {
_bookmarks.Remove(window.title);
}
public IEnumerable<WindowState> bookmarks => _bookmarks.Values;
}
}
@@ -34,7 +34,7 @@ namespace bts.ui {
public int priority => 40;
public IEnumerable<SparkCommand> actions => new []{
public IEnumerable<SparkCommand> actions => new SparkCommand[]{
SparkCommands.ShowWorkspaces
//SparkCommands.ShowSettings
};
@@ -75,7 +75,7 @@ namespace mbts.ui.tests
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_windows_disposed");
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
var thread_factory = new ShellThreadFactory();
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
Assert.IsNotNull(Application.Current);
Assert.IsNotNull(Application.Current.Windows);
Assert.AreEqual(Application.Current.Windows.Count, 0);
@@ -100,7 +100,7 @@ namespace mbts.ui.tests
Logger.log(bts.log.Level.Info, bts.log.Subsystem.Ui, "Begin: test_slow_windows_disposed");
// Make sure that an IShellWindow and ISlowWindow added to the Windows collection is eventually disposed
var thread_factory = new ShellThreadFactory();
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), new TestUndo(), new SimpleBus());
IWindowManager wm = new WindowManager(thread_factory, new Lazy<ISettingsCollector>(() => new TestSettingsCollector()), new TestSavedWindowFactory(), null, new SimpleBus());
Assert.AreEqual(Application.Current.Windows.Count, 0);
// an ISlowWindow gets its own thread
+3
View File
@@ -5,6 +5,7 @@ using bts.spark;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using static mbts.spark.SparkBusData;
namespace mbts.spark
@@ -34,6 +35,8 @@ namespace mbts.spark
public static ITag<(PartialCommand<SecurityMD>, SecurityCriteria)> FilterPickOne { get; } = new SparkTag<(PartialCommand<SecurityMD>, SecurityCriteria)>("FilterPick1");
public static ITag<IShellWindow> ShowWindow { get; } = new SparkTag<IShellWindow>("Show Window");
public static ITag<Task<IShellWindow>> ShowWindowAsync { get; } = new SparkTag<Task<IShellWindow>>("ShowWindowAsync");
public static ITag<IShellWindow> ActivateWindow { get; } = new SparkTag<IShellWindow>("Activate Window");
public static ITag<Nothing> ShowWorkspaces { get; } = new SparkTag<Nothing>(nameof(ShowWorkspaces));
public static ITag<Nothing> ShowSettings { get; } = new SparkTag<Nothing>(nameof(ShowSettings));
public static ITag<Uri> ShowUri { get; } = new SparkTag<Uri>("ShowUri");
@@ -48,10 +48,10 @@ namespace System.Collections.Generic {
return rv;
}
public static IEnumerable<T> ToIEnumerable<T>(this T item) {
yield return item;
}
// use "new [] { item }" - avoid putting this on the intellisense for every item everywhere
//public static IEnumerable<T> ToIEnumerable<T>(this T item) {
// yield return item;
//}
public static IEnumerable<T> TakeWhile<T, TAccumulate>(this IEnumerable<T> source, TAccumulate seed, Func<TAccumulate, T, TAccumulate> accumulate, Func<TAccumulate, T, bool> predicate) {
TAccumulate a = seed;
@@ -69,16 +69,18 @@ namespace mbts.ui.settings
if (window == null || window.Owner == null) return null;
MemoryStream out_stream = new MemoryStream();
window.GetState(out_stream);
var pos = _interop.GetPosition(window);
var state = new WindowState() {
owner = window.Owner.key(),
app_data = out_stream.ToArray(),
position = _interop.GetPosition(window),
title = window.Title
position = pos,
title = string.IsNullOrEmpty(pos.custom_title) ? window.Title : pos.custom_title
};
return state;
}
catch (Exception e) {
Logger.log(bts.log.Level.Error, bts.log.Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
Logger.log(Level.Error, Subsystem.Ui, "Error saving window state for type {0}.\n {1}".fmt(window.GetType(), e.Message));
}
return null;
}
@@ -39,6 +39,9 @@ namespace mbts.ui.shell {
[ProtoMember(102), DefaultValue(true)]
public bool always_on_top = true;
//[ProtoMember(103)] //used
[ProtoMember(104)]
public string custom_title;
}
[ProtoContract]
@@ -58,7 +61,7 @@ namespace mbts.ui.shell {
public WindowPosition position;
[ProtoMember(9)]
public string title;
public string title; // for debugging
}
[ProtoContract]
@@ -23,6 +23,9 @@ namespace mbts.ui
Task<IShellWindow> create(Stream load_stream);
}
// ILazyModule may also impement IShellMenu to get opening/closing notification
public interface ILazyMenuModule : IShellModule { }
public interface IPersistShellModule : IShellModule {
// Called at startup before the UI is shown
// This is an opportunity for you to kick off background services
@@ -17,5 +17,5 @@ namespace mbts.ui
WindowPosition GetPosition(IShellWindow window);
bool IsVisible(IShellWindow window);
void PinWindow(IShellWindow window);
}
}
}
@@ -10,9 +10,11 @@ namespace mbts.ui.controls {
static SparkMenu() =>
DefaultStyleKeyProperty.OverrideMetadata(typeof(SparkMenu), new FrameworkPropertyMetadata(typeof(SparkMenu)));
public SparkMenu() =>
public SparkMenu() {
SetCurrentValue(FocusableProperty, false);
}
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
@@ -61,16 +61,16 @@ namespace mbts.ui
_source_window = window as FloatingWindow;
if (_source != null) {
_mouse_start = e.GetPosition(window);
if (!is_button_descendant((DependencyObject)e.OriginalSource)) {
if (!is_clickable_descendant((DependencyObject)e.OriginalSource)) {
Mouse.Capture(_source);
}
}
}
static bool is_button_descendant(DependencyObject target) {
static bool is_clickable_descendant(DependencyObject target) {
while (target != null && !(target is TabDocument)) {
target = VisualTreeHelper.GetParent(target);
if (target is Button) return true;
if (target is Button || target is TextBox) return true;
}
return false;
}
@@ -24,38 +24,7 @@ namespace mbts.ui
}
}
public static async Task FromEvent(this object target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<object>();
Action result = () => { tcs.TrySetResult(null); };
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
try {
evt.AddEventHandler(target, d);
await tcs.Task;
}
finally {
evt.RemoveEventHandler(target, d);
}
}
public static async Task<T> FromEvent<T>(this object target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<T>();
Action<T> result = (arg) => { tcs.TrySetResult(arg); };
var d = Delegate.CreateDelegate(evt.EventHandlerType, result, "Invoke");
try {
evt.AddEventHandler(target, d);
await tcs.Task;
return tcs.Task.Result;
}
finally {
evt.RemoveEventHandler(target, d);
}
}
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this object target, string event_name) {
public static async Task<Tuple<T1,T2>> FromEvent<T1,T2>(this DependencyObject target, string event_name) {
var evt = target.GetType().GetEvent(event_name);
var delegate_type = evt.EventHandlerType;
var tcs = new TaskCompletionSource<Tuple<T1,T2>>();
@@ -14,18 +14,19 @@ namespace mbts.ui.shell
public partial class FloatingWindow : Window
{
bool _closed = false;
private IUndoActions _undo;
readonly IBookmarks _bookmark;
private IWindowManager _wm;
private ISavedWindowFactory _window_factory;
private ISettingsCollector _window_settings;
private Stack<Action> _recently_closed_windows = new Stack<Action>();
// if is_loaded is true, will NOT set width/height
internal FloatingWindow(TabDocument document, IWindowManager wm, IUndoActions undo, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
internal FloatingWindow(TabDocument document,
IWindowManager wm, IBookmarks bookmarks, ISettingsCollector settings, ISavedWindowFactory factory, bool is_loaded=false) {
_wm = wm;
_undo = undo;
_window_settings = settings;
_window_factory = factory;
_bookmark = bookmarks;
InitializeComponent();
if (string.IsNullOrEmpty(document.ViewModel.host_id)) {
@@ -67,6 +68,7 @@ namespace mbts.ui.shell
private ItemCollection Items { get { return _tabs.Items; } }
public IEnumerable<TabDocument> Documents => _tabs.Items.OfType<TabDocument>();
public bool IsEmpty { get { return _tabs.Items.Count == 0; } }
public bool SingleChild { get { return _tabs.Items.Count == 1; } }
public string host_id { get; private set; }
@@ -88,10 +90,10 @@ namespace mbts.ui.shell
if (sender != this) {
var undoaction = create_undo_action(document);
if (undoaction != null) {
IDisposable undoitem = _undo.push("Restore Tab '{0}'".fmt(document.Title), undoaction);
_recently_closed_windows.Push(() => { undoaction(); undoitem.Dispose(); });
_recently_closed_windows.Push(() => { undoaction(); });
}
}
_bookmark.save_bookmark(document.Model);
}
public void remove_document(TabDocument document) {
@@ -144,22 +146,9 @@ namespace mbts.ui.shell
protected override void OnClosed(EventArgs e) {
_closed = true;
TabDocument[] items = _tabs.Items.OfType<TabDocument>().ToArray();
if (items.Length > 0) {
// If we're closing and still have Tabs open we save the whole "window" worth of state in the undo stack
// If the application is shutting down this might be unnecessary work but it's not too costly
var undo_actions = items.Select(t => create_undo_action(t.ViewModel)).Where(item => item != null).ToArray();
if (undo_actions.Length > 0) {
string name = "Restore Window: '{0}'".fmt(string.Join("', '", items.Select(t => t.ViewModel.Title)));
_undo.push(name, () => {
foreach (var u in undo_actions) { u(); }
});
}
}
base.OnClosed(e);
foreach (TabDocument document in items) {
foreach (TabDocument document in _tabs.Items.OfType<TabDocument>().ToList()) {
if (document != null && document.ViewModel.CloseCommand.CanExecute(this)) {
document.ViewModel.CloseCommand.Execute(this);
}
+39 -12
View File
@@ -3,7 +3,9 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d" Style="{StaticResource {x:Type TabItem}}" d:DesignWidth="125"
xmlns:ui="clr-namespace:mbts.ui"
xmlns:convert="clr-namespace:mbts.ui.converters"
mc:Ignorable="d" d:DesignWidth="125"
Header="{Binding}" Content="{Binding AppContent}"
Background="#e6e6e6">
@@ -18,6 +20,18 @@
<Storyboard x:Key="_slide_back">
<DoubleAnimation Storyboard.TargetName="translate_transform" Storyboard.TargetProperty="X" To="100" Duration="0:0:0.25" FillBehavior="Stop" />
</Storyboard>
<DataTemplate x:Key="_edit_template">
<TextBox Text="{Binding TitleOverride}"
ui:TextBoxBehavior.CommitOnEnter="True"
ui:TextBoxBehavior.FocusOnLoaded="True"
ui:TextBoxBehavior.SelectAllOnFocus="True"
ui:TextBoxBehavior.ReleaseOnEscape="True"
Width="150" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0" />
</DataTemplate>
<DataTemplate x:Key="_template">
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"></TextBlock>
</DataTemplate>
<convert:BooleanToObjectConverter x:Key="MenuHeaderConverter" ValueForTrue="Rename Window" ValueForFalse="Add to My Windows" />
</ResourceDictionary>
</TabItem.Resources>
<TabItem.LayoutTransform>
@@ -28,25 +42,32 @@
<TabItem.RenderTransform>
<TranslateTransform x:Name="translate_transform" X="0" />
</TabItem.RenderTransform>
<TabItem.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" TextTrimming="CharacterEllipsis" HorizontalAlignment="Left" VerticalAlignment="Stretch" Grid.Column="0" Margin="0,0,4,0"> </TextBlock>
</DataTemplate>
</TabItem.HeaderTemplate>
<TabItem.Template>
<ControlTemplate TargetType="{x:Type TabItem}">
<Border x:Name="bd" BorderBrush="Black" BorderThickness="0,0,0.5,0" Background="#d6d6d6" Width="{DynamicResource TabWidth}" Height="{DynamicResource TabHeight}" MinWidth="40" Margin="0" SnapsToDevicePixels="True" DockPanel.Dock="Left">
<Border.ToolTip>
<TextBlock Text="{Binding Title}" />
</Border.ToolTip>
<Border.ContextMenu>
<ContextMenu>
<MenuItem Header="{Binding HasCustomTitle, Converter={StaticResource MenuHeaderConverter}}" Command="{Binding rename_command}">
<MenuItem.Icon>
<Image Source="{StaticResource Bookmarks}" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Remove from My Windows" Command="{Binding unrename_command}" IsEnabled="{Binding HasCustomTitle}" />
</ContextMenu>
</Border.ContextMenu>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0" Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
<Image SnapsToDevicePixels="True" MaxHeight="20" VerticalAlignment="Center" HorizontalAlignment="Left" Stretch="Uniform" Margin="5,0,5,0"
Source="{Binding Icon, Mode=OneTime}" RenderOptions.BitmapScalingMode="HighQuality" />
<!--<Image MaxHeight="10" HorizontalAlignment="Left" VerticalAlignment="Bottom" Stretch="Uniform" Margin="5"
Source="{StaticResource Bookmarks}" Visibility="{Binding HasCustomTitle, Converter={StaticResource BoolToVisibility}}" />-->
<ContentPresenter Grid.Column="1" x:Name="content" ContentSource="Header" TextBlock.Foreground="#666"
RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
HorizontalAlignment="Left"
@@ -62,12 +83,18 @@
<Setter Property="Panel.ZIndex" Value="1" />
<Setter Property="Background" TargetName="bd" Value="#F9F9F9" />
<Setter Property="TextBlock.Foreground" TargetName="content" Value="black" />
<!--<Setter Property="BorderThickness" TargetName="bd" Value="0,4,0.5,0" />-->
<!--<Setter Property="Margin" Value="-2,-2,-2,-1" />
<Setter Property="Margin" TargetName="content" Value="0,0,0,1" />
<Setter Property="Margin" TargetName="bd" Value="0,0,0,0" />-->
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</TabItem.Template>
<TabItem.Style>
<Style TargetType="TabItem" BasedOn="{StaticResource {x:Type TabItem}}">
<Setter Property="HeaderTemplate" Value="{StaticResource _template}" />
<Style.Triggers>
<DataTrigger Binding="{Binding EditMode}" Value="True">
<Setter Property="HeaderTemplate" Value="{StaticResource _edit_template}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TabItem.Style>
</TabItem>
@@ -18,8 +18,15 @@ namespace mbts.ui.shell {
public ImageSource IconSource { get { return _module.icon; } }
public UIElement MenuContent => new SparkMenu(_module.actions, _bus);
public void opening() { }
public void opening() {
(_module as IShellMenu)?.opening();
if (_module is ILazyMenuModule) {
RaisePropertyChanged(nameof(MenuContent));
}
}
public void closing() { }
public void closing() {
(_module as IShellMenu)?.closing();
}
}
}
@@ -24,10 +24,11 @@ namespace mbts.ui.shell
Dispatcher _dispatcher;
HwndContentHost _hwnd_host;
public ShellWindowVM(IShellWindow model, IWindowManager wm, IShellThreadFactory threads, string host = "") {
Logger.assert(model != null);
_model = model;
_model.TitleChanged += () => RaisePropertyChanged("Title");
_model.TitleChanged += () => { if (!HasCustomTitle) RaisePropertyChanged("Title"); };
_model.IconChanged += () => { _icon = null; RaisePropertyChanged("Icon"); };
_wm = wm;
host_id = host;
@@ -40,13 +41,55 @@ namespace mbts.ui.shell
if (HasTabAffinity) {
this.host_id = ((ITabAffineWindow)model).tab_group;
}
_edit_mode = backing(nameof(EditMode), false);
}
public string host_id { get; set; }
public Dispatcher Dispatcher { get { return _dispatcher; } }
public string Title { get { return _model.Title; } }
public string Title { get { return HasCustomTitle? TitleOverride : _model.Title; } }
string _title_override;
public string TitleOverride {
get => _title_override;
set {
if (_title_override != value) {
if (value == _model.Title) {
_title_override = null;
}
else {
_title_override = value;
}
RaisePropertyChanged(nameof(TitleOverride), nameof(Title), nameof(HasCustomTitle));
}
_edit_mode.set(false);
}
}
public bool HasCustomTitle => !string.IsNullOrWhiteSpace(TitleOverride);
Property<bool> _edit_mode;
public bool EditMode => _edit_mode.Value;
public ICommand rename_command => new DelegateCommand(begin_rename);
public ICommand unrename_command => new DelegateCommand(remove_custom);
private void begin_rename() {
if (string.IsNullOrEmpty(TitleOverride)) {
_title_override = Title;
}
_edit_mode.set(true);
}
private void remove_custom() {
TitleOverride = string.Empty;
//begin_close(this);
}
public string OwnerName { get { return _model.Owner == null ? "" : _model.Owner.name; } }
public ICommand CloseCommand { get { return new DelegateCommand(begin_close); } }
public double InitialWidth { get; private set; }
public double InitialHeight { get; private set; }
@@ -155,18 +198,14 @@ namespace mbts.ui.shell
}
}
public byte[] GetSaveState() {
MemoryStream stream = new MemoryStream();
_model.GetState(stream);
return stream.ToArray();
}
public IShellWindow Model { get { return _model; } }
public bool IsSlow { get { return _model is ISlowWindow; } }
public bool IsShared { get { return _model is ISharedWindow; } }
public bool HasTabAffinity { get { return _model is ITabAffineWindow; } }
public void Dispose() {
if (IsShared) {
// Shared threads continue running
+26 -13
View File
@@ -20,25 +20,31 @@ namespace mbts.ui.shell {
[Export(typeof(IWindowManager))]
[Export(typeof(IWindowsInterop))]
[Export]
public class WindowManager : IWindowManager, IWindowsInterop, IDisposable {
Dictionary<IShellWindow, TabDocument> _documents = new Dictionary<IShellWindow, TabDocument>();
Dictionary<string, FloatingWindow> _windows = new Dictionary<string, FloatingWindow>();
IShellThreadFactory _threads;
readonly IShellThreadFactory _threads;
Lazy<ISettingsCollector> _window_settings;
ISavedWindowFactory _window_factory;
IUndoActions _undo;
readonly ISavedWindowFactory _window_factory;
readonly Lazy<IBookmarks> _bookmarks;
IDisposable _dispose;
object _lock = new object();
[ImportingConstructor]
public WindowManager(
IShellThreadFactory threads, Lazy<ISettingsCollector> settings,
ISavedWindowFactory factory, IUndoActions undo, IBus bus) {
ISavedWindowFactory factory, Lazy<IBookmarks> bookmarks, IBus bus) {
_threads = threads;
_window_settings = settings;
_window_factory = factory;
_undo = undo;
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
_bookmarks = bookmarks;
_dispose =
Disposable.Create(
bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore()),
bus.subscribe(SparkTags.ShowWindowAsync).OnDispatcher().Subscribe(w => add(w).ignore()),
bus.subscribe(SparkTags.ActivateWindow).OnDispatcher().Subscribe(MakeVisible)
);
}
#region Window creation
@@ -48,6 +54,7 @@ namespace mbts.ui.shell {
var state = model.start_position();
if (state != null) {
viewmodel.host_id = state.host_id ?? "";
viewmodel.TitleOverride = state.custom_title;
}
var window = CreateWindow(content, state);
@@ -66,10 +73,10 @@ namespace mbts.ui.shell {
}
} else {
if (position != null) {
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory, true);
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory, true);
window.set_position(position);
} else
window = new FloatingWindow(document, this, _undo, _window_settings.Value, _window_factory);
window = new FloatingWindow(document, this, _bookmarks.Value, _window_settings.Value, _window_factory);
_windows.Add(window.host_id, window);
window.Closed += (o, e) => _windows.Remove(window.host_id);
}
@@ -81,12 +88,19 @@ namespace mbts.ui.shell {
public IReadOnlyList<IShellWindow> list_windows() =>
_documents.Keys.ToList();
public IReadOnlyList<TabDocument> list_documents() => _documents.Values.ToList();
public async Task add(IShellWindow window) {
if (!Application.Current.Dispatcher.CheckAccess())
await Application.Current.Dispatcher.InvokeAsync(() => add(window));
Logger.info(bts.log.Subsystem.Ui, $"Opening window {window.diagnostic_name()}.");
lock (_lock)
lock (_lock) {
_documents[window] = _create_document(window);
}
}
public async Task add(Task<IShellWindow> window) {
await add(await window);
}
public void remove(IShellWindow window) {
@@ -139,14 +153,13 @@ namespace mbts.ui.shell {
#region IWindowsInterop
public WindowPosition GetPosition(IShellWindow shell_window) {
return GetPosition(shell_window, null);
}
public WindowPosition GetPosition(IShellWindow shell_window, FloatingWindow owning_window) {
FloatingWindow window = owning_window == null ? window_for(shell_window) : owning_window;
FloatingWindow window = window_for(shell_window);
var doc = window.Documents.FirstOrDefault(td => td.ViewModel.Model == shell_window);
if (window != null) {
WindowPosition pos = window.get_position();
pos.tab_index = window.document_index(shell_window);
pos.host_id = window.host_id;
pos.custom_title = doc?.ViewModel?.TitleOverride;
return pos;
}
Logger.log(bts.log.Level.Warn, bts.log.Subsystem.Ui, "IShellWindow '{0}' does not have a valid position. It may not be displayed".fmt(shell_window.Title));
@@ -19,6 +19,7 @@ using mbts.persist;
namespace mbts.ui.shell
{
[Export]
public class WorkspacesModel : ViewModelBaseWithValidation
{
@@ -35,10 +36,11 @@ namespace mbts.ui.shell
private bool _closing = false;
private ILauncher _launcher;
private IUndoActions _undo;
readonly IBookmarks _bookmarks;
[ImportingConstructor]
public WorkspacesModel(ISettingsCollector settings, ISavedWindowFactory window_restore,
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist,
IWindowManager windows, ObservableCollection<IShellModule> modules, IPersist persist, IBookmarks bm,
IUserPersist user_persist, ILauncher launcher, IUndoActions undo, [Import("workspace")]string initial_workspace) {
_settings_collector = settings;
_window_restore = window_restore;
@@ -48,6 +50,7 @@ namespace mbts.ui.shell
_modules = modules;
_launcher = launcher;
_undo = undo;
_bookmarks = bm;
_launcher.closing += _launcher_closing;
Workspaces = new ObservableCollection<WorkspaceViewModel>();
init(initial_workspace).ignore();
@@ -150,6 +153,7 @@ namespace mbts.ui.shell
workspace.windows = _settings_collector.get_window_state().ToList();
workspace.modules = _settings_collector.get_module_state().ToList();
workspace.launcher_state = _settings_collector.get_launcher_state();
workspace.named_windows = _bookmarks.bookmarks.ToList();
return workspace;
}
@@ -166,6 +170,7 @@ namespace mbts.ui.shell
await Task.Yield();
}
workspace.launcher_state = _settings_collector.get_launcher_state();
workspace.named_windows = _bookmarks.bookmarks.ToList();
return workspace;
}
@@ -226,10 +231,15 @@ namespace mbts.ui.shell
}
}
// Notify workspace changed
if (WorkspaceLoaded != null) {
WorkspaceLoaded(name);
_bookmarks.clear();
if (def.named_windows != null) {
foreach (var window in def.named_windows) {
_bookmarks.save_bookmark(window);
}
}
// Notify workspace changed
WorkspaceLoaded?.Invoke(name);
}
public Task delete_workspace(string name, WorkspaceDefinition def) {
@@ -244,18 +254,20 @@ namespace mbts.ui.shell
}
return (def.is_shared ? _public : _private).clear(WORKSPACE_NAMESPACE, name);
}
}
// The serialized data for the Workspace
[ProtoContract(Name = "WorkspaceDefinition")]
public class WorkspaceDefinition
{
// Converted the protobuf type ShellSettings to c#
// Avoid re-using id: 1,3,4,5,6,7
[ProtoMember(2, IsRequired = false)]
public List<WindowState> windows { get; set; }
// named windows which are not currently open
[ProtoMember(3)]
public List<WindowState> named_windows { get; set; }
[ProtoMember(9, IsRequired = false)]
public List<ModuleState> modules { get; set; }
@@ -37,13 +37,13 @@ namespace bts.spark
if (this.IsRoot) {
return Enumerable.Empty<OrderNode>();
}
return this.Parent.ToIEnumerable().Concat(this.Parent.Ancestors);
return new[] { this.Parent }.Concat(this.Parent.Ancestors);
}
}
public IEnumerable<OrderNode> MeAndDescedants {
get {
return this.ToIEnumerable().Concat(this.children.SelectMany(c => c.MeAndDescedants));
return new[] { this }.Concat(this.children.SelectMany(c => c.MeAndDescedants));
}
}
+2 -5
View File
@@ -89,7 +89,7 @@ size_t load_securities(
void store_if_new(const std::vector<Security*>& securities) {
auto dbptr = open_db();
auto insert = utils::prepare_query(dbptr, "insert into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
auto insert = utils::prepare_query(dbptr, "insert or ignore into securities (key, symbol, venue, type, security_blob, expiration) values (?,?,?,?,?,?)");
sqlite3_exec_nofail(dbptr, "BEGIN TRANSACTION");
std::string buffer;
for (const api::Security* psecurity : securities) {
@@ -100,10 +100,7 @@ void store_if_new(const std::vector<Security*>& securities) {
SQLITE_CHK(sqlite3_bind_int(insert.get(), 4, securitytype_of(*psecurity)), dbptr);
SQLITE_CHK(sqlite3_bind_blob(insert.get(), 5, buffer.c_str(), buffer.size(), SQLITE_STATIC), dbptr);
SQLITE_CHK(sqlite3_bind_int(insert.get(), 6, expiration(*psecurity).to_short()), dbptr);
auto result = sqlite3_step(insert.get());
if (result != SQLITE_DONE) {
LOG(md, Warn, "Failed to insert security into security db. key={}", psecurity->id());
}
sqlite3_step(insert.get());
sqlite3_reset(insert.get());
}
}
+13 -3
View File
@@ -133,6 +133,8 @@ void SecurityIDRecorder::work_db() {
// Once a security has a key it is public so record it in the security database
std::shared_ptr<SecurityIDEntry> temp;
vector<std::shared_ptr<SecurityIDEntry>> new_entries;
// SPARK-1446 Need to try to add existing securities to the db in case we deleted them by hand
vector<std::shared_ptr<SecurityIDEntry>> existing_securities;
vector<std::shared_ptr<SecurityIDEntry>> defer;
vector<string> new_descriptors;
size_t count = 0;
@@ -150,6 +152,7 @@ void SecurityIDRecorder::work_db() {
if (key != invalid_security_key) {
temp->security.set_id(key);
temp->callback(temp->security);
existing_securities.push_back(move(temp));
}
else {
// always push in parallel
@@ -168,7 +171,7 @@ void SecurityIDRecorder::work_db() {
}
if (new_entries.size() > 0) {
if (!new_entries.empty()) {
sqlite3_exec_nofail(_self->db.get(), "BEGIN TRANSACTION");
for (size_t x = 0; x < new_entries.size(); ++x) {
@@ -179,15 +182,22 @@ void SecurityIDRecorder::work_db() {
}
sqlite3_exec_nofail(_self->db.get(), "COMMIT TRANSACTION");
}
// record the securities
// record the securities
if (!new_entries.empty() || !existing_securities.empty()) {
std::vector<Security*> save_these;
save_these.reserve(new_entries.size());
save_these.reserve(new_entries.size() + existing_securities.size());
for (const auto& e : new_entries) {
save_these.push_back(&e->security);
}
for (const auto& e : existing_securities) {
save_these.push_back(&e->security);
}
db::store_if_new(save_these);
}
if (!new_entries.empty()) {
for (size_t x = 0; x < new_entries.size(); ++x) {
Security& security = new_entries[x]->security;
new_entries[x]->callback(security);
+13
View File
@@ -0,0 +1,13 @@
@echo off
@setlocal
SETLOCAL
if "%SPARKVM_PORT%"=="" set SPARKVM_PORT=22
if "%1" == "" goto :err
echo.
ssh bts@sparkvm -p %SPARKVM_PORT% -o StrictHostKeyChecking=no -t "cd /home/bts/spark && git checkout %1 && git reset --hard && git clean -fd && git pull && git submodule update"
ENDLOCAL
goto :eof
:err
echo usage git_switch_linux [branch]