Compare commits
4 commits
106d4d0ed8
...
605c34a9f5
Author | SHA1 | Date | |
---|---|---|---|
605c34a9f5 | |||
4c212c4c21 | |||
9913c3118f | |||
4544acd01e |
5 changed files with 515 additions and 95 deletions
232
MainWindow.cs
232
MainWindow.cs
|
@ -24,14 +24,16 @@ using System.Text.Json;
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Net;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using CsvHelper;
|
||||
using Gtk;
|
||||
|
||||
namespace Buypeeb {
|
||||
|
||||
class MainWindow : Window {
|
||||
|
||||
private string location;
|
||||
private JsonSerializerOptions jsonOptions;
|
||||
|
||||
private ListStore items;
|
||||
private Settings settings;
|
||||
|
@ -45,11 +47,13 @@ namespace Buypeeb {
|
|||
private Box selectionViewBox;
|
||||
private Label endingLabel;
|
||||
private bool queueActive;
|
||||
private Dictionary<string, CheckButton> filterChecks = new Dictionary<string, CheckButton>();
|
||||
|
||||
// ...to here.
|
||||
|
||||
static SemaphoreSlim taskLimit = new SemaphoreSlim(6);
|
||||
private Queue<string> updateQueue = new Queue<string>();
|
||||
private IEnumerable<YahooAuctionsItem> filterQuery;
|
||||
|
||||
private YahooAuctionsItem selectedItem {
|
||||
get {
|
||||
|
@ -66,6 +70,11 @@ namespace Buypeeb {
|
|||
public MainWindow() : this(new Builder("main.glade")) { }
|
||||
|
||||
private MainWindow(Builder builder) : base(builder.GetObject("wndMain").Handle) {
|
||||
|
||||
this.jsonOptions = new JsonSerializerOptions {
|
||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.Create(System.Text.Unicode.UnicodeRanges.All)
|
||||
};
|
||||
|
||||
if (Environment.OSVersion.Platform == PlatformID.Win32NT) {
|
||||
// C:\Users\Beebus\AppData\Roaming\Lynnear Software\buypeeb
|
||||
this.location = System.IO.Path.Combine(Environment.ExpandEnvironmentVariables("%APPDATA%"), "Lynnear Software", "buypeeb");
|
||||
|
@ -99,13 +108,33 @@ namespace Buypeeb {
|
|||
|
||||
this.selectionViewBox = (Box)builder.GetObject("SelectionViewBox");
|
||||
this.endingLabel = (Label)builder.GetObject("LabelSelectedEnding");
|
||||
foreach (var name in new List<string> { "Favourites", "NonFavourites", "Active", "Ended", "EndingToday", "EndingAfterToday", "WithWinPrice", "WithNoWinPrice" }) {
|
||||
this.filterChecks.Add(name, (CheckButton)builder.GetObject($"CheckButtonFilter{name}"));
|
||||
this.filterChecks[name].Active = false;
|
||||
}
|
||||
|
||||
// father forgive me for i have lynned
|
||||
this.filterQuery =
|
||||
from item in this.settings.watchlist.Values.ToList()
|
||||
where (item.favourite != this.filterChecks["Favourites"].Active ||
|
||||
item.favourite == this.filterChecks["NonFavourites"].Active) &&
|
||||
(item.available != this.filterChecks["Active"].Active ||
|
||||
item.available == this.filterChecks["Ended"].Active) &&
|
||||
(item.endingToday != this.filterChecks["EndingToday"].Active ||
|
||||
item.endingToday == this.filterChecks["EndingAfterToday"].Active) &&
|
||||
(item.hasWinPrice != this.filterChecks["WithWinPrice"].Active ||
|
||||
item.hasWinPrice == this.filterChecks["WithNoWinPrice"].Active)
|
||||
select item;
|
||||
|
||||
Console.WriteLine(this.filterChecks["Favourites"].Active);
|
||||
|
||||
// bind treeview columns to watchlist instead of needing to manually sync its liststore
|
||||
this.itemTreeView = (TreeView)builder.GetObject("TreeViewItems");
|
||||
this.items = new ListStore(typeof(YahooAuctionsItem));
|
||||
this.RenderList();
|
||||
var filteredItems = new TreeModelFilter(this.items, null);
|
||||
filteredItems.VisibleFunc = this.ItemFilter;
|
||||
|
||||
this.itemTreeView.Model = this.items;
|
||||
this.itemTreeView.Model = filteredItems;
|
||||
TreeCellDataFunc[] funcs = {
|
||||
new TreeCellDataFunc(this.RenderColumnFavourite),
|
||||
new TreeCellDataFunc(this.RenderColumnName),
|
||||
|
@ -120,6 +149,7 @@ namespace Buypeeb {
|
|||
c.SetCellDataFunc(c.Cells[0], funcs[i]);
|
||||
}
|
||||
|
||||
this.RenderList();
|
||||
this.UpdateItems();
|
||||
GLib.Timeout.Add(1000, new GLib.TimeoutHandler(UpdateSelectionEndTime));
|
||||
|
||||
|
@ -137,11 +167,13 @@ namespace Buypeeb {
|
|||
// TODO: surely there's a better way to do this
|
||||
TreeIter iter;
|
||||
this.itemTreeView.Model.GetIterFirst(out iter);
|
||||
var m = (TreeModelFilter)this.itemTreeView.Model;
|
||||
|
||||
for (int i = 0; i < this.itemTreeView.Model.IterNChildren(); i++) {
|
||||
var x = (YahooAuctionsItem)this.itemTreeView.Model.GetValue(iter, 0);
|
||||
Console.WriteLine(x);
|
||||
if (x.id == id) {
|
||||
return (this.itemTreeView.Model.GetPath(iter), iter);
|
||||
return (m.ConvertPathToChildPath(m.GetPath(iter)), m.ConvertIterToChildIter(iter));
|
||||
}
|
||||
else {
|
||||
this.itemTreeView.Model.IterNext(ref iter);
|
||||
|
@ -153,7 +185,7 @@ namespace Buypeeb {
|
|||
}
|
||||
|
||||
private void SaveSettings() {
|
||||
string j = JsonSerializer.Serialize(this.settings);
|
||||
string j = JsonSerializer.Serialize(this.settings, this.jsonOptions);
|
||||
string p = System.IO.Path.Combine(this.location, "userdata.json");
|
||||
Console.WriteLine(j);
|
||||
if (!Directory.Exists(this.location)) {
|
||||
|
@ -176,18 +208,23 @@ namespace Buypeeb {
|
|||
Gtk.Application.Invoke(delegate {
|
||||
// TODO: find a way to not have to do this. i think we need to avoid actually modifying the items outside of the main thread :/
|
||||
var pathAndIter = this.GetRow(id);
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
if (pathAndIter.path != null) {
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
}
|
||||
});
|
||||
|
||||
using (WebClient client = new WebClient()) {
|
||||
// TODO: download should have timeout
|
||||
item.Update(client.DownloadString(item.url));
|
||||
// item.Update(File.ReadAllText("yahoo.html"));
|
||||
// item.Update(client.DownloadString(item.url));
|
||||
Thread.Sleep(5000);
|
||||
item.Update(File.ReadAllText("yahoo.html"));
|
||||
}
|
||||
|
||||
Gtk.Application.Invoke(delegate {
|
||||
var pathAndIter = this.GetRow(id);
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
if (pathAndIter.path != null) {
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
}
|
||||
if (item == this.selectedItem) {
|
||||
// if the user has this item selected and it just became ready, enable the selection box
|
||||
this.selectionViewBox.Sensitive = true;
|
||||
|
@ -272,11 +309,11 @@ namespace Buypeeb {
|
|||
var info = new Dictionary<string, string> {
|
||||
{ "Name", item.name },
|
||||
{ "YahooName", item.originalName },
|
||||
{ "Price", item.PriceJPY() },
|
||||
{ "PriceAUD", item.PriceAUD() },
|
||||
{ "Price", item.priceJPY },
|
||||
{ "PriceAUD", item.priceAUD },
|
||||
{ "Ending", "..." },
|
||||
{ "Bids", $"{item.bids}" },
|
||||
{ "BuyItNow", item.winPrice == 0 ? "No" : $"¥{item.PriceJPY(true)} (${item.PriceAUD(true)})" },
|
||||
{ "BuyItNow", item.winPrice == 0 ? "No" : $"{item.winPriceJPY} ({item.winPriceAUD})" },
|
||||
{ "AutoExtension", item.autoExtension ? "Yes" : "No" },
|
||||
{ "LastUpdated", "Last updated: heeeenlo" }
|
||||
};
|
||||
|
@ -305,25 +342,31 @@ namespace Buypeeb {
|
|||
}
|
||||
}
|
||||
|
||||
private MessageDialog OkCancelDialogue(string message) {
|
||||
private MessageDialog MsgBox(string message, ButtonsType buttonsType = ButtonsType.OkCancel) {
|
||||
var md = new MessageDialog(
|
||||
parent_window: this,
|
||||
flags: DialogFlags.DestroyWithParent | DialogFlags.Modal,
|
||||
type: MessageType.Question,
|
||||
bt: ButtonsType.OkCancel,
|
||||
bt: buttonsType,
|
||||
format: message
|
||||
);
|
||||
md.KeepAbove = true;
|
||||
md.Resizable = false;
|
||||
md.FocusOnMap = true;
|
||||
md.Title = "buypeeb";
|
||||
md.Title = "Buypeeb";
|
||||
|
||||
return md;
|
||||
}
|
||||
|
||||
// show a simple entry dialogue that allows the user to enter text and either cancel or submit it
|
||||
private (Boolean accepted, string response) EntryDialogue(string title = "Buypeeb", string message = "Hi there!", string prefill = null) {
|
||||
Dialog ed = new Dialog(title, null, DialogFlags.DestroyWithParent | DialogFlags.Modal, "Cancel", ResponseType.Cancel, "OK", ResponseType.Ok);
|
||||
Dialog ed = new Dialog(
|
||||
title: title,
|
||||
parent: this,
|
||||
flags: DialogFlags.DestroyWithParent | DialogFlags.Modal,
|
||||
/* button_data: */ "Cancel", ResponseType.Cancel, "OK", ResponseType.Ok
|
||||
);
|
||||
|
||||
ed.DefaultResponse = ResponseType.Ok;
|
||||
ed.KeepAbove = true;
|
||||
|
||||
|
@ -353,9 +396,11 @@ namespace Buypeeb {
|
|||
|
||||
private void RenderList() {
|
||||
this.items.Clear();
|
||||
foreach (var item in this.settings.watchlist) {
|
||||
items.AppendValues(item.Value);
|
||||
foreach (var item in this.settings.watchlist.Values) {
|
||||
items.AppendValues(item);
|
||||
}
|
||||
var m = (TreeModelFilter)this.itemTreeView.Model;
|
||||
m.Refilter();
|
||||
}
|
||||
|
||||
// event handlers
|
||||
|
@ -387,7 +432,7 @@ namespace Buypeeb {
|
|||
|
||||
private void ButtonClearEndedClicked(object sender, EventArgs a) {
|
||||
Console.WriteLine("ButtonClearEndedClicked");
|
||||
var md = this.OkCancelDialogue("Are you sure you want to remove all ended auctions from the list?");
|
||||
var md = this.MsgBox("Are you sure you want to remove all ended auctions from the list?");
|
||||
var r = (ResponseType)md.Run();
|
||||
md.Dispose();
|
||||
if (r != ResponseType.Ok) {
|
||||
|
@ -412,12 +457,120 @@ namespace Buypeeb {
|
|||
Console.WriteLine("ButtonClearAllClicked");
|
||||
}
|
||||
|
||||
private void ButtonOpenClicked(object sender, EventArgs a) {
|
||||
var od = new FileChooserDialog(
|
||||
title: "Open userdata.json",
|
||||
parent: this,
|
||||
action: FileChooserAction.Open,
|
||||
"Cancel", ResponseType.Cancel, "Open", ResponseType.Accept
|
||||
);
|
||||
|
||||
var odf = new FileFilter();
|
||||
odf.Name = "JSON files";
|
||||
odf.AddMimeType("application/json");
|
||||
odf.AddPattern("*.json");
|
||||
od.AddFilter(odf);
|
||||
|
||||
if (od.Run() == (int)ResponseType.Accept) {
|
||||
try {
|
||||
string j = File.ReadAllText(od.Filename);
|
||||
this.settings = JsonSerializer.Deserialize<Settings>(j);
|
||||
this.RenderList();
|
||||
this.UpdateItems();
|
||||
}
|
||||
catch (Exception e) {
|
||||
Console.WriteLine(e);
|
||||
var md = MsgBox($"Failed to load {od.Filename}!\n{e.Message}", ButtonsType.Ok);
|
||||
md.Run();
|
||||
md.Dispose();
|
||||
}
|
||||
}
|
||||
od.Dispose();
|
||||
}
|
||||
|
||||
private void ButtonSaveClicked(object sender, EventArgs a) {
|
||||
this.SaveSettings();
|
||||
}
|
||||
|
||||
private void ButtonSaveAsClicked(object sender, EventArgs a) {
|
||||
var sd = new FileChooserDialog(
|
||||
title: "Save userdata.json",
|
||||
parent: this,
|
||||
action: FileChooserAction.Save,
|
||||
"Cancel", ResponseType.Cancel, "Save", ResponseType.Accept
|
||||
);
|
||||
sd.CurrentName = "userdata.json";
|
||||
|
||||
var sdf = new FileFilter();
|
||||
sdf.Name = "JSON files";
|
||||
sdf.AddMimeType("application/json");
|
||||
sdf.AddPattern("*.json");
|
||||
sd.AddFilter(sdf);
|
||||
|
||||
if (sd.Run() == (int)ResponseType.Accept) {
|
||||
try {
|
||||
if (!File.Exists(sd.Filename)) {
|
||||
var fs = File.CreateText(sd.Filename);
|
||||
fs.Close();
|
||||
}
|
||||
File.WriteAllText(sd.Filename, JsonSerializer.Serialize(this.settings, jsonOptions));
|
||||
}
|
||||
catch (Exception e) {
|
||||
Console.WriteLine(e);
|
||||
var md = MsgBox($"Failed to write {sd.Filename}!\n{e.Message}.", ButtonsType.Ok);
|
||||
md.Run();
|
||||
md.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
sd.Dispose();
|
||||
}
|
||||
|
||||
private void ButtonExportClicked(object sender, EventArgs a) {
|
||||
var readyQuery = from item in this.settings.watchlist.Values.ToList() where !item.ready select item;
|
||||
foreach (var item in readyQuery) {
|
||||
Console.WriteLine(item.name);
|
||||
}
|
||||
|
||||
if (readyQuery.Count() != 0) {
|
||||
var md = MsgBox("Please wait for all items to update before exporting a CSV.", ButtonsType.Ok);
|
||||
md.Run();
|
||||
md.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
var sd = new FileChooserDialog(
|
||||
title: "Export watchlist as CSV",
|
||||
parent: this,
|
||||
action: FileChooserAction.Save,
|
||||
"Cancel", ResponseType.Cancel, "Save", ResponseType.Accept
|
||||
);
|
||||
sd.CurrentName = "buypeeb.csv";
|
||||
|
||||
var sdf = new FileFilter();
|
||||
sdf.Name = "CSV files";
|
||||
sdf.AddMimeType("text/csv");
|
||||
sdf.AddPattern("*.csv");
|
||||
sd.AddFilter(sdf);
|
||||
|
||||
if (sd.Run() == (int)ResponseType.Accept) {
|
||||
try {
|
||||
using (var writer = new StreamWriter(sd.Filename))
|
||||
using (var csv = new CsvWriter(writer, System.Globalization.CultureInfo.InvariantCulture)) {
|
||||
csv.WriteRecords(this.settings.watchlist);
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
Console.WriteLine(e);
|
||||
MsgBox($"Failed to write {sd.Filename}!\n{e.Message}.", ButtonsType.Ok);
|
||||
}
|
||||
}
|
||||
|
||||
sd.Dispose();
|
||||
}
|
||||
|
||||
private void ButtonQuitClicked(object sender, EventArgs a) {
|
||||
var md = this.OkCancelDialogue("Are you sure you want to quit?");
|
||||
var md = this.MsgBox("Are you sure you want to quit?");
|
||||
|
||||
ResponseType response = (ResponseType)md.Run();
|
||||
md.Dispose();
|
||||
|
@ -442,7 +595,7 @@ namespace Buypeeb {
|
|||
private void ButtonSelectedRemoveClicked(object sender, EventArgs a) {
|
||||
var item = this.selectedItem;
|
||||
|
||||
var md = this.OkCancelDialogue($"Are you sure you want to remove the item \"{item.name}\"?"); // TODO: this looks bad being all on one line
|
||||
var md = this.MsgBox($"Are you sure you want to remove the item \"{item.name}\"?"); // TODO: this looks bad being all on one line
|
||||
|
||||
ResponseType response = (ResponseType)md.Run();
|
||||
md.Dispose();
|
||||
|
@ -476,7 +629,9 @@ namespace Buypeeb {
|
|||
|
||||
// i don't know why this is necessary
|
||||
var pathAndIter = this.GetRow(this.selectedItem.id);
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
if (pathAndIter.path != null) {
|
||||
this.items.EmitRowChanged(pathAndIter.path, pathAndIter.iter);
|
||||
}
|
||||
}
|
||||
|
||||
// timers
|
||||
|
@ -527,12 +682,12 @@ namespace Buypeeb {
|
|||
|
||||
private void RenderColumnPriceYen(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) {
|
||||
YahooAuctionsItem item = (YahooAuctionsItem)model.GetValue(iter, 0);
|
||||
(cell as Gtk.CellRendererText).Text = item.ready ? item.PriceJPY() : "...";
|
||||
(cell as Gtk.CellRendererText).Text = item.ready ? item.priceJPY : "...";
|
||||
}
|
||||
|
||||
private void RenderColumnPriceAUD(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) {
|
||||
YahooAuctionsItem item = (YahooAuctionsItem)model.GetValue(iter, 0);
|
||||
(cell as Gtk.CellRendererText).Text = item.ready ? item.PriceAUD() : "...";
|
||||
(cell as Gtk.CellRendererText).Text = item.ready ? item.priceAUD : "...";
|
||||
}
|
||||
|
||||
private void RenderColumnEnding(Gtk.TreeViewColumn column, Gtk.CellRenderer cell, Gtk.ITreeModel model, Gtk.TreeIter iter) {
|
||||
|
@ -559,5 +714,34 @@ namespace Buypeeb {
|
|||
}
|
||||
(cell as Gtk.CellRendererText).Text = item.ready ? ending : "...";
|
||||
}
|
||||
|
||||
// tree filter
|
||||
|
||||
private bool ItemFilter(ITreeModel model, TreeIter iter) {
|
||||
var item = (YahooAuctionsItem)model.GetValue(iter, 0);
|
||||
|
||||
bool Filtered(string name) {
|
||||
return this.filterChecks[name].Active;
|
||||
}
|
||||
|
||||
// first, check to see if any filters are set that would exclude everything, such as hiding both active and ended auctions
|
||||
// if so, there's no need to run the more expensive linq query
|
||||
if (
|
||||
(Filtered("Favourites") && Filtered("NonFavourites")) ||
|
||||
(Filtered("Active") && Filtered("Ended")) ||
|
||||
(Filtered("EndingToday") && Filtered("EndingAfterToday")) ||
|
||||
(Filtered("WithWinPrice") && Filtered("WithNoWinPrice"))
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// this.filterQuery.ToList().ForEach(Console.WriteLine);
|
||||
return this.filterQuery.Contains(item);
|
||||
}
|
||||
|
||||
private void RunFilter(object sender, EventArgs a) {
|
||||
var m = (TreeModelFilter)this.itemTreeView.Model;
|
||||
m.Refilter();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,15 +18,7 @@ namespace Buypeeb {
|
|||
if (this.watchlist == null) {
|
||||
// either this is the first time the program has been run, or there's something wrong with userdata.json
|
||||
this.watchlist = new Dictionary<string, YahooAuctionsItem>();
|
||||
// this.Watch("https://buypeeb.biz/whatever/k12345", "my thingy");
|
||||
// this.Watch("https://buypeeb.biz/whatever/z09876", "your thingy");
|
||||
// this.Watch("https://buypeeb.biz/whatever/h55555", "our thingy");
|
||||
// for (int i = 0; i < 10; i++) {
|
||||
// this.Watch($"https://buypeeb.biz/whatever/x{i * 123}", $"filler {i}");
|
||||
// }
|
||||
// this.watchlist["k12345"].Update();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public YahooAuctionsItem Watch(string url, string name) {
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace Buypeeb {
|
|||
// there's not really a need for it i guess but it's my program and i can do what i want
|
||||
public string id { get; set; }
|
||||
public string name { get; set; }
|
||||
public int price = 0;
|
||||
private int price = 0;
|
||||
public int winPrice;
|
||||
public string originalName { get; set; }
|
||||
public string notes { get; set; }
|
||||
|
@ -45,6 +45,13 @@ namespace Buypeeb {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: don't serialise this stuff
|
||||
public string priceJPY { get { return $"¥{this.price}"; } }
|
||||
public string winPriceJPY { get { return $"¥{this.winPrice}"; } }
|
||||
public string priceAUD { get { return $"${(this.price / 75.0):f2}"; } }
|
||||
public string winPriceAUD { get { return $"${(this.winPrice / 75.0):f2}"; } }
|
||||
public bool endingToday { get { return this.endDate.DayOfYear == DateTime.UtcNow.DayOfYear; } }
|
||||
public bool hasWinPrice { get { return this.winPrice != 0; } }
|
||||
private bool success { get; set; } // TODO: custom setter that throws an exception if set to false or something idk
|
||||
|
||||
public YahooAuctionsItem(string id, string name) {
|
||||
|
@ -104,13 +111,8 @@ namespace Buypeeb {
|
|||
}
|
||||
}
|
||||
|
||||
public string PriceAUD(bool win = false) {
|
||||
double aud = win ? this.winPrice / 75.0 : this.price / 75.0;
|
||||
return $"${aud:f2}";
|
||||
}
|
||||
|
||||
public string PriceJPY(bool win = false) {
|
||||
return win ? $"¥{this.winPrice}" : $"¥{this.price}";
|
||||
public override string ToString() {
|
||||
return $"{this.id}: {this.name}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="15.0.5" />
|
||||
<PackageReference Include="GtkSharp" Version="3.22.25.128" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
351
ui/main.glade
351
ui/main.glade
|
@ -3,6 +3,171 @@
|
|||
<interface>
|
||||
<requires lib="gtk+" version="3.22"/>
|
||||
<object class="GtkTextBuffer" id="TextBufferSelectedNotes"/>
|
||||
<object class="GtkPopover" id="popover1">
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">5</property>
|
||||
<property name="margin_end">5</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<property name="spacing">3</property>
|
||||
<child>
|
||||
<object class="GtkLabel">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_top">5</property>
|
||||
<property name="margin_bottom">5</property>
|
||||
<property name="label" translatable="yes">Items to hide:</property>
|
||||
<attributes>
|
||||
<attribute name="weight" value="bold"/>
|
||||
</attributes>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterFavourites">
|
||||
<property name="label" translatable="yes">Favourites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterNonFavourites">
|
||||
<property name="label" translatable="yes">Non-favourites</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterActive">
|
||||
<property name="label" translatable="yes">Active</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterEnded">
|
||||
<property name="label" translatable="yes">Ended</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterEndingToday">
|
||||
<property name="label" translatable="yes">Ending today</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterEndingAfterToday">
|
||||
<property name="label" translatable="yes">Ending after today</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">6</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterWithWinPrice">
|
||||
<property name="label" translatable="yes">With win price</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">7</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="CheckButtonFilterWithNoWinPrice">
|
||||
<property name="label" translatable="yes">With no win price</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
<signal name="toggled" handler="RunFilter" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">8</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Show all</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">9</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<object class="GtkWindow" id="wndMain">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="default_width">810</property>
|
||||
|
@ -152,12 +317,12 @@
|
|||
<child>
|
||||
<object class="GtkToolButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Open</property>
|
||||
<property name="label" translatable="yes">Open</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-open</property>
|
||||
<signal name="clicked" handler="ButtonOpenClicked" swapped="no"/>
|
||||
<accelerator key="o" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -184,12 +349,28 @@
|
|||
<child>
|
||||
<object class="GtkToolButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Export as...</property>
|
||||
<property name="label" translatable="yes">Export as...</property>
|
||||
<property name="tooltip_text" translatable="yes">Save as...</property>
|
||||
<property name="label" translatable="yes">Save as...</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-save-as</property>
|
||||
<signal name="clicked" handler="ButtonSaveAsClicked" swapped="no"/>
|
||||
<accelerator key="s" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="homogeneous">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkToolButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="tooltip_text" translatable="yes">Export</property>
|
||||
<property name="label" translatable="yes">Export</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="stock_id">gtk-convert</property>
|
||||
<signal name="clicked" handler="ButtonExportClicked" swapped="no"/>
|
||||
<accelerator key="e" signal="clicked" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
|
||||
</object>
|
||||
<packing>
|
||||
|
@ -263,75 +444,135 @@
|
|||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_width">200</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="TreeViewItems">
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="margin_start">3</property>
|
||||
<property name="margin_end">3</property>
|
||||
<property name="spacing">3</property>
|
||||
<child>
|
||||
<object class="GtkSearchEntry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="primary_icon_name">edit-find-symbolic</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkMenuButton">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="popover">popover1</property>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="search_column">1</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<signal name="changed" handler="TreeViewItemsSelectionChanged" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
<property name="hexpand">True</property>
|
||||
<property name="vexpand">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="min_content_width">200</property>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">♥</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<object class="GtkTreeView" id="TreeViewItems">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="rules_hint">True</property>
|
||||
<property name="enable_search">False</property>
|
||||
<property name="search_column">1</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection">
|
||||
<signal name="changed" handler="TreeViewItemsSelectionChanged" swapped="no"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">100</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">♥</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Price (¥)</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="sizing">fixed</property>
|
||||
<property name="fixed_width">100</property>
|
||||
<property name="title" translatable="yes">Name</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="clickable">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Price (AUD)</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Price (¥)</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Ending at</property>
|
||||
<property name="clickable">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Price (AUD)</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="resizable">True</property>
|
||||
<property name="title" translatable="yes">Ending at</property>
|
||||
<property name="clickable">True</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
|
|
Loading…
Reference in a new issue